diff --git a/.circleci/config.yml b/.circleci/config.yml
index ec9fa3bade..b392f5c301 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -348,7 +348,7 @@ jobs:
type: string
docker:
- image: speckle/pre-commit-runner:latest
- resource_class: medium
+ resource_class: large
working_directory: *work-dir
steps:
- checkout
@@ -416,6 +416,8 @@ jobs:
S3_CREATE_BUCKET: 'true'
REDIS_URL: 'redis://127.0.0.1:6379'
S3_REGION: '' # optional, defaults to 'us-east-1'
+ FF_AUTOMATE_MODULE_ENABLED: 'true'
+ AUTOMATE_ENCRYPTION_KEYS_PATH: 'test/assets/automate/encryptionKeys.json'
steps:
- checkout
- restore_cache:
diff --git a/.husky/pre-commit b/.husky/pre-commit
index b9acddca1c..9c3e8a14da 100755
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -6,7 +6,10 @@ if [ -n "$CI" ]
then
echo "running eslint"
yarn lint
+ echo "...eslint done"
+ echo "running prettier"
yarn prettier:check
+ echo "...prettier done"
else
# shellcheck disable=SC1090
. "$(dirname "$0")/_/husky.sh"
diff --git a/docker-compose-ingress.yml b/docker-compose-ingress.yml
new file mode 100644
index 0000000000..38f99bce02
--- /dev/null
+++ b/docker-compose-ingress.yml
@@ -0,0 +1,9 @@
+services:
+ nginx:
+ restart: always
+ image: nginx:1-alpine
+ ports:
+ - 8080:8080
+ volumes:
+ - ./utils/docker-compose-ingress/nginx/default.conf:/etc/nginx/conf.d/default.conf
+ network_mode: host
diff --git a/package.json b/package.json
index 3b1e6004cd..8af86b2c36 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"build:public": "yarn workspaces foreach -ptv --no-private run build",
"build:tailwind-deps": "yarn workspaces foreach -iv -j unlimited --include '{@speckle/shared,@speckle/tailwind-theme,@speckle/ui-components}' run build",
"ensure:tailwind-deps": "node ./utils/ensure-tailwind-deps.mjs",
- "lint": "eslint . --ext .js,.ts,.vue --max-warnings=0",
+ "lint": "node --max-old-space-size=4096 ./node_modules/eslint/bin/eslint.js . --ext .js,.ts,.vue --max-warnings=0",
"helm:readme:generate": "./utils/helm/update-schema-json.sh",
"prettier:check": "prettier --check .",
"prettier:fix": "prettier --write .",
@@ -45,6 +45,7 @@
"@types/eslint": "^8.4.1",
"@types/lockfile": "^1.0.2",
"commitizen": "^4.2.5",
+ "cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
diff --git a/packages/frontend-2/assets/images/banners/speckleverse.svg b/packages/frontend-2/assets/images/banners/speckleverse.svg
new file mode 100644
index 0000000000..593dee2380
--- /dev/null
+++ b/packages/frontend-2/assets/images/banners/speckleverse.svg
@@ -0,0 +1,65 @@
+
diff --git a/packages/frontend-2/components/auth/LoginPanel.vue b/packages/frontend-2/components/auth/LoginPanel.vue
index ece8aa6c10..9045f187da 100644
--- a/packages/frontend-2/components/auth/LoginPanel.vue
+++ b/packages/frontend-2/components/auth/LoginPanel.vue
@@ -1,6 +1,7 @@
+
diff --git a/packages/frontend-2/components/automate/function/Card.vue b/packages/frontend-2/components/automate/function/Card.vue
index 5f59d2b855..5808b4b9de 100644
--- a/packages/frontend-2/components/automate/function/Card.vue
+++ b/packages/frontend-2/components/automate/function/Card.vue
@@ -20,7 +20,12 @@
{{ fn.name }}
-
@@ -52,10 +57,14 @@
- Featured
+ Outdated
+ Featured
@@ -75,8 +84,10 @@ graphql(`
isFeatured
description
logo
- creator {
+ repo {
id
+ url
+ owner
name
}
}
@@ -93,10 +104,11 @@ const props = defineProps<{
noButtons?: boolean
externalMoreInfo?: boolean
selected?: boolean
+ isOutdated?: boolean
}>()
const NuxtLink = resolveComponent('NuxtLink')
-const hasLabel = computed(() => props.fn.isFeatured)
+const hasLabel = computed(() => props.fn.isFeatured || props.isOutdated)
const { html: plaintextDescription } = useMarkdown(
computed(() => props.fn.description || ''),
{ plaintext: true }
diff --git a/packages/frontend-2/components/automate/function/CreateDialog.vue b/packages/frontend-2/components/automate/function/CreateDialog.vue
index 0e3e928d2b..c632b971c0 100644
--- a/packages/frontend-2/components/automate/function/CreateDialog.vue
+++ b/packages/frontend-2/components/automate/function/CreateDialog.vue
@@ -172,6 +172,7 @@ const buttons = computed((): LayoutDialogButton[] => {
case FunctionCreateSteps.Authorize:
return [
{
+ id: 'authorizeClose',
text: 'Close',
props: {
color: 'secondary',
@@ -180,6 +181,7 @@ const buttons = computed((): LayoutDialogButton[] => {
onClick: () => (open.value = false)
},
{
+ id: 'authorizeAuthorize',
text: 'Authorize',
props: {
fullWidth: true,
@@ -192,6 +194,7 @@ const buttons = computed((): LayoutDialogButton[] => {
case FunctionCreateSteps.Template:
return [
{
+ id: 'templateNext',
text: 'Next',
props: {
iconRight: ChevronRightIcon,
@@ -203,6 +206,7 @@ const buttons = computed((): LayoutDialogButton[] => {
case FunctionCreateSteps.Details:
return [
{
+ id: 'detailsPrevious',
text: 'Previous',
props: {
color: 'secondary',
@@ -212,6 +216,7 @@ const buttons = computed((): LayoutDialogButton[] => {
onClick: () => step.value--
},
{
+ id: 'detailsCreate',
text: 'Create',
submit: true,
disabled: mutationLoading.value
@@ -220,6 +225,7 @@ const buttons = computed((): LayoutDialogButton[] => {
case FunctionCreateSteps.Done:
return [
{
+ id: 'doneClose',
text: 'Close',
props: {
color: 'secondary',
@@ -228,6 +234,7 @@ const buttons = computed((): LayoutDialogButton[] => {
onClick: () => (open.value = false)
},
{
+ id: 'doneGoToFunction',
text: 'Go to Function',
props: {
iconRight: ArrowRightIcon,
diff --git a/packages/frontend-2/components/automate/function/create-dialog/DoneStep.vue b/packages/frontend-2/components/automate/function/create-dialog/DoneStep.vue
index bc8016fed5..4505f14b57 100644
--- a/packages/frontend-2/components/automate/function/create-dialog/DoneStep.vue
+++ b/packages/frontend-2/components/automate/function/create-dialog/DoneStep.vue
@@ -37,14 +37,18 @@ import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/outline'
import { graphql } from '~/lib/common/generated/gql'
import {
buildGithubRepoHttpCloneUrl,
- buildGithubRepoSshUrl,
- parseGithubRepoUrl
+ buildGithubRepoSshUrl
} from '~/lib/common/helpers/github'
graphql(`
fragment AutomateFunctionCreateDialogDoneStep_AutomateFunction on AutomateFunction {
id
- repoUrl
+ repo {
+ id
+ url
+ owner
+ name
+ }
...AutomationsFunctionsCard_AutomateFunction
}
`)
@@ -53,21 +57,18 @@ const props = defineProps<{
createdFunction: AutomateFunctionCreateDialogDoneStep_AutomateFunctionFragment
}>()
-const repoDetails = computed(() => parseGithubRepoUrl(props.createdFunction.repoUrl))
-
const repoCodespaceLink = computed(() => {
- if (!repoDetails.value) return undefined
-
- return `https://codespaces.new/${repoDetails.value.owner}/${repoDetails.value.name}`
+ const { owner, name } = props.createdFunction.repo
+ return `https://codespaces.new/${owner}/${name}`
})
-const repoLink = computed(() => props.createdFunction.repoUrl)
+const repoLink = computed(() => props.createdFunction.repo.url)
const cloneInstructions = computed(() => {
- if (!repoDetails.value) return ''
+ const repo = props.createdFunction.repo
- const htmlUrl = buildGithubRepoHttpCloneUrl(repoDetails.value)
- const sshUrl = buildGithubRepoSshUrl(repoDetails.value)
+ const htmlUrl = buildGithubRepoHttpCloneUrl(repo)
+ const sshUrl = buildGithubRepoSshUrl(repo)
return `# Clone the repository using SSH (recommended)
git clone ${sshUrl}
diff --git a/packages/frontend-2/components/automate/function/page/Header.vue b/packages/frontend-2/components/automate/function/page/Header.vue
index 1479a8f4c3..a0c62077e1 100644
--- a/packages/frontend-2/components/automate/function/page/Header.vue
+++ b/packages/frontend-2/components/automate/function/page/Header.vue
@@ -48,8 +48,11 @@ graphql(`
id
name
logo
- creator {
+ repo {
id
+ url
+ owner
+ name
}
}
`)
diff --git a/packages/frontend-2/components/automate/function/page/Info.vue b/packages/frontend-2/components/automate/function/page/Info.vue
index c16530c757..8f5d8cff1e 100644
--- a/packages/frontend-2/components/automate/function/page/Info.vue
+++ b/packages/frontend-2/components/automate/function/page/Info.vue
@@ -28,7 +28,7 @@
diff --git a/packages/ui-components/src/components/layout/Tabs.stories.ts b/packages/ui-components/src/components/layout/Tabs.stories.ts
deleted file mode 100644
index 8112981916..0000000000
--- a/packages/ui-components/src/components/layout/Tabs.stories.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import type { Meta, StoryObj } from '@storybook/vue3'
-import LayoutTabs from '~~/src/components/layout/Tabs.vue'
-import type { LayoutTabItem } from '~~/src/helpers/layout/components'
-
-export default {
- component: LayoutTabs,
- parameters: {
- docs: {
- description: {
- component: 'Standard tabs component'
- }
- }
- }
-} as Meta
-
-const defaultItems: LayoutTabItem[] = [
- { title: 'First tab', id: 'first' },
- { title: 'Second tab', id: 'second' }
-]
-
-export const Default: StoryObj = {
- render: (args) => ({
- components: { LayoutTabs },
- setup() {
- return { args }
- },
- template: `
-
-
- Title: {{ activeItem.title }}
- ID: {{ activeItem.id }}
-
-
`
- }),
- args: {
- items: defaultItems
- }
-}
diff --git a/packages/ui-components/src/components/layout/Tabs.vue b/packages/ui-components/src/components/layout/Tabs.vue
deleted file mode 100644
index eb33975a31..0000000000
--- a/packages/ui-components/src/components/layout/Tabs.vue
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
- {{ item.title }}
-
-
-
-
-
-
diff --git a/packages/ui-components/src/components/layout/tabs/Horizontal.stories.ts b/packages/ui-components/src/components/layout/tabs/Horizontal.stories.ts
new file mode 100644
index 0000000000..eb7c035217
--- /dev/null
+++ b/packages/ui-components/src/components/layout/tabs/Horizontal.stories.ts
@@ -0,0 +1,67 @@
+import type { Meta, StoryObj } from '@storybook/vue3'
+import LayoutTabsHorizontal from '~~/src/components/layout/tabs/Horizontal.vue'
+import type { LayoutPageTabItem } from '~~/src/helpers/layout/components'
+import { useStorybookVmodel } from '~~/src/composables/testing'
+
+const items: LayoutPageTabItem[] = [
+ { title: 'Models', id: 'models', count: 300 },
+ { title: 'Discussions', id: 'discussions' },
+ { title: 'Automations', id: 'automations', tag: 'New' },
+ { title: 'Settings', id: 'settings' },
+ {
+ title: 'Disabled Item',
+ id: 'disabled',
+ disabled: true,
+ disabledMessage: 'Example disabled message'
+ }
+]
+
+export default {
+ component: LayoutTabsHorizontal,
+ parameters: {
+ docs: {
+ description: {
+ component:
+ 'This component displays a set of horizontal tabs, allowing user interaction and selection.'
+ }
+ }
+ },
+ argTypes: {
+ items: {
+ description: 'Array of items to display in the tabs'
+ },
+ title: {
+ description: 'Title of the tabs, displayed above the tabs if provided'
+ },
+ activeItem: {
+ description: 'The active item model. Not required.'
+ },
+ 'update:activeItem': {
+ description: 'Event emitted when the active item changes',
+ type: 'function',
+ action: 'v-model:activeItem'
+ }
+ }
+} as Meta
+
+export const Default: StoryObj = {
+ render: (args, ctx) => ({
+ components: { LayoutTabsHorizontal },
+ setup() {
+ const { model } = useStorybookVmodel({ args, prop: 'activeItem', ctx })
+ return { args, model }
+ },
+ template: `
+
+
+ Title: {{ activeItem?.title }}
+ ID: {{ activeItem?.id }}
+
+
`
+ }),
+ args: {
+ items,
+ title: 'Tab Example',
+ activeItem: items[2]
+ }
+}
diff --git a/packages/ui-components/src/components/layout/tabs/Horizontal.vue b/packages/ui-components/src/components/layout/tabs/Horizontal.vue
new file mode 100644
index 0000000000..1fc12fc530
--- /dev/null
+++ b/packages/ui-components/src/components/layout/tabs/Horizontal.vue
@@ -0,0 +1,241 @@
+
+
+
+
+
+
diff --git a/packages/ui-components/src/components/layout/tabs/Vertical.stories.ts b/packages/ui-components/src/components/layout/tabs/Vertical.stories.ts
new file mode 100644
index 0000000000..d7099ff20c
--- /dev/null
+++ b/packages/ui-components/src/components/layout/tabs/Vertical.stories.ts
@@ -0,0 +1,67 @@
+import type { Meta, StoryObj } from '@storybook/vue3'
+import LayoutTabsVertical from '~~/src/components/layout/tabs/Vertical.vue'
+import type { LayoutPageTabItem } from '~~/src/helpers/layout/components'
+import { useStorybookVmodel } from '~~/src/composables/testing'
+
+const items: LayoutPageTabItem[] = [
+ { title: 'Models', id: 'models', count: 300 },
+ { title: 'Discussions', id: 'discussions' },
+ { title: 'Automations', id: 'automations', tag: 'New' },
+ { title: 'Settings', id: 'settings' },
+ {
+ title: 'Disabled Item',
+ id: 'disabled',
+ disabled: true,
+ disabledMessage: 'Example disabled message'
+ }
+]
+
+export default {
+ component: LayoutTabsVertical,
+ parameters: {
+ docs: {
+ description: {
+ component:
+ 'This component displays a set of vertical tabs, allowing user interaction and selection.'
+ }
+ }
+ },
+ argTypes: {
+ items: {
+ description: 'Array of items to display in the tabs'
+ },
+ title: {
+ description: 'Title of the tabs, displayed above the tabs if provided'
+ },
+ activeItem: {
+ description: 'The active item model. Not required.'
+ },
+ 'update:activeItem': {
+ description: 'Event emitted when the active item changes',
+ type: 'function',
+ action: 'v-model:activeItem'
+ }
+ }
+} as Meta
+
+export const Default: StoryObj = {
+ render: (args, ctx) => ({
+ components: { LayoutTabsVertical },
+ setup() {
+ const { model } = useStorybookVmodel({ args, prop: 'activeItem', ctx })
+ return { args, model }
+ },
+ template: `
+
+
+ Title: {{ activeItem?.title }}
+ ID: {{ activeItem?.id }}
+
+
`
+ }),
+ args: {
+ items,
+ title: 'Tab Example',
+ activeItem: items[2]
+ }
+}
diff --git a/packages/ui-components/src/components/layout/tabs/Vertical.vue b/packages/ui-components/src/components/layout/tabs/Vertical.vue
new file mode 100644
index 0000000000..46e1f130ab
--- /dev/null
+++ b/packages/ui-components/src/components/layout/tabs/Vertical.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/ui-components/src/composables/form/textInput.ts b/packages/ui-components/src/composables/form/textInput.ts
index 30ba602533..e607a9fb89 100644
--- a/packages/ui-components/src/composables/form/textInput.ts
+++ b/packages/ui-components/src/composables/form/textInput.ts
@@ -46,7 +46,10 @@ export function useTextInputCore
(params: {
})
const labelClasses = computed(() => {
- const classParts = ['block label text-foreground-2 mb-2']
+ const classParts = [
+ 'flex label mb-1.5',
+ unref(props.color) === 'foundation' ? 'text-foreground' : 'text-foreground-2'
+ ]
if (!unref(props.showLabel)) {
classParts.push('sr-only')
}
@@ -58,7 +61,7 @@ export function useTextInputCore(params: {
const classParts: string[] = [
'focus:outline-none disabled:cursor-not-allowed disabled:bg-foundation-disabled',
'disabled:text-disabled-muted placeholder:text-foreground-2',
- 'rounded'
+ 'rounded-md'
]
return classParts.join(' ')
@@ -80,7 +83,9 @@ export function useTextInputCore(params: {
const color = unref(props.color)
if (color === 'foundation') {
- classParts.push('bg-foundation shadow-sm hover:shadow')
+ classParts.push(
+ 'bg-foundation !border border-outline-3 focus:border-outline-1 focus:!outline-0 focus:!ring-0'
+ )
} else if (color === 'transparent') {
classParts.push('bg-transparent')
} else {
@@ -108,7 +113,7 @@ export function useTextInputCore(params: {
hasHelpTip.value ? `${unref(props.name)}-${internalHelpTipId.value}` : undefined
)
const helpTipClasses = computed((): string => {
- const classParts = ['mt-2 text-xs sm:text-sm']
+ const classParts = ['mt-2 text-xs']
classParts.push(error.value ? 'text-danger' : 'text-foreground-2')
return classParts.join(' ')
})
diff --git a/packages/ui-components/src/helpers/layout/components.ts b/packages/ui-components/src/helpers/layout/components.ts
index 60192ad57f..53cb347188 100644
--- a/packages/ui-components/src/helpers/layout/components.ts
+++ b/packages/ui-components/src/helpers/layout/components.ts
@@ -2,6 +2,7 @@ import type { ConcreteComponent } from 'vue'
import type { FormButton } from '~~/src/lib'
type FormButtonProps = InstanceType['$props']
+import type { PropAnyComponent } from '~~/src/helpers/common/components'
export enum GridListToggleValue {
Grid = 'grid',
@@ -16,9 +17,11 @@ export type LayoutTabItem = {
export type LayoutPageTabItem = {
title: string
id: I
- icon?: ConcreteComponent
count?: number
tag?: string
+ icon?: PropAnyComponent
+ disabled?: boolean
+ disabledMessage?: string
}
export type LayoutMenuItem = {
@@ -33,7 +36,13 @@ export type LayoutMenuItem = {
export type LayoutDialogButton = {
text: string
props?: Record & FormButtonProps
- onClick?: () => void
+ onClick?: (e: MouseEvent) => void
disabled?: boolean
submit?: boolean
+ /**
+ * This should uniquely identify the button within the form. Even if you have different sets
+ * of buttons rendered on different steps of a wizard, all of them should have unique IDs to
+ * ensure proper form functionality.
+ */
+ id?: string
}
diff --git a/packages/ui-components/src/lib.ts b/packages/ui-components/src/lib.ts
index 4c8bb336cc..a83c6e23f9 100644
--- a/packages/ui-components/src/lib.ts
+++ b/packages/ui-components/src/lib.ts
@@ -21,6 +21,7 @@ import CommonVimeoEmbed from '~~/src/components/common/VimeoEmbed.vue'
import FormCardButton from '~~/src/components/form/CardButton.vue'
import FormCheckbox from '~~/src/components/form/Checkbox.vue'
import FormRadio from '~~/src/components/form/Radio.vue'
+import FormRadioGroup from '~~/src/components/form/RadioGroup.vue'
import FormTextArea from '~~/src/components/form/TextArea.vue'
import FormTextInput from '~~/src/components/form/TextInput.vue'
import * as ValidationHelpers from '~~/src/helpers/common/validation'
@@ -57,8 +58,8 @@ import {
} from '~~/src/composables/common/window'
import LayoutMenu from '~~/src/components/layout/Menu.vue'
import type { LayoutMenuItem, LayoutTabItem } from '~~/src/helpers/layout/components'
-import LayoutTabs from '~~/src/components/layout/Tabs.vue'
-import LayoutPageTabs from '~~/src/components/layout/PageTabs.vue'
+import LayoutTabsHoriztonal from '~~/src/components/layout/tabs/Horizontal.vue'
+import LayoutTabsVertical from '~~/src/components/layout/tabs/Vertical.vue'
import LayoutTable from '~~/src/components/layout/Table.vue'
import InfiniteLoading from '~~/src/components/InfiniteLoading.vue'
import type { InfiniteLoaderState } from '~~/src/helpers/global/components'
@@ -114,6 +115,7 @@ export {
FormCardButton,
FormCheckbox,
FormRadio,
+ FormRadioGroup,
FormTextArea,
FormTextInput,
FormSwitch,
@@ -141,8 +143,8 @@ export {
useOnBeforeWindowUnload,
useResponsiveHorizontalDirectionCalculation,
LayoutMenu,
- LayoutTabs,
- LayoutPageTabs,
+ LayoutTabsHoriztonal,
+ LayoutTabsVertical,
LayoutTable,
InfiniteLoading,
LayoutPanel,
diff --git a/packages/viewer-sandbox/.eslintrc.js b/packages/viewer-sandbox/.eslintrc.js
index 5a57524a34..6d9efc2ad8 100644
--- a/packages/viewer-sandbox/.eslintrc.js
+++ b/packages/viewer-sandbox/.eslintrc.js
@@ -11,6 +11,9 @@ const config = {
parserOptions: {
sourceType: 'module'
},
+ rules: {
+ '@typescript-eslint/no-non-null-assertion': 'error'
+ },
overrides: [
{
files: '*.ts',
diff --git a/packages/viewer-sandbox/src/Extensions/BoxSelection.ts b/packages/viewer-sandbox/src/Extensions/BoxSelection.ts
index d390115247..ee369b7624 100644
--- a/packages/viewer-sandbox/src/Extensions/BoxSelection.ts
+++ b/packages/viewer-sandbox/src/Extensions/BoxSelection.ts
@@ -4,13 +4,7 @@ import { ObjectLayers } from '@speckle/viewer'
import { NodeRenderView } from '@speckle/viewer'
import { SelectionExtension } from '@speckle/viewer'
import { BatchObject } from '@speckle/viewer'
-import {
- Extension,
- IViewer,
- GeometryType,
- MeshBatch,
- CameraController
-} from '@speckle/viewer'
+import { Extension, IViewer, GeometryType, CameraController } from '@speckle/viewer'
import {
Matrix4,
ShaderMaterial,
@@ -113,17 +107,18 @@ export class BoxSelection extends Extension {
/** Gets the object ids that fall withing the provided selection box */
private getSelectionIds(selectionBox: Box3) {
+ /** Get the renderer */
+ const renderer = this.viewer.getRenderer()
/** Get the mesh batches */
- const batches = this.viewer
- .getRenderer()
- .batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[]
-
+ const batches = renderer.batcher.getBatches(undefined, GeometryType.MESH)
/** Compute the clip matrix */
const clipMatrix = new Matrix4()
- clipMatrix.multiplyMatrices(
- this.viewer.getRenderer().renderingCamera.projectionMatrix,
- this.viewer.getRenderer().renderingCamera.matrixWorldInverse
- )
+ if (renderer.renderingCamera) {
+ clipMatrix.multiplyMatrices(
+ renderer.renderingCamera.projectionMatrix,
+ renderer.renderingCamera.matrixWorldInverse
+ )
+ }
/** We're using three-mesh-bvh library for out BVH
* Go over each batch and test it against the TAS only.
diff --git a/packages/viewer-sandbox/src/Extensions/CameraPlanes.ts b/packages/viewer-sandbox/src/Extensions/CameraPlanes.ts
index 7ed3378368..0815712eb8 100644
--- a/packages/viewer-sandbox/src/Extensions/CameraPlanes.ts
+++ b/packages/viewer-sandbox/src/Extensions/CameraPlanes.ts
@@ -3,7 +3,6 @@ import {
Extension,
GeometryType,
IViewer,
- MeshBatch,
Vector3
} from '@speckle/viewer'
import { PerspectiveCamera } from 'three'
@@ -20,9 +19,7 @@ export class CameraPlanes extends Extension {
public constructor(viewer: IViewer) {
super(viewer)
- this.camerController = viewer.getExtension(
- CameraController as new () => CameraController
- )
+ this.camerController = viewer.getExtension(CameraController) as CameraController
}
public onEarlyUpdate(): void {
@@ -30,7 +27,10 @@ export class CameraPlanes extends Extension {
}
public computePerspectiveCameraPlanes() {
- const camera = this.viewer.getRenderer().renderingCamera as PerspectiveCamera
+ const renderer = this.viewer.getRenderer()
+ if (!renderer.renderingCamera) return
+
+ const camera = renderer.renderingCamera as PerspectiveCamera
const minDist = this.getClosestGeometryDistance(camera)
if (minDist === Number.POSITIVE_INFINITY) return
@@ -42,7 +42,7 @@ export class CameraPlanes extends Extension {
1 +
Math.pow(Math.tan(((fov / 180) * Math.PI) / 2), 2) * (Math.pow(aspect, 2) + 1)
)
- this.viewer.getRenderer().renderingCamera.near = nearPlane
+ renderer.renderingCamera.near = nearPlane
console.log(minDist, nearPlane)
}
@@ -55,11 +55,13 @@ export class CameraPlanes extends Extension {
const batches = this.viewer
.getRenderer()
- .batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[]
+ .batcher.getBatches(undefined, GeometryType.MESH)
let minDist = Number.POSITIVE_INFINITY
const minPoint = new Vector3()
for (let b = 0; b < batches.length; b++) {
const result = batches[b].mesh.TAS.closestPointToPoint(cameraPosition)
+ if (!result) continue
+
const planarity = cameraDir.dot(
new Vector3().subVectors(result.point, cameraPosition).normalize()
)
diff --git a/packages/viewer-sandbox/src/Extensions/ExtendedSelection.ts b/packages/viewer-sandbox/src/Extensions/ExtendedSelection.ts
index b49c61b318..6777802a32 100644
--- a/packages/viewer-sandbox/src/Extensions/ExtendedSelection.ts
+++ b/packages/viewer-sandbox/src/Extensions/ExtendedSelection.ts
@@ -34,10 +34,14 @@ export class ExtendedSelection extends SelectionExtension {
}
private initGizmo() {
+ const rendeder = this.viewer.getRenderer()
+ if (!rendeder.renderingCamera)
+ throw new Error('Cannot use ExtendedSelection without a rendering camera')
+
/** Create a new TransformControls gizmo */
this.transformControls = new TransformControls(
- this.viewer.getRenderer().renderingCamera,
- this.viewer.getRenderer().renderer.domElement
+ rendeder.renderingCamera,
+ rendeder.renderer.domElement
)
/** The gizmo creates an entire hierarchy of children internally,
* and three.js objects do not inherit parent layer values, so
diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts
index 1d941b1ca4..fd06d03c94 100644
--- a/packages/viewer-sandbox/src/Sandbox.ts
+++ b/packages/viewer-sandbox/src/Sandbox.ts
@@ -2,7 +2,7 @@
import { Box3, SectionTool, SpeckleStandardMaterial, TreeNode } from '@speckle/viewer'
import {
CanonicalView,
- DebugViewer,
+ Viewer,
PropertyInfo,
SelectionEvent,
SunLightConfiguration,
@@ -14,7 +14,8 @@ import {
DiffExtension,
SpeckleLoader,
ObjLoader,
- UrlHelper
+ UrlHelper,
+ LoaderEvent
} from '@speckle/viewer'
import { FolderApi, Pane } from 'tweakpane'
import { DiffResult } from '@speckle/viewer'
@@ -25,7 +26,7 @@ import { FilteringExtension } from '@speckle/viewer'
import { MeasurementsExtension } from '@speckle/viewer'
import { CameraController } from '@speckle/viewer'
import { UpdateFlags } from '@speckle/viewer'
-import { Viewer, AssetType, Assets } from '@speckle/viewer'
+import { AssetType, Assets } from '@speckle/viewer'
import Neutral from '../assets/hdri/Neutral.png'
import Mild from '../assets/hdri/Mild.png'
import Mild2 from '../assets/hdri/Mild2.png'
@@ -133,7 +134,7 @@ export default class Sandbox {
public constructor(
container: HTMLElement,
- viewer: DebugViewer,
+ viewer: Viewer,
selectionList: SelectionEvent[]
) {
this.viewer = viewer
@@ -164,20 +165,17 @@ export default class Sandbox {
this.batchesParams.totalBvhSize = this.getBVHSize()
this.refresh()
})
- viewer.on(ViewerEvent.UnloadComplete, async (url: string) => {
- url
+ viewer.on(ViewerEvent.UnloadComplete, async () => {
this.removeViewControls()
this.addViewControls()
this.properties = await this.viewer.getObjectProperties()
})
- viewer.on(ViewerEvent.UnloadAllComplete, async (url: string) => {
+ viewer.on(ViewerEvent.UnloadAllComplete, async () => {
this.removeViewControls()
this.addViewControls()
this.properties = await this.viewer.getObjectProperties()
- // viewer.World.resetWorld()
- url
})
- viewer.on(ViewerEvent.ObjectClicked, (selectionEvent: SelectionEvent) => {
+ viewer.on(ViewerEvent.ObjectClicked, (selectionEvent) => {
if (selectionEvent && selectionEvent.hits) {
const firstHitNode = selectionEvent.hits[0].node
if (firstHitNode) {
@@ -218,13 +216,14 @@ export default class Sandbox {
})
const position = { value: { x: 0, y: 0, z: 0 } }
folder.addInput(position, 'value', { label: 'Position' }).on('change', () => {
- const rvs = this.viewer
- .getWorldTree()
- .getRenderTree(url)
- .getRenderViewsForNodeId(url)
- for (let k = 0; k < rvs.length; k++) {
- const object = this.viewer.getRenderer().getObject(rvs[k])
- object.transformTRS(position.value, undefined, undefined, undefined)
+ const tree = this.viewer.getWorldTree()
+ const rvs = tree.getRenderTree(url)?.getRenderViewsForNodeId(url)
+ if (rvs) {
+ for (let k = 0; k < rvs.length; k++) {
+ const object = this.viewer.getRenderer().getObject(rvs[k])
+ if (object)
+ object.transformTRS(position.value, undefined, undefined, undefined)
+ }
}
this.viewer.requestRender(UpdateFlags.RENDER | UpdateFlags.SHADOWS)
this.viewer.getRenderer().updateShadowCatcher()
@@ -276,10 +275,7 @@ export default class Sandbox {
title: `Object: ${node.model.id}`
})
- const rvs = this.viewer
- .getWorldTree()
- .getRenderTree()
- .getRenderViewsForNode(node, node)
+ const rvs = this.viewer.getWorldTree().getRenderTree().getRenderViewsForNode(node)
const objects: BatchObject[] = []
for (let k = 0; k < rvs.length; k++) {
const batchObject = this.viewer.getRenderer().getObject(rvs[k])
@@ -344,7 +340,7 @@ export default class Sandbox {
.on('change', () => {
const unionBox: Box3 = new Box3()
objects.forEach((obj: BatchObject) => {
- unionBox.union(obj.renderView.aabb)
+ unionBox.union(obj.renderView.aabb || new Box3())
})
const origin = unionBox.getCenter(new Vector3())
objects.forEach((obj: BatchObject) => {
@@ -577,7 +573,7 @@ export default class Sandbox {
.on('change', () => {
const batches = this.viewer
.getRenderer()
- .batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[]
+ .batcher.getBatches(undefined, GeometryType.MESH)
batches.forEach((batch: MeshBatch) => {
const materials = batch.materials as SpeckleStandardMaterial[]
materials.forEach((material: SpeckleStandardMaterial) => {
@@ -595,7 +591,7 @@ export default class Sandbox {
.on('change', () => {
const batches = this.viewer
.getRenderer()
- .batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[]
+ .batcher.getBatches(undefined, GeometryType.MESH)
batches.forEach((batch: MeshBatch) => {
const materials = batch.materials as SpeckleStandardMaterial[]
materials.forEach((material: SpeckleStandardMaterial) => {
@@ -617,7 +613,7 @@ export default class Sandbox {
.on('change', () => {
const batches = this.viewer
.getRenderer()
- .batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[]
+ .batcher.getBatches(undefined, GeometryType.MESH)
batches.forEach((batch: MeshBatch) => {
const materials = batch.materials as SpeckleStandardMaterial[]
materials.forEach((material: SpeckleStandardMaterial) => {
@@ -947,6 +943,13 @@ export default class Sandbox {
this.viewer.setLightConfiguration(this.lightParams)
})
+ const updateShadowcatcher = () => {
+ const shadowCatcher = this.viewer.getRenderer().shadowcatcher
+ if (shadowCatcher) {
+ shadowCatcher.configuration = this.shadowCatcherParams
+ this.viewer.getRenderer().updateShadowCatcher()
+ }
+ }
shadowcatcherFolder
.addInput(this.shadowCatcherParams, 'textureSize', {
label: 'Texture Size',
@@ -954,10 +957,8 @@ export default class Sandbox {
max: 1024,
step: 1
})
- .on('change', (value) => {
- value
- this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams
- this.viewer.getRenderer().updateShadowCatcher()
+ .on('change', () => {
+ updateShadowcatcher()
})
shadowcatcherFolder
.addInput(this.shadowCatcherParams, 'weights', {
@@ -967,10 +968,8 @@ export default class Sandbox {
z: { min: -100, max: 100 },
w: { min: -100, max: 100 }
})
- .on('change', (value) => {
- value
- this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams
- this.viewer.getRenderer().updateShadowCatcher()
+ .on('change', () => {
+ updateShadowcatcher()
})
shadowcatcherFolder
.addInput(this.shadowCatcherParams, 'blurRadius', {
@@ -979,10 +978,8 @@ export default class Sandbox {
max: 128,
step: 1
})
- .on('change', (value) => {
- value
- this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams
- this.viewer.getRenderer().updateShadowCatcher()
+ .on('change', () => {
+ updateShadowcatcher()
})
shadowcatcherFolder
.addInput(this.shadowCatcherParams, 'stdDeviation', {
@@ -991,10 +988,8 @@ export default class Sandbox {
max: 128,
step: 1
})
- .on('change', (value) => {
- value
- this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams
- this.viewer.getRenderer().updateShadowCatcher()
+ .on('change', () => {
+ updateShadowcatcher()
})
shadowcatcherFolder
.addInput(this.shadowCatcherParams, 'sigmoidRange', {
@@ -1003,10 +998,8 @@ export default class Sandbox {
max: 10,
step: 0.1
})
- .on('change', (value) => {
- value
- this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams
- this.viewer.getRenderer().updateShadowCatcher()
+ .on('change', () => {
+ updateShadowcatcher()
})
shadowcatcherFolder
.addInput(this.shadowCatcherParams, 'sigmoidStrength', {
@@ -1015,10 +1008,8 @@ export default class Sandbox {
max: 10,
step: 0.1
})
- .on('change', (value) => {
- value
- this.viewer.getRenderer().shadowcatcher.configuration = this.shadowCatcherParams
- this.viewer.getRenderer().updateShadowCatcher()
+ .on('change', () => {
+ updateShadowcatcher()
})
}
@@ -1301,9 +1292,19 @@ export default class Sandbox {
url,
authToken,
true,
- undefined,
- 1
+ undefined
)
+ /** Too spammy */
+ // loader.on(LoaderEvent.LoadProgress, (arg: { progress: number; id: string }) => {
+ // console.warn(arg)
+ // })
+ loader.on(LoaderEvent.LoadCancelled, (resource: string) => {
+ console.warn(`Resource ${resource} loading was canceled`)
+ })
+ loader.on(LoaderEvent.LoadWarning, (arg: { message: string }) => {
+ console.error(`Loader warning: ${arg.message}`)
+ })
+
await this.viewer.loadObject(loader, true)
}
localStorage.setItem('last-load-url', url)
diff --git a/packages/viewer-sandbox/src/main-multi.ts b/packages/viewer-sandbox/src/main-multi.ts
index 75324f2d24..dd9cc3b433 100644
--- a/packages/viewer-sandbox/src/main-multi.ts
+++ b/packages/viewer-sandbox/src/main-multi.ts
@@ -2,13 +2,7 @@ import {
DefaultViewerParams,
SelectionEvent,
ViewerEvent,
- DebugViewer,
- Viewer
-} from '@speckle/viewer'
-
-import './style.css'
-import Sandbox from './Sandbox'
-import {
+ Viewer,
CameraController,
SelectionExtension,
SectionTool,
@@ -16,15 +10,8 @@ import {
MeasurementsExtension
} from '@speckle/viewer'
-// const container0 = document.querySelector('#renderer0')
-// if (!container0) {
-// throw new Error("Couldn't find #app container!")
-// }
-
-// const container1 = document.querySelector('#renderer1')
-// if (!container1) {
-// throw new Error("Couldn't find #app container!")
-// }
+import './style.css'
+import Sandbox from './Sandbox'
const createViewer = async (containerName: string, stream: string) => {
const container = document.querySelector(containerName)
@@ -44,7 +31,7 @@ const createViewer = async (containerName: string, stream: string) => {
params.verbose = true
const multiSelectList: SelectionEvent[] = []
- const viewer: Viewer = new DebugViewer(container, params)
+ const viewer: Viewer = new Viewer(container, params)
await viewer.init()
const cameraController = viewer.createExtension(CameraController)
@@ -58,21 +45,12 @@ const createViewer = async (containerName: string, stream: string) => {
sectionOutlines // use it
measurements // use it
- const sandbox = new Sandbox(controlsContainer, viewer as DebugViewer, multiSelectList)
+ const sandbox = new Sandbox(controlsContainer, viewer, multiSelectList)
window.addEventListener('load', () => {
viewer.resize()
})
- viewer.on(
- ViewerEvent.LoadProgress,
- (a: { progress: number; id: string; url: string }) => {
- if (a.progress >= 1) {
- viewer.resize()
- }
- }
- )
-
viewer.on(ViewerEvent.LoadComplete, () => {
Object.assign(sandbox.sceneParams.worldSize, viewer.World.worldSize)
Object.assign(sandbox.sceneParams.worldOrigin, viewer.World.worldOrigin)
diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts
index 78f7d76a98..7289d139ef 100644
--- a/packages/viewer-sandbox/src/main.ts
+++ b/packages/viewer-sandbox/src/main.ts
@@ -4,7 +4,6 @@ import {
DefaultViewerParams,
SelectionEvent,
ViewerEvent,
- DebugViewer,
Viewer
} from '@speckle/viewer'
@@ -40,7 +39,7 @@ const createViewer = async (containerName: string, stream: string) => {
params.verbose = true
const multiSelectList: SelectionEvent[] = []
- const viewer: Viewer = new DebugViewer(container, params)
+ const viewer: Viewer = new Viewer(container, params)
await viewer.init()
const cameraController = viewer.createExtension(CameraController)
@@ -64,27 +63,15 @@ const createViewer = async (containerName: string, stream: string) => {
// rotateCamera // use it
// boxSelect // use it
- const sandbox = new Sandbox(controlsContainer, viewer as DebugViewer, multiSelectList)
+ const sandbox = new Sandbox(controlsContainer, viewer, multiSelectList)
window.addEventListener('load', () => {
viewer.resize()
})
- viewer.on(
- ViewerEvent.ObjectClicked,
- (event: { hits: { node: { model: { id: string } } }[] }) => {
- if (event) console.log(event.hits[0].node.model.id)
- }
- )
-
- viewer.on(
- ViewerEvent.LoadProgress,
- (a: { progress: number; id: string; url: string }) => {
- if (a.progress >= 1) {
- viewer.resize()
- }
- }
- )
+ viewer.on(ViewerEvent.ObjectClicked, (event: SelectionEvent | null) => {
+ if (event) console.log(event.hits[0].node.model.id)
+ })
viewer.on(ViewerEvent.LoadComplete, async () => {
console.warn(viewer.getRenderer().renderingStats)
@@ -121,6 +108,7 @@ const getStream = () => {
// 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D'
// Revit sample house (good for bim-like stuff with many display meshes)
'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8'
+ // 'https://latest.speckle.dev/streams/c1faab5c62/commits/ab1a1ab2b6'
// 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8'
// 'https://latest.speckle.dev/streams/58b5648c4d/commits/60371ecb2d'
// 'Super' heavy revit shit
@@ -347,6 +335,7 @@ const getStream = () => {
// 'https://latest.speckle.dev/streams/ee5346d3e1/commits/576310a6d5'
// 'https://latest.speckle.dev/streams/ee5346d3e1/commits/489d42ca8c'
// 'https://latest.speckle.dev/streams/97750296c2/objects/11a7752e40b4ef0620affc55ce9fdf5a'
+ // 'https://speckle.xyz/streams/0ed2cdc8eb/commits/350c4e1a4d'
// 'https://latest.speckle.dev/streams/92b620fb17/objects/7118603b197c00944f53be650ce721ec'
diff --git a/packages/viewer/.eslintrc.cjs b/packages/viewer/.eslintrc.cjs
index dbeae6a4a4..8eff2ff389 100644
--- a/packages/viewer/.eslintrc.cjs
+++ b/packages/viewer/.eslintrc.cjs
@@ -18,7 +18,8 @@ const config = {
}
},
rules: {
- 'no-console': ['warn', { allow: ['warn', 'error'] }]
+ 'no-console': ['warn', { allow: ['warn', 'error'] }],
+ '@typescript-eslint/no-non-null-assertion': 'error'
},
ignorePatterns: ['dist2', 'example/speckleviewer.web.js'],
overrides: [
diff --git a/packages/viewer/package.json b/packages/viewer/package.json
index b037623822..33aa79a40b 100644
--- a/packages/viewer/package.json
+++ b/packages/viewer/package.json
@@ -56,18 +56,15 @@
"@speckle/shared": "workspace:^",
"@types/flat": "^5.0.2",
"camera-controls": "^1.33.1",
- "flat": "^5.0.2",
"hold-event": "^0.1.0",
"js-logger": "1.6.1",
"lodash-es": "^4.17.21",
- "rainbowvis.js": "^1.0.1",
"string-to-color": "^2.2.2",
"three": "^0.140.0",
"three-mesh-bvh": "0.5.17",
"tree-model": "1.0.7",
"troika-three-text": "0.47.2",
- "type-fest": "^4.15.0",
- "underscore": "1.13.6"
+ "type-fest": "^4.15.0"
},
"devDependencies": {
"@babel/core": "^7.18.2",
@@ -76,6 +73,7 @@
"@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-image": "^3.0.2",
"@types/babel__core": "^7.20.1",
+ "@types/lodash-es": "4.17.12",
"@types/three": "^0.136.0",
"@typescript-eslint/eslint-plugin": "^5.39.0",
"@typescript-eslint/parser": "^5.39.0",
diff --git a/packages/viewer/src/IViewer.ts b/packages/viewer/src/IViewer.ts
index 52d7a0840d..e68768a9c8 100644
--- a/packages/viewer/src/IViewer.ts
+++ b/packages/viewer/src/IViewer.ts
@@ -1,16 +1,31 @@
import { Vector3 } from 'three'
+import { type PropertyInfo } from './modules/filtering/PropertyManager'
+import type { Query, QueryArgsResultMap } from './modules/queries/Query'
+import { type TreeNode, WorldTree } from './modules/tree/WorldTree'
+import { type Utils } from './modules/Utils'
import defaultHdri from './assets/hdri/Mild-dwab.png'
-import { PropertyInfo } from './modules/filtering/PropertyManager'
-import { Query, QueryArgsResultMap, QueryResult } from './modules/queries/Query'
-import { DataTree } from './modules/tree/DataTree'
-import { TreeNode, WorldTree } from './modules/tree/WorldTree'
-import { Utils } from './modules/Utils'
import { World } from './modules/World'
import SpeckleRenderer from './modules/SpeckleRenderer'
import { Extension } from './modules/extensions/Extension'
-import Input from './modules/input/Input'
import { Loader } from './modules/loaders/Loader'
import { type Constructor } from 'type-fest'
+import type { Vector3Like } from './modules/batching/BatchObject'
+import type { FilteringState } from './modules/extensions/FilteringExtension'
+
+export type SpeckleReference = {
+ referencedId: string
+}
+
+export type SpeckleObject = {
+ [k: string]: unknown
+ speckle_type: string
+ id: string
+ elements?: SpeckleReference[]
+ children?: SpeckleObject[] | SpeckleReference[]
+ name?: string
+ referencedId?: string
+ units?: string
+}
export interface ViewerParams {
showStats: boolean
@@ -54,21 +69,29 @@ export const DefaultViewerParams: ViewerParams = {
export enum ViewerEvent {
ObjectClicked = 'object-clicked',
ObjectDoubleClicked = 'object-doubleclicked',
- DownloadComplete = 'download-complete',
LoadComplete = 'load-complete',
- LoadProgress = 'load-progress',
UnloadComplete = 'unload-complete',
- LoadCancelled = 'load-cancelled',
UnloadAllComplete = 'unload-all-complete',
Busy = 'busy',
FilteringStateSet = 'filtering-state-set',
LightConfigUpdated = 'light-config-updated'
}
+export interface ViewerEventPayload {
+ [ViewerEvent.ObjectClicked]: SelectionEvent | null
+ [ViewerEvent.ObjectDoubleClicked]: SelectionEvent | null
+ [ViewerEvent.LoadComplete]: string
+ [ViewerEvent.UnloadComplete]: string
+ [ViewerEvent.UnloadAllComplete]: void
+ [ViewerEvent.Busy]: boolean
+ [ViewerEvent.FilteringStateSet]: FilteringState
+ [ViewerEvent.LightConfigUpdated]: LightConfiguration
+}
+
export type SpeckleView = {
name: string
id: string
- view: Record
+ view: { origin: Vector3Like; target: Vector3Like }
}
export type SelectionEvent = {
@@ -140,14 +163,16 @@ export enum StencilOutlineType {
}
export interface IViewer {
- get input(): Input
get Utils(): Utils
get World(): World
init(): Promise
resize(): void
- on(eventType: ViewerEvent, handler: (arg) => void)
- requestRender(flags?: number): void
+ on(
+ eventType: T,
+ handler: (arg: ViewerEventPayload[T]) => void
+ ): void
+ requestRender(flags?: UpdateFlags): void
setLightConfiguration(config: LightConfiguration): void
@@ -166,16 +191,14 @@ export interface IViewer {
): Promise
/** Data ops */
- getDataTree(): DataTree
getWorldTree(): WorldTree
- query(query: T): QueryArgsResultMap[T['operation']]
- queryAsync(query: Query): Promise
+ query(query: T): QueryArgsResultMap[T['operation']] | null
getRenderer(): SpeckleRenderer
getContainer(): HTMLElement
createExtension(type: Constructor): T
getExtension(type: Constructor): T
-
+ hasExtension(type: Constructor): boolean
dispose(): void
}
diff --git a/packages/viewer/src/helpers/flatten.ts b/packages/viewer/src/helpers/flatten.ts
index 819d1e674f..bf1e743b17 100644
--- a/packages/viewer/src/helpers/flatten.ts
+++ b/packages/viewer/src/helpers/flatten.ts
@@ -4,7 +4,7 @@
* @param obj object to flatten
* @returns an object with all its props flattened into `prop.subprop.subsubprop`.
*/
-const flattenObject = function (obj) {
+const flattenObject = function (obj: { [x: string]: unknown; id: unknown }) {
const flatten = {} as Record
for (const k in obj) {
if (
@@ -19,7 +19,7 @@ const flattenObject = function (obj) {
const v = obj[k]
if (v === null || v === undefined || Array.isArray(v)) continue
if (v.constructor === Object) {
- const flattenProp = flattenObject(v)
+ const flattenProp = flattenObject(v as { [x: string]: unknown; id: unknown })
for (const pk in flattenProp) {
flatten[k + '.' + pk] = flattenProp[pk]
}
diff --git a/packages/viewer/src/index.ts b/packages/viewer/src/index.ts
index ca30e7228e..233f87cc72 100644
--- a/packages/viewer/src/index.ts
+++ b/packages/viewer/src/index.ts
@@ -3,75 +3,78 @@ import {
AssetType,
DefaultLightConfiguration,
DefaultViewerParams,
- IViewer,
+ type IViewer,
ObjectLayers,
- SelectionEvent,
- SpeckleView,
+ type SelectionEvent,
+ type SpeckleObject,
+ type SpeckleReference,
+ type SpeckleView,
UpdateFlags,
ViewerEvent,
- ViewerParams
+ type ViewerParams,
+ LightConfiguration,
+ ViewerEventPayload
} from './IViewer'
-import {
+import type {
PropertyInfo,
StringPropertyInfo,
NumericPropertyInfo
} from './modules/filtering/PropertyManager'
-import { SunLightConfiguration } from './IViewer'
-import { DataTree, ObjectPredicate, SpeckleObject } from './modules/tree/DataTree'
+import { type SunLightConfiguration } from './IViewer'
import { World } from './modules/World'
-import { DebugViewer } from './modules/DebugViewer'
-import { NodeData, TreeNode, WorldTree } from './modules/tree/WorldTree'
-import {
+import { type NodeData, type TreeNode, WorldTree } from './modules/tree/WorldTree'
+import type {
PointQuery,
QueryResult,
IntersectionQuery,
PointQueryResult,
IntersectionQueryResult
} from './modules/queries/Query'
-import { Utils } from './modules/Utils'
+import { type Utils } from './modules/Utils'
import { BatchObject } from './modules/batching/BatchObject'
import { Box3, Vector3 } from 'three'
import {
- MeasurementOptions,
+ type MeasurementOptions,
MeasurementType,
MeasurementsExtension
} from './modules/extensions/measurements/MeasurementsExtension'
import { Units } from './modules/converter/Units'
import { SelectionExtension } from './modules/extensions/SelectionExtension'
import { CameraController } from './modules/extensions/CameraController'
-import { InlineView } from './modules/extensions/CameraController'
-import { CanonicalView } from './modules/extensions/CameraController'
-import { CameraEvent } from './modules/objects/SpeckleCamera'
-import { SectionTool } from './modules/extensions/SectionTool'
+import { type InlineView } from './modules/extensions/CameraController'
+import { type CanonicalView } from './modules/extensions/CameraController'
+import { CameraEvent, CameraEventPayload } from './modules/objects/SpeckleCamera'
+import { SectionTool, SectionToolEventPayload } from './modules/extensions/SectionTool'
import { SectionOutlines } from './modules/extensions/SectionOutlines'
import {
FilteringExtension,
- FilteringState
+ type FilteringState
} from './modules/extensions/FilteringExtension'
import { Extension } from './modules/extensions/Extension'
import { ExplodeExtension } from './modules/extensions/ExplodeExtension'
import {
DiffExtension,
- DiffResult,
+ type DiffResult,
VisualDiffMode
} from './modules/extensions/DiffExtension'
-import { Loader } from './modules/loaders/Loader'
+import { Loader, LoaderEvent } from './modules/loaders/Loader'
import { SpeckleLoader } from './modules/loaders/Speckle/SpeckleLoader'
import { ObjLoader } from './modules/loaders/OBJ/ObjLoader'
import { LegacyViewer } from './modules/LegacyViewer'
import { SpeckleType } from './modules/loaders/GeometryConverter'
-import Input, { InputEvent } from './modules/input/Input'
+import Input, { InputEvent, InputEventPayload } from './modules/input/Input'
import { GeometryType } from './modules/batching/Batch'
import { MeshBatch } from './modules/batching/MeshBatch'
import SpeckleStandardMaterial from './modules/materials/SpeckleStandardMaterial'
import SpeckleTextMaterial from './modules/materials/SpeckleTextMaterial'
import { SpeckleText } from './modules/objects/SpeckleText'
import { NodeRenderView } from './modules/tree/NodeRenderView'
+import { type ExtendedIntersection } from './modules/objects/SpeckleRaycaster'
+import { SpeckleGeometryConverter } from './modules/loaders/Speckle/SpeckleGeometryConverter'
import { Assets } from './modules/Assets'
export {
Viewer,
- DebugViewer,
LegacyViewer,
DefaultViewerParams,
ViewerEvent,
@@ -97,6 +100,7 @@ export {
Loader,
SpeckleLoader,
ObjLoader,
+ LoaderEvent,
UpdateFlags,
SpeckleType,
Input,
@@ -108,6 +112,7 @@ export {
SpeckleTextMaterial,
SpeckleText,
NodeRenderView,
+ SpeckleGeometryConverter,
Assets,
AssetType
}
@@ -119,10 +124,10 @@ export type {
PropertyInfo,
StringPropertyInfo,
NumericPropertyInfo,
+ LightConfiguration,
SunLightConfiguration,
- DataTree,
- ObjectPredicate,
SpeckleObject,
+ SpeckleReference,
SpeckleView,
CanonicalView,
InlineView,
@@ -136,7 +141,12 @@ export type {
Utils,
DiffResult,
MeasurementOptions,
- FilteringState
+ FilteringState,
+ ExtendedIntersection,
+ ViewerEventPayload,
+ InputEventPayload,
+ SectionToolEventPayload,
+ CameraEventPayload
}
export * as UrlHelper from './modules/UrlHelper'
diff --git a/packages/viewer/src/modules/Assets.ts b/packages/viewer/src/modules/Assets.ts
index ac4f483c3a..ff7b635746 100644
--- a/packages/viewer/src/modules/Assets.ts
+++ b/packages/viewer/src/modules/Assets.ts
@@ -4,20 +4,24 @@ import {
TextureLoader,
Color,
DataTexture,
+ DataTextureLoader,
Matrix4,
Euler
} from 'three'
import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js'
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
import { FontLoader, Font } from 'three/examples/jsm/loaders/FontLoader.js'
-import { Asset, AssetType } from '../IViewer'
+import { type Asset, AssetType } from '../IViewer'
import Logger from 'js-logger'
import { RotatablePMREMGenerator } from './objects/RotatablePMREMGenerator'
export class Assets {
private static _cache: { [name: string]: Texture | Font } = {}
- private static getLoader(src: string, assetType: AssetType): TextureLoader {
+ private static getLoader(
+ src: string,
+ assetType: AssetType
+ ): TextureLoader | DataTextureLoader | null {
if (assetType === undefined) assetType = src.split('.').pop() as AssetType
if (!Object.values(AssetType).includes(assetType)) {
Logger.warn(`Asset ${src} could not be loaded. Unknown type`)
@@ -30,6 +34,8 @@ export class Assets {
return new RGBELoader()
case AssetType.TEXTURE_8BPP:
return new TextureLoader()
+ default:
+ return null
}
}
@@ -115,7 +121,7 @@ export class Assets {
}
public static getFont(asset: Asset | string): Promise {
- let srcUrl: string = null
+ let srcUrl: string | null = null
if ((asset).src) {
srcUrl = (asset as Asset).src
} else {
@@ -128,7 +134,7 @@ export class Assets {
return new Promise((resolve, reject) => {
new FontLoader().load(
- srcUrl,
+ srcUrl as string,
(font: Font) => {
resolve(font)
},
@@ -148,6 +154,14 @@ export class Assets {
canvas.height = texture.image.height
const context = canvas.getContext('2d')
+ /** As you can see here https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/getContext#return_value
+ * The only valid cases where `getContext` returns null are:
+ * - "contextType doesn't match a possible drawing context" Definetely not the case as we're providing '2d'!
+ * - "differs from the first contextType requested". It can't since **we're only requesting a context once**!
+ * - If it returns null outside of these two casese, you have bigger problems than us throwing an exception here
+ */
+ if (!context) throw new Error('Fatal! 2d context could not be retrieved.')
+
context.drawImage(texture.image, 0, 0)
const data = context.getImageData(0, 0, canvas.width, canvas.height)
diff --git a/packages/viewer/src/modules/DebugViewer.ts b/packages/viewer/src/modules/DebugViewer.ts
deleted file mode 100644
index 2d63377f9e..0000000000
--- a/packages/viewer/src/modules/DebugViewer.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { Viewer } from './Viewer'
-
-export class DebugViewer extends Viewer {
- requestRenderShadowmap() {
- this.getRenderer().updateDirectLights()
- this.requestRender()
- }
-}
diff --git a/packages/viewer/src/modules/Intersections.ts b/packages/viewer/src/modules/Intersections.ts
index 1d9d667536..43ddae12f9 100644
--- a/packages/viewer/src/modules/Intersections.ts
+++ b/packages/viewer/src/modules/Intersections.ts
@@ -1,7 +1,6 @@
import {
Box3,
Camera,
- Intersection,
Object3D,
Plane,
Ray,
@@ -12,11 +11,15 @@ import {
} from 'three'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2.js'
-import { SpeckleRaycaster } from './objects/SpeckleRaycaster'
+import {
+ ExtendedIntersection,
+ ExtendedMeshIntersection,
+ SpeckleRaycaster
+} from './objects/SpeckleRaycaster'
import { ObjectLayers } from '../IViewer'
export class Intersections {
- private raycaster: SpeckleRaycaster
+ protected raycaster: SpeckleRaycaster
private boxBuffer: Box3 = new Box3()
private vec0Buffer: Vector4 = new Vector4()
private vec1Buffer: Vector4 = new Vector4()
@@ -26,12 +29,11 @@ export class Intersections {
this.raycaster = new SpeckleRaycaster()
this.raycaster.params.Line = { threshold: 0.01 }
this.raycaster.params.Points = { threshold: 0.01 }
- ;(this.raycaster.params as { Line2? }).Line2 = {}
- ;(this.raycaster.params as { Line2? }).Line2.threshold = 1
+ this.raycaster.params.Line2 = { threshold: 1 }
this.raycaster.onObjectIntersectionTest = this.onObjectIntersection.bind(this)
}
- private onObjectIntersection(obj: Object3D) {
+ protected onObjectIntersection(obj: Object3D) {
if (obj instanceof LineSegments2) {
const box = this.boxBuffer.setFromObject(obj)
const min = this.vec0Buffer.set(box.min.x, box.min.y, box.min.z, 1)
@@ -62,17 +64,11 @@ export class Intersections {
* original line width and how zoomed in the camer is on the line(batch)
*/
if (!worldSpace) {
- if (ssDistance < 1) {
- ;(this.raycaster.params as { Line2? }).Line2.threshold = lineWidth * 8
- } else {
- ;(this.raycaster.params as { Line2? }).Line2.threshold = lineWidth * 5
- }
+ this.raycaster.params.Line2.threshold =
+ ssDistance < 1 ? lineWidth * 8 : lineWidth * 5
} else {
- if (ssDistance < 1) {
- ;(this.raycaster.params as { Line2? }).Line2.threshold = lineWidth * 2
- } else {
- ;(this.raycaster.params as { Line2? }).Line2.threshold = lineWidth
- }
+ this.raycaster.params.Line2.threshold =
+ ssDistance < 1 ? lineWidth * 2 : lineWidth
}
}
}
@@ -81,54 +77,111 @@ export class Intersections {
scene: Scene,
camera: Camera,
point: Vector2,
+ castLayers: ObjectLayers.STREAM_CONTENT_MESH,
+ nearest?: boolean,
+ bounds?: Box3,
+ firstOnly?: boolean
+ ): Array | null
+ public intersect(
+ scene: Scene,
+ camera: Camera,
+ point: Vector2,
+ castLayers?: Array,
+ nearest?: boolean,
+ bounds?: Box3,
+ firstOnly?: boolean
+ ): Array | null
+
+ public intersect(
+ scene: Scene,
+ camera: Camera,
+ point: Vector2,
+ castLayers: Array | ObjectLayers | undefined = undefined,
nearest = true,
- bounds: Box3 = null,
- castLayers: Array = undefined,
+ bounds?: Box3,
firstOnly = false
- ): Array {
+ ): Array | Array | null {
this.raycaster.setFromCamera(point, camera)
this.raycaster.firstHitOnly = firstOnly
- return this.intersectInternal(scene, nearest, bounds, castLayers)
+ const preserveMask = this.setRaycasterLayers(castLayers)
+ let result: Array | Array | null
+ if (castLayers === ObjectLayers.STREAM_CONTENT_MESH) {
+ result = this.intersectInternal(scene, nearest, bounds)
+ } else result = this.intersectInternal(scene, nearest, bounds)
+ this.raycaster.layers.mask = preserveMask
+ return result
}
public intersectRay(
scene: Scene,
camera: Camera,
ray: Ray,
+ castLayers: ObjectLayers.STREAM_CONTENT_MESH,
+ nearest?: boolean,
+ bounds?: Box3,
+ firstOnly?: boolean
+ ): Array | null
+ public intersectRay(
+ scene: Scene,
+ camera: Camera,
+ ray: Ray,
+ castLayers?: Array,
+ nearest?: boolean,
+ bounds?: Box3,
+ firstOnly?: boolean
+ ): Array | null
+
+ public intersectRay(
+ scene: Scene,
+ camera: Camera,
+ ray: Ray,
+ castLayers: Array | ObjectLayers | undefined = undefined,
nearest = true,
- bounds: Box3 = null,
- castLayers: Array = undefined,
+ bounds?: Box3,
firstOnly = false
- ): Array {
+ ): Array | Array | null {
this.raycaster.camera = camera
this.raycaster.set(ray.origin, ray.direction)
this.raycaster.firstHitOnly = firstOnly
- return this.intersectInternal(scene, nearest, bounds, castLayers)
+ const preserveMask = this.setRaycasterLayers(castLayers)
+ let result: Array | Array | null
+ if (castLayers === ObjectLayers.STREAM_CONTENT_MESH) {
+ result = this.intersectInternal(scene, nearest, bounds)
+ } else result = this.intersectInternal(scene, nearest, bounds)
+ this.raycaster.layers.mask = preserveMask
+ return result
}
- private intersectInternal(
- scene: Scene,
- nearest: boolean,
- bounds: Box3,
- castLayers: Array
- ) {
+ private setRaycasterLayers(
+ castLayers: Array | ObjectLayers | undefined
+ ): number {
const preserveMask = this.raycaster.layers.mask
-
if (castLayers !== undefined) {
this.raycaster.layers.disableAll()
- castLayers.forEach((layer) => {
- this.raycaster.layers.enable(layer)
- })
+ if (Array.isArray(castLayers))
+ castLayers.forEach((layer) => {
+ this.raycaster.layers.enable(layer)
+ })
+ else {
+ this.raycaster.layers.enable(castLayers)
+ }
}
+ return preserveMask
+ }
+
+ private intersectInternal(
+ scene: Scene,
+ nearest?: boolean,
+ bounds?: Box3
+ ): T[] | null {
+ let results: T[] | null = []
const target = scene.getObjectByName('ContentGroup')
- let results = []
if (target) {
// const start = performance.now()
results = this.raycaster.intersectObjects(target.children)
// Logger.warn('Interesct time -> ', performance.now() - start)
}
- this.raycaster.layers.mask = preserveMask
if (results.length === 0) return null
if (nearest)
diff --git a/packages/viewer/src/modules/LegacyViewer.ts b/packages/viewer/src/modules/LegacyViewer.ts
index ad1aa862b2..5aec7f7c60 100644
--- a/packages/viewer/src/modules/LegacyViewer.ts
+++ b/packages/viewer/src/modules/LegacyViewer.ts
@@ -1,39 +1,48 @@
-import { MathUtils } from 'three'
-import { FilteringExtension, FilteringState } from './extensions/FilteringExtension'
-import { PolarView } from './extensions/CameraController'
-import { InlineView } from './extensions/CameraController'
-import { CanonicalView } from './extensions/CameraController'
+import { Box3, MathUtils, Vector2 } from 'three'
+import {
+ FilteringExtension,
+ type FilteringState
+} from './extensions/FilteringExtension'
+import {
+ type InlineView,
+ type PolarView,
+ type CanonicalView,
+ CameraController
+} from './extensions/CameraController'
import { SpeckleType } from './loaders/GeometryConverter'
import { Queries } from './queries/Queries'
-import { Query, QueryArgsResultMap, QueryResult } from './queries/Query'
-import { DataTree, DataTreeBuilder } from './tree/DataTree'
+import type { Query, QueryArgsResultMap, QueryResult } from './queries/Query'
import {
SelectionExtension,
- SelectionExtensionOptions
+ type SelectionExtensionOptions
} from './extensions/SelectionExtension'
-import { StencilOutlineType } from '../IViewer'
import {
DefaultViewerParams,
- IViewer,
- SelectionEvent,
- SpeckleView,
- SunLightConfiguration,
- ViewerParams
+ type IViewer,
+ type SelectionEvent,
+ type SpeckleView,
+ type SunLightConfiguration,
+ type ViewerParams,
+ StencilOutlineType
} from '../IViewer'
-import { TreeNode, WorldTree } from './tree/WorldTree'
import { Viewer } from './Viewer'
-import { CameraController } from './extensions/CameraController'
import { SectionTool } from './extensions/SectionTool'
import { SectionOutlines } from './extensions/SectionOutlines'
+import { type TreeNode, WorldTree } from './tree/WorldTree'
import {
- MeasurementOptions,
+ type MeasurementOptions,
MeasurementsExtension
} from './extensions/measurements/MeasurementsExtension'
import { ExplodeExtension } from './extensions/ExplodeExtension'
-import { DiffExtension, DiffResult, VisualDiffMode } from './extensions/DiffExtension'
-import { PropertyInfo } from './filtering/PropertyManager'
+import {
+ DiffExtension,
+ type DiffResult,
+ VisualDiffMode
+} from './extensions/DiffExtension'
+import { type PropertyInfo } from './filtering/PropertyManager'
import { BatchObject } from './batching/BatchObject'
import { SpeckleLoader } from './loaders/Speckle/SpeckleLoader'
+import Logger from 'js-logger'
class LegacySelectionExtension extends SelectionExtension {
/** FE2 'manually' selects objects pon it's own, so we're disabling the extension's event handler
@@ -61,7 +70,7 @@ class HighlightExtension extends SelectionExtension {
pointSize: 4
}
}
- this.setOptions(highlightMaterialData)
+ this.options = highlightMaterialData
}
public unselectObjects(ids: Array) {
@@ -70,7 +79,8 @@ class HighlightExtension extends SelectionExtension {
const nodes = []
for (let k = 0; k < ids.length; k++) {
- nodes.push(...this.viewer.getWorldTree().findId(ids[k]))
+ const foundNodes = this.viewer.getWorldTree().findId(ids[k])
+ if (foundNodes) nodes.push(...foundNodes)
}
this.clearSelection(
nodes.filter((node: TreeNode) => {
@@ -88,21 +98,20 @@ class HighlightExtension extends SelectionExtension {
selection
}
- protected onPointerMove(e) {
+ protected onPointerMove(e: Vector2) {
e
}
}
export class LegacyViewer extends Viewer {
- private cameraController: CameraController = null
- private selection: SelectionExtension = null
- private sections: SectionTool = null
- private sectionOutlines: SectionOutlines = null
- private measurements: MeasurementsExtension = null
- private filtering: FilteringExtension = null
- private explodeExtension: ExplodeExtension = null
- private diffExtension: DiffExtension = null
- private highlightExtension: HighlightExtension = null
+ private cameraController: CameraController
+ private selection: SelectionExtension
+ private sections: SectionTool
+ private measurements: MeasurementsExtension
+ private filtering: FilteringExtension
+ private explodeExtension: ExplodeExtension
+ private diffExtension: DiffExtension
+ private highlightExtension: HighlightExtension
public constructor(
container: HTMLElement,
@@ -112,7 +121,7 @@ export class LegacyViewer extends Viewer {
this.cameraController = this.createExtension(CameraController)
this.selection = this.createExtension(LegacySelectionExtension)
this.sections = this.createExtension(SectionTool)
- this.sectionOutlines = this.createExtension(SectionOutlines)
+ this.createExtension(SectionOutlines)
this.measurements = this.createExtension(MeasurementsExtension)
this.filtering = this.createExtension(FilteringExtension)
this.explodeExtension = this.createExtension(ExplodeExtension)
@@ -143,7 +152,7 @@ export class LegacyViewer extends Viewer {
if (!box) {
box = this.speckleRenderer.sceneBox
}
- this.sections.setBox(box, offset)
+ this.sections.setBox(box as Box3, offset)
}
public getSectionBoxFromObjects(objectIds: string[]) {
@@ -155,7 +164,7 @@ export class LegacyViewer extends Viewer {
}
public getCurrentSectionBox() {
- return this.sections.getCurrentBox()
+ return this.sections.getBox()
}
public toggleSectionBox() {
@@ -178,7 +187,7 @@ export class LegacyViewer extends Viewer {
if (!this.filtering.filteringState.selectedObjects)
this.filtering.filteringState.selectedObjects = []
this.filtering.filteringState.selectedObjects.push(
- ...this.selection.getSelectedObjects().map((obj) => obj.id)
+ ...this.selection.getSelectedObjects().map((obj) => obj.id as string)
)
return Promise.resolve(this.filtering.filteringState)
}
@@ -192,7 +201,7 @@ export class LegacyViewer extends Viewer {
public hideObjects(
objectIds: string[],
- stateKey: string = null,
+ stateKey: string | undefined = undefined,
includeDescendants = false,
ghost = false
): Promise {
@@ -211,7 +220,7 @@ export class LegacyViewer extends Viewer {
public showObjects(
objectIds: string[],
- stateKey: string = null,
+ stateKey: string | undefined = undefined,
includeDescendants = false
): Promise {
return new Promise((resolve) => {
@@ -224,7 +233,7 @@ export class LegacyViewer extends Viewer {
public isolateObjects(
objectIds: string[],
- stateKey: string = null,
+ stateKey: string | undefined = undefined,
includeDescendants = false,
ghost = true
): Promise {
@@ -243,7 +252,7 @@ export class LegacyViewer extends Viewer {
public unIsolateObjects(
objectIds: string[],
- stateKey: string = null,
+ stateKey: string | undefined = undefined,
includeDescendants = false
): Promise {
return new Promise((resolve) => {
@@ -294,7 +303,7 @@ export class LegacyViewer extends Viewer {
})
}
- public resetFilters(): Promise {
+ public resetFilters(): Promise {
return new Promise((resolve) => {
const filteringState = this.preserveSelectionFilter(() => {
return this.filtering.resetFilters()
@@ -303,20 +312,26 @@ export class LegacyViewer extends Viewer {
})
}
- private preserveSelectionFilter(filterFn: () => FilteringState): FilteringState {
- const selectedObjects = this.selection.getSelectedObjects().map((obj) => obj.id)
+ private preserveSelectionFilter(
+ filterFn: () => FilteringState | null
+ ): FilteringState {
+ const selectedObjects = this.selection
+ .getSelectedObjects()
+ .map((obj) => obj.id) as string[]
if (selectedObjects.length) this.selection.clearSelection()
const filteringState = filterFn()
- if (!filteringState.selectedObjects)
- filteringState.selectedObjects = selectedObjects
+ if (filteringState) {
+ if (!filteringState.selectedObjects)
+ filteringState.selectedObjects = selectedObjects
- this.selection.selectObjects(filteringState.selectedObjects)
- return filteringState
+ this.selection.selectObjects(filteringState.selectedObjects)
+ }
+ return filteringState || this.filtering.filteringState
}
/** TREE */
- public getDataTree(): DataTree {
- return DataTreeBuilder.build(this.tree)
+ public getDataTree(): void {
+ Logger.error('DataTree is obsolete, please use WorldTree instead')
}
public getWorldTree(): WorldTree {
@@ -333,9 +348,10 @@ export class LegacyViewer extends Viewer {
Queries.DefaultIntersectionQuerySolver.setContext(this.speckleRenderer)
return Queries.DefaultIntersectionQuerySolver.solve(query)
}
+ return null
}
- public queryAsync(query: Query): Promise {
+ public queryAsync(query: Query): Promise | null {
//TO DO
query
return null
@@ -391,11 +407,11 @@ export class LegacyViewer extends Viewer {
return new Promise((resolve) => {
const sectionBoxVisible = this.sections.enabled
if (sectionBoxVisible) {
- this.sections.displayOff()
+ this.sections.visible = false
}
const screenshot = this.speckleRenderer.renderer.domElement.toDataURL('image/png')
if (sectionBoxVisible) {
- this.sections.displayOn()
+ this.sections.visible = true
}
resolve(screenshot)
})
@@ -407,11 +423,15 @@ export class LegacyViewer extends Viewer {
public getObjects(id: string): BatchObject[] {
const nodes = this.tree.findId(id)
- const objects = []
- nodes.forEach((node: TreeNode) => {
- if (node.model.renderView)
- objects.push(this.speckleRenderer.getObject(node.model.renderView))
- })
+ const objects: BatchObject[] = []
+ if (nodes) {
+ nodes.forEach((node: TreeNode) => {
+ if (node.model.renderView)
+ objects.push(
+ this.speckleRenderer.getObject(node.model.renderView) as BatchObject
+ )
+ })
+ }
return objects
}
@@ -421,7 +441,7 @@ export class LegacyViewer extends Viewer {
public async loadObjectAsync(
url: string,
- token: string = null,
+ token: string | undefined = undefined,
enableCaching = true,
zoomToObject = true
) {
@@ -442,11 +462,11 @@ export class LegacyViewer extends Viewer {
return this.diffExtension.undiff()
}
- public setDiffTime(diffResult: DiffResult, time: number) {
+ public setDiffTime(_diffResult: DiffResult, time: number) {
this.diffExtension.updateVisualDiff(time)
}
- public setVisualDiffMode(diffResult: DiffResult, mode: VisualDiffMode) {
+ public setVisualDiffMode(_diffResult: DiffResult, mode: VisualDiffMode) {
this.diffExtension.updateVisualDiff(undefined, mode)
}
diff --git a/packages/viewer/src/modules/Shadowcatcher.ts b/packages/viewer/src/modules/Shadowcatcher.ts
index 15dff06013..dbd1611077 100644
--- a/packages/viewer/src/modules/Shadowcatcher.ts
+++ b/packages/viewer/src/modules/Shadowcatcher.ts
@@ -12,23 +12,26 @@ import {
Scene,
Vector2,
Vector3,
- WebGLRenderer,
ZeroFactor
} from 'three'
import { Geometry } from './converter/Geometry'
import SpeckleBasicMaterial from './materials/SpeckleBasicMaterial'
import { ShadowcatcherPass } from './pipeline/ShadowcatcherPass'
import { ObjectLayers } from '../IViewer'
-import { DefaultShadowcatcherConfig, ShadowcatcherConfig } from './ShadowcatcherConfig'
+import {
+ DefaultShadowcatcherConfig,
+ type ShadowcatcherConfig
+} from './ShadowcatcherConfig'
+import type { SpeckleWebGLRenderer } from './objects/SpeckleWebGLRenderer'
export class Shadowcatcher {
public static readonly MESH_NAME = 'Shadowcatcher'
public static readonly PLANE_SUBD = 2
public static readonly MAX_TEXTURE_SIZE_SCALE = 0.5
- private planeMesh: Mesh = null
+ private planeMesh: Mesh
private planeSize: Vector2 = new Vector2()
- private displayMaterial: SpeckleBasicMaterial = null
- public shadowcatcherPass: ShadowcatcherPass = null
+ private displayMaterial: SpeckleBasicMaterial
+ public shadowcatcherPass: ShadowcatcherPass
private _config: ShadowcatcherConfig = DefaultShadowcatcherConfig
public get shadowcatcherMesh() {
@@ -73,8 +76,8 @@ export class Shadowcatcher {
this.shadowcatcherPass.update(scene)
}
- public render(renderer: WebGLRenderer) {
- this.shadowcatcherPass.render(renderer, null, null)
+ public render(renderer: SpeckleWebGLRenderer) {
+ this.shadowcatcherPass.render(renderer)
}
public bake(worldBox: Box3, maxTexSize: number, force?: boolean) {
diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts
index f9a95a52ad..9ac289a2aa 100644
--- a/packages/viewer/src/modules/SpeckleRenderer.ts
+++ b/packages/viewer/src/modules/SpeckleRenderer.ts
@@ -7,7 +7,7 @@ import {
DirectionalLight,
DirectionalLightHelper,
Group,
- Intersection,
+ type Intersection,
Material,
Mesh,
Object3D,
@@ -19,43 +19,53 @@ import {
sRGBEncoding,
Texture,
Vector3,
- VSMShadowMap
+ VSMShadowMap,
+ Vector2,
+ PerspectiveCamera,
+ OrthographicCamera
} from 'three'
-import { Batch, BatchUpdateRange, GeometryType } from './batching/Batch'
+import { type Batch, type BatchUpdateRange, GeometryType } from './batching/Batch'
import Batcher from './batching/Batcher'
import { Geometry } from './converter/Geometry'
-import Input, { InputEvent, InputOptionsDefault } from './input/Input'
+import Input, { InputEvent } from './input/Input'
import { Intersections } from './Intersections'
import SpeckleDepthMaterial from './materials/SpeckleDepthMaterial'
import SpeckleStandardMaterial from './materials/SpeckleStandardMaterial'
import { NodeRenderView } from './tree/NodeRenderView'
import { Viewer } from './Viewer'
-import { TreeNode } from './tree/WorldTree'
+import { WorldTree, type TreeNode } from './tree/WorldTree'
import {
DefaultLightConfiguration,
ObjectLayers,
- SelectionEvent,
- SunLightConfiguration,
+ type SelectionEvent,
+ type SunLightConfiguration,
ViewerEvent
} from '../IViewer'
-import { DefaultPipelineOptions, Pipeline, PipelineOptions } from './pipeline/Pipeline'
+import {
+ DefaultPipelineOptions,
+ Pipeline,
+ type PipelineOptions
+} from './pipeline/Pipeline'
import { Shadowcatcher } from './Shadowcatcher'
import SpeckleMesh from './objects/SpeckleMesh'
-import { ExtendedIntersection } from './objects/SpeckleRaycaster'
+import { type ExtendedIntersection } from './objects/SpeckleRaycaster'
import { BatchObject } from './batching/BatchObject'
-import { CameraEvent, SpeckleCamera } from './objects/SpeckleCamera'
+import { CameraEvent, type SpeckleCamera } from './objects/SpeckleCamera'
import Materials, {
- RenderMaterial,
- DisplayStyle,
- FilterMaterial
+ type RenderMaterial,
+ type DisplayStyle,
+ type FilterMaterial,
+ type FilterMaterialOptions
} from './materials/Materials'
-import { MaterialOptions } from './materials/MaterialOptions'
+import { type 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 { MeshBatch } from './batching/MeshBatch'
+import type { Pass } from 'three/examples/jsm/postprocessing/Pass.js'
+import { RenderTree } from './tree/RenderTree'
export class RenderingStats {
private renderTimeAcc = 0
@@ -64,13 +74,13 @@ export class RenderingStats {
private renderTimeStart = 0
public renderTime = 0
- public objects: number
- public batchCount: number
- public drawCalls: number
- public trisCount: number
- public vertCount: number
+ public objects: number = 0
+ public batchCount: number = 0
+ public drawCalls: number = 0
+ public trisCount: number = 0
+ public vertCount: number = 0
- public batchDetails: Array<{
+ public batchDetails!: Array<{
drawCalls: number
minDrawCalls: number
tris: number
@@ -93,32 +103,36 @@ export class RenderingStats {
}
export default class SpeckleRenderer {
- private readonly SHOW_HELPERS = false
- private readonly IGNORE_ZERO_OPACITY_OBJECTS = true
+ protected readonly SHOW_HELPERS = false
+ protected readonly IGNORE_ZERO_OPACITY_OBJECTS = true
public SHOW_BVH = false
- private container: HTMLElement
- private _renderer: SpeckleWebGLRenderer
- private _renderinStats: RenderingStats
- public _scene: Scene
- private _needsRender: boolean
- private rootGroup: Group
+
+ protected _renderer: SpeckleWebGLRenderer
+ protected _renderinStats: RenderingStats
+ protected _scene: Scene
+ protected _needsRender: boolean
+ protected _intersections: Intersections
+ protected _shadowcatcher: Shadowcatcher
+ protected _speckleCamera: SpeckleCamera | null = null
+
+ protected container: HTMLElement
+ protected rootGroup: Group
public batcher: Batcher
- private _intersections: Intersections
- public input: Input
- private sun: DirectionalLight
- private sunConfiguration: SunLightConfiguration = DefaultLightConfiguration
- private sunTarget: Object3D
- public viewer: Viewer // TEMPORARY
- public pipeline: Pipeline
+ protected sun: DirectionalLight
+ protected sunConfiguration: SunLightConfiguration = DefaultLightConfiguration
+ protected sunTarget: Object3D
+ protected tree: WorldTree
- private _shadowcatcher: Shadowcatcher = null
- private cancel: { [subtreeId: string]: boolean } = {}
+ protected cancel: { [subtreeId: string]: boolean } = {}
- private _speckleCamera: SpeckleCamera = null
- private _clippingPlanes: Plane[] = []
- private _clippingVolume: Box3 = new Box3()
+ protected _clippingPlanes: Plane[] = []
+ protected _clippingVolume: Box3 = new Box3()
- private _renderOverride: () => void = null
+ protected _renderOverride: (() => void) | null = null
+
+ public viewer: Viewer // TEMPORARY
+ public pipeline: Pipeline
+ public input: Input
/********************************
* Renderer and rendering flags */
@@ -136,7 +150,7 @@ export default class SpeckleRenderer {
/**********************
* Bounds and volumes */
- public get sceneBox() {
+ public get sceneBox(): Box3 {
const bounds: Box3 = new Box3()
const batches = this.batcher.getBatches()
for (let k = 0; k < batches.length; k++) {
@@ -145,11 +159,11 @@ export default class SpeckleRenderer {
return bounds
}
- public get sceneSphere() {
+ public get sceneSphere(): Sphere {
return this.sceneBox.getBoundingSphere(new Sphere())
}
- public get sceneCenter() {
+ public get sceneCenter(): Vector3 {
return this.sceneBox.getCenter(new Vector3())
}
@@ -176,12 +190,8 @@ export default class SpeckleRenderer {
/****************
* Common Objects */
- public get allObjects() {
- return this._scene.getObjectByName('ContentGroup')
- }
-
- public subtree(subtreeId: string) {
- return this._scene.getObjectByName(subtreeId)
+ public get allObjects(): Object3D {
+ return this._scene.getObjectByName('ContentGroup') as Object3D
}
public get scene() {
@@ -191,7 +201,7 @@ export default class SpeckleRenderer {
/********
* Lights */
- public get sunLight() {
+ public get sunLight(): DirectionalLight {
return this.sun
}
@@ -213,7 +223,7 @@ export default class SpeckleRenderer {
/********
* Camera */
- public get speckleCamera(): SpeckleCamera {
+ public get speckleCamera(): SpeckleCamera | null {
return this._speckleCamera
}
@@ -235,7 +245,8 @@ export default class SpeckleRenderer {
})
}
- public get renderingCamera() {
+ public get renderingCamera(): PerspectiveCamera | OrthographicCamera | null {
+ if (!this._speckleCamera) return null
return this._speckleCamera.renderingCamera
}
@@ -249,13 +260,13 @@ export default class SpeckleRenderer {
return this.pipeline.pipelineOptions
}
- public get shadowcatcher() {
+ public get shadowcatcher(): Shadowcatcher | null {
return this._shadowcatcher
}
/**************
* Intersections */
- public get intersections() {
+ public get intersections(): Intersections {
return this._intersections
}
@@ -294,7 +305,8 @@ export default class SpeckleRenderer {
return this._renderinStats
}
- public constructor(viewer: Viewer /** TEMPORARY */) {
+ public constructor(tree: WorldTree, viewer: Viewer /** TEMPORARY */) {
+ this.tree = tree
this._renderinStats = new RenderingStats()
this._scene = new Scene()
this.rootGroup = new Group()
@@ -338,7 +350,7 @@ export default class SpeckleRenderer {
this.pipeline.configure()
this.pipeline.pipelineOptions = DefaultPipelineOptions
- this.input = new Input(this._renderer.domElement, InputOptionsDefault)
+ this.input = new Input(this._renderer.domElement)
this.input.on(InputEvent.Click, this.onClick.bind(this))
this.input.on(InputEvent.DoubleClick, this.onDoubleClick.bind(this))
@@ -368,7 +380,9 @@ export default class SpeckleRenderer {
ObjectLayers.STREAM_CONTENT_MESH
// ObjectLayers.STREAM_CONTENT_LINE
])
- let restoreVisibility, opaque
+ let restoreVisibility: Record
+ let opaque: Record
+
this._shadowcatcher.shadowcatcherPass.onBeforeRender = () => {
restoreVisibility = this.batcher.saveVisiblity()
opaque = this.batcher.getOpaque()
@@ -387,7 +401,7 @@ export default class SpeckleRenderer {
}
public update(deltaTime: number) {
- if (!this._speckleCamera) return
+ if (!this.renderingCamera) return
this.batcher.update(deltaTime)
this.renderingCamera.updateMatrixWorld(true)
@@ -395,11 +409,11 @@ export default class SpeckleRenderer {
this.updateRTEShadows()
this.updateTransforms()
- this.updateFrustum()
+ this.updateFrustum(this.renderingCamera)
this.pipeline.update(this)
- if (this.sunConfiguration.shadowcatcher) {
+ if (this.sunConfiguration.shadowcatcher && this._shadowcatcher) {
this._shadowcatcher.update(this._scene)
}
}
@@ -457,13 +471,12 @@ export default class SpeckleRenderer {
private updateRTEShadows() {
if (!this.updateRTEShadowBuffers()) return
- const meshBatches = this.batcher.getBatches(
+ const meshBatches: MeshBatch[] = this.batcher.getBatches(
undefined,
GeometryType.MESH
- ) as MeshBatch[]
+ )
for (let k = 0; k < meshBatches.length; k++) {
- const speckleMesh: SpeckleMesh | SpeckleInstancedMesh = meshBatches[k]
- .renderObject as SpeckleMesh | SpeckleInstancedMesh
+ const speckleMesh: SpeckleMesh | SpeckleInstancedMesh = meshBatches[k].mesh
/** Shadowmap depth material does not go thorugh the normal flow.
* It's onBeforeRender is not getting called That's why we're updating
@@ -489,10 +502,12 @@ export default class SpeckleRenderer {
}
private updateTransforms() {
- const meshBatches = this.batcher.getBatches(undefined, GeometryType.MESH)
+ const meshBatches: MeshBatch[] = this.batcher.getBatches(
+ undefined,
+ GeometryType.MESH
+ )
for (let k = 0; k < meshBatches.length; k++) {
- const meshBatch: SpeckleMesh | SpeckleInstancedMesh = meshBatches[k]
- .renderObject as SpeckleMesh | SpeckleInstancedMesh
+ const meshBatch: SpeckleMesh | SpeckleInstancedMesh = meshBatches[k].mesh
meshBatch.updateTransformsUniform()
meshBatch.traverse((obj: Object3D) => {
const depthMaterial: SpeckleDepthMaterial =
@@ -504,10 +519,10 @@ export default class SpeckleRenderer {
}
}
- private updateFrustum() {
+ private updateFrustum(camera: PerspectiveCamera | OrthographicCamera) {
const v = new Vector3()
const box = this.sceneBox
- const camPos = new Vector3().copy(this.renderingCamera.position)
+ const camPos = new Vector3().copy(camera.position)
let d = 0
v.set(box.min.x, box.min.y, box.min.z) // 000
d = Math.max(camPos.distanceTo(v), d)
@@ -525,8 +540,8 @@ export default class SpeckleRenderer {
d = Math.max(camPos.distanceTo(v), d)
v.set(box.max.x, box.max.y, box.max.z) // 111
d = Math.max(camPos.distanceTo(v), d)
- this.renderingCamera.far = d * 2
- this.renderingCamera.updateProjectionMatrix()
+ camera.far = d * 2
+ camera.updateProjectionMatrix()
}
public resetPipeline() {
@@ -549,7 +564,7 @@ export default class SpeckleRenderer {
// this._needsRender = true
this._renderinStats.frameEnd()
- if (this.sunConfiguration.shadowcatcher) {
+ if (this.sunConfiguration.shadowcatcher && this._shadowcatcher) {
this._shadowcatcher.render(this._renderer)
}
// console.log('Get transparent time -> ', InstancedMeshBatch.transparentTime)
@@ -562,16 +577,16 @@ export default class SpeckleRenderer {
this._needsRender = true
}
- public async *addRenderTree(subtreeId: string) {
- this.cancel[subtreeId] = false
+ public async *addRenderTree(renderTree: RenderTree) {
+ this.cancel[renderTree.id] = false
const subtreeGroup = new Group()
- subtreeGroup.name = subtreeId
+ subtreeGroup.name = renderTree.id
subtreeGroup.layers.set(ObjectLayers.STREAM_CONTENT)
this.rootGroup.add(subtreeGroup)
const generator = this.batcher.makeBatches(
- this.viewer.getWorldTree(),
- this.viewer.getWorldTree().getRenderTree(subtreeId),
+ this.tree,
+ renderTree,
SpeckleTypeAllRenderables
)
let currentBatchCount = 0
@@ -591,10 +606,10 @@ export default class SpeckleRenderer {
this.updateDirectLights()
}
- if (this.cancel[subtreeId]) {
+ if (this.cancel[renderTree.id]) {
generator.return()
- this.removeRenderTree(subtreeId)
- delete this.cancel[subtreeId]
+ this.removeRenderTree(renderTree.id)
+ delete this.cancel[renderTree.id]
break
}
currentBatchCount++
@@ -607,8 +622,8 @@ export default class SpeckleRenderer {
/** We'll just update the shadowcatcher after all batches are loaded */
this.updateShadowCatcher()
this.updateClippingPlanes()
- this._speckleCamera.setCameraPlanes(this.sceneBox)
- delete this.cancel[subtreeId]
+ if (this._speckleCamera) this._speckleCamera.setCameraPlanes(this.sceneBox)
+ delete this.cancel[renderTree.id]
}
private addBatch(batch: Batch, parent: Object3D) {
@@ -638,7 +653,7 @@ export default class SpeckleRenderer {
}
public removeRenderTree(subtreeId: string) {
- this.rootGroup.remove(this.rootGroup.getObjectByName(subtreeId))
+ this.rootGroup.remove(this.rootGroup.getObjectByName(subtreeId) as Object3D)
this.updateShadowCatcher()
const batches = this.batcher.getBatches(subtreeId)
@@ -657,16 +672,22 @@ export default class SpeckleRenderer {
}
}
- public setMaterial(rvs: NodeRenderView[], material: Material)
+ public setMaterial(rvs: NodeRenderView[], material: Material): void
public setMaterial(
rvs: NodeRenderView[],
material: RenderMaterial & DisplayStyle & MaterialOptions
- )
- public setMaterial(rvs: NodeRenderView[], material: FilterMaterial)
- public setMaterial(rvs: NodeRenderView[], material) {
+ ): void
+ public setMaterial(rvs: NodeRenderView[], material: FilterMaterial): void
+ public setMaterial(
+ rvs: NodeRenderView[],
+ material:
+ | Material
+ | (RenderMaterial & DisplayStyle & MaterialOptions)
+ | FilterMaterial
+ ): void {
if (!material) return
- const rvMap = {}
+ const rvMap: { [id: string]: NodeRenderView[] } = {}
for (let k = 0; k < rvs.length; k++) {
if (!rvs[k].batchId) {
continue
@@ -699,7 +720,7 @@ export default class SpeckleRenderer {
return { offset: value.batchStart, count: value.batchCount, material }
})
if (this.batcher.batches[k])
- this.batcher.batches[k].setDrawRanges(...this.flattenDrawRanges(drawRanges))
+ this.batcher.batches[k].setDrawRanges(this.flattenDrawRanges(drawRanges))
}
}
@@ -712,12 +733,17 @@ export default class SpeckleRenderer {
return {
offset: value.batchStart,
count: value.batchCount,
- material: this.batcher.materials.getFilterMaterial(value, material),
- materialOptions: this.batcher.materials.getFilterMaterialOptions(material)
+ material: this.batcher.materials.getFilterMaterial(
+ value,
+ material
+ ) as Material,
+ materialOptions: this.batcher.materials.getFilterMaterialOptions(
+ material
+ ) as FilterMaterialOptions
}
})
if (this.batcher.batches[k])
- this.batcher.batches[k].setDrawRanges(...this.flattenDrawRanges(drawRanges))
+ this.batcher.batches[k].setDrawRanges(this.flattenDrawRanges(drawRanges))
}
}
@@ -737,7 +763,7 @@ export default class SpeckleRenderer {
})
if (this.batcher.batches[k])
- this.batcher.batches[k].setDrawRanges(...this.flattenDrawRanges(drawRanges))
+ this.batcher.batches[k].setDrawRanges(this.flattenDrawRanges(drawRanges))
}
}
@@ -791,14 +817,14 @@ export default class SpeckleRenderer {
return flatRanges
}
- public getMaterial(rv: NodeRenderView): Material {
+ public getMaterial(rv: NodeRenderView): Material | null {
if (!rv || !rv.batchId) {
return null
}
return this.batcher.getBatch(rv).getMaterial(rv)
}
- public getBatchMaterial(rv: NodeRenderView): Material {
+ public getBatchMaterial(rv: NodeRenderView): Material | null {
if (!rv || !rv.batchId) {
return null
}
@@ -818,7 +844,7 @@ export default class SpeckleRenderer {
const planes = this._clippingPlanes
this.allObjects.traverse((object) => {
- const material = (object as unknown as { material }).material
+ const material = (object as unknown as { material: Material }).material
if (!material) return
if (!Array.isArray(material)) {
material.clippingPlanes = planes
@@ -829,13 +855,15 @@ export default class SpeckleRenderer {
}
})
this.pipeline.updateClippingPlanes(planes)
- this._shadowcatcher.updateClippingPlanes(planes)
+ this._shadowcatcher?.updateClippingPlanes(planes)
this.renderer.shadowMap.needsUpdate = true
this.resetPipeline()
}
public updateShadowCatcher() {
- this._shadowcatcher.shadowcatcherMesh.visible = this.sunConfiguration.shadowcatcher
+ if (this.sunConfiguration.shadowcatcher !== undefined)
+ this._shadowcatcher.shadowcatcherMesh.visible =
+ this.sunConfiguration.shadowcatcher
if (this.sunConfiguration.shadowcatcher) {
this._shadowcatcher.bake(
this.clippingVolume,
@@ -873,14 +901,17 @@ export default class SpeckleRenderer {
this.sun.target = this.sunTarget
}
- public updateDirectLights() {
+ private updateDirectLights() {
const phi = this.sunConfiguration.elevation
const theta = this.sunConfiguration.azimuth
- const radiusOffset = this.sunConfiguration.radius
- this.sun.castShadow = this.sunConfiguration.castShadow
- this.sun.intensity = this.sunConfiguration.intensity
+ const radiusOffset = this.sunConfiguration.radius || 0
+ if (this.sunConfiguration.castShadow !== undefined)
+ this.sun.castShadow = this.sunConfiguration.castShadow
+ if (this.sunConfiguration.intensity !== undefined)
+ this.sun.intensity = this.sunConfiguration.intensity
this.sun.color = new Color(this.sunConfiguration.color)
- this.sun.visible = this.sunConfiguration.enabled
+ if (this.sunConfiguration.enabled !== undefined)
+ this.sun.visible = this.sunConfiguration.enabled
this.sunTarget.position.copy(this.sceneCenter)
const spherical = new Spherical(this.sceneSphere.radius + radiusOffset, phi, theta)
@@ -946,7 +977,7 @@ export default class SpeckleRenderer {
this.viewer.emit(ViewerEvent.LightConfigUpdated, { ...config })
}
- public updateHelpers() {
+ private updateHelpers() {
if (this.SHOW_HELPERS) {
;(this._scene.getObjectByName('CamHelper') as CameraHelper).update()
// Thank you prettier, this looks so much better
@@ -961,7 +992,7 @@ export default class SpeckleRenderer {
public queryHits(
results: Array
- ): Array<{ node: TreeNode; point: Vector3 }> {
+ ): Array<{ node: TreeNode; point: Vector3 }> | null {
const rvs = []
const points = []
for (let k = 0; k < results.length; k++) {
@@ -981,7 +1012,10 @@ export default class SpeckleRenderer {
for (let k = 0; k < rvs.length; k++) {
const hitId = rvs[k].renderData.id
const subtreeId = rvs[k].renderData.subtreeId
- const hitNode = this.viewer.getWorldTree().findId(hitId, subtreeId)[0]
+ const hitNodes = this.tree.findId(hitId, subtreeId)
+ if (!hitNodes) continue
+
+ const hitNode = hitNodes[0]
let parentNode = hitNode
while (!parentNode.model.atomic && parentNode.parent) {
parentNode = parentNode.parent
@@ -993,15 +1027,15 @@ export default class SpeckleRenderer {
public queryHitIds(
results: Array
- ): Array<{ nodeId: string; point: Vector3 }> {
+ ): Array<{ nodeId: string; point: Vector3 }> | null {
const queryResult = []
for (let k = 0; k < results.length; k++) {
- let rv = results[k].batchObject?.renderView
+ let rv: NodeRenderView | null = results[k].batchObject
+ ?.renderView as NodeRenderView
if (!rv) {
- rv = this.batcher.getRenderView(
- results[k].object.uuid,
+ const index: number | undefined =
results[k].faceIndex !== undefined ? results[k].faceIndex : results[k].index
- )
+ if (index) rv = this.batcher.getRenderView(results[k].object.uuid, index)
}
if (rv) {
queryResult.push({ nodeId: rv.renderData.id, point: results[k].point })
@@ -1016,42 +1050,47 @@ export default class SpeckleRenderer {
return queryResult
}
+ // TO DO: Maybe need a better way
public renderViewFromIntersection(
intersection: ExtendedIntersection
- ): NodeRenderView {
+ ): NodeRenderView | null {
let rv = null
if (intersection.batchObject) {
rv = intersection.batchObject.renderView
const material = (intersection.object as SpeckleMesh).getBatchObjectMaterial(
intersection.batchObject
)
- if (material.opacity === 0 && this.IGNORE_ZERO_OPACITY_OBJECTS) return null
+ if (material && material.opacity === 0 && this.IGNORE_ZERO_OPACITY_OBJECTS)
+ return null
} else {
- rv = this.batcher.getRenderView(
- intersection.object.uuid,
+ const index =
intersection.faceIndex !== undefined
? intersection.faceIndex
: intersection.index
- )
- if (rv) {
- const material = this.batcher.getRenderViewMaterial(
- intersection.object.uuid,
- intersection.faceIndex !== undefined
- ? intersection.faceIndex
- : intersection.index
- )
- if (material.opacity === 0 && this.IGNORE_ZERO_OPACITY_OBJECTS) return null
+ if (index) {
+ rv = this.batcher.getRenderView(intersection.object.uuid, index)
+ if (rv) {
+ const material = this.batcher.getRenderViewMaterial(
+ intersection.object.uuid,
+ index
+ )
+ if (material && material.opacity === 0 && this.IGNORE_ZERO_OPACITY_OBJECTS)
+ return null
+ }
}
}
return rv
}
- private onClick(e) {
- const results: Array = this._intersections.intersect(
+ private onClick(e: Vector2 & { multiSelect: boolean; event: PointerEvent }) {
+ if (!this.renderingCamera) return
+
+ const results: Array | null = this._intersections.intersect(
this._scene,
this.renderingCamera,
e,
+ undefined,
true,
this.clippingVolume
)
@@ -1085,11 +1124,14 @@ export default class SpeckleRenderer {
this.viewer.emit(ViewerEvent.ObjectClicked, selectionInfo)
}
- private onDoubleClick(e) {
- const results: Array = this._intersections.intersect(
+ private onDoubleClick(e: Vector2 & { multiSelect: boolean; event: PointerEvent }) {
+ if (!this.renderingCamera) return
+
+ const results: Array | null = this._intersections.intersect(
this._scene,
this.renderingCamera,
e,
+ undefined,
true,
this.clippingVolume
)
@@ -1120,20 +1162,16 @@ export default class SpeckleRenderer {
this.viewer.emit(ViewerEvent.ObjectDoubleClicked, selectionInfo)
}
- public boxFromObjects(objectIds: string[]) {
+ public boxFromObjects(objectIds: string[]): Box3 {
let box = new Box3()
const rvs: NodeRenderView[] = []
if (objectIds.length > 0) {
for (let k = 0; k < objectIds.length; k++) {
- const nodes = this.viewer.getWorldTree().findId(objectIds[k])
- nodes.forEach((node: TreeNode) => {
- rvs.push(
- ...this.viewer
- .getWorldTree()
- .getRenderTree()
- .getRenderViewsForNode(node, node)
- )
- })
+ const nodes = this.tree.findId(objectIds[k])
+ if (nodes)
+ nodes.forEach((node: TreeNode) => {
+ rvs.push(...this.tree.getRenderTree().getRenderViewsForNode(node))
+ })
}
} else box = this.sceneBox
for (let k = 0; k < rvs.length; k++) {
@@ -1215,7 +1253,7 @@ export default class SpeckleRenderer {
return ids.reverse()
}
- public getBatchSize(batchId: string) {
+ public getBatchSize(batchId: string): number {
return this.batcher.batches[batchId].renderViews.length
}
@@ -1225,23 +1263,25 @@ export default class SpeckleRenderer {
}
public getObjects(): BatchObject[] {
- const batches = this.batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[]
+ const batches = this.batcher.getBatches(undefined, GeometryType.MESH)
const meshes = batches.map((batch: MeshBatch) => batch.mesh)
const objects = meshes.flatMap((mesh) => mesh.batchObjects)
return objects
}
- public getObject(rv: NodeRenderView): BatchObject {
+ public getObject(rv: NodeRenderView): BatchObject | null {
const batch = this.batcher.getBatch(rv) as MeshBatch
if (batch.geometryType !== GeometryType.MESH) {
// Logger.error('Render view is not of mesh type. No batch object found')
return null
}
- return batch.mesh.batchObjects.find((value) => value.renderView.guid === rv.guid)
+ return batch.mesh.batchObjects.find(
+ (value) => value.renderView.guid === rv.guid
+ ) as BatchObject
}
public enableLayers(layers: ObjectLayers[], value: boolean) {
- this.pipeline.composer.passes.forEach((pass: BaseSpecklePass) => {
+ this.pipeline.composer.passes.forEach((pass: Pass) => {
if (!(pass instanceof BaseSpecklePass)) return
layers.forEach((layer: ObjectLayers) => {
pass.enableLayer(layer, value)
diff --git a/packages/viewer/src/modules/UrlHelper.ts b/packages/viewer/src/modules/UrlHelper.ts
index 4f407b576c..11d675170d 100644
--- a/packages/viewer/src/modules/UrlHelper.ts
+++ b/packages/viewer/src/modules/UrlHelper.ts
@@ -207,7 +207,9 @@ async function runModelLastVersionQuery(
return `${ref.origin}/streams/${ref.projectId}/objects/${data.project.model.versions.items[0].referencedObject}`
} catch (e) {
Logger.error(
- `Could not get object URLs for project ${ref.projectId} and model ${resource.modelId}. Error: ${e.message}`
+ `Could not get object URLs for project ${ref.projectId} and model ${
+ resource.modelId
+ }. Error: ${e instanceof Error ? e.message : e}`
)
}
return ''
@@ -245,7 +247,9 @@ async function runModelVersionQuery(
return `${ref.origin}/streams/${ref.projectId}/objects/${data.project.model.version.referencedObject}`
} catch (e) {
Logger.error(
- `Could not get object URLs for project ${ref.projectId} and model ${resource.modelId}. Error: ${e.message}`
+ `Could not get object URLs for project ${ref.projectId} and model ${
+ resource.modelId
+ }. Error: ${e instanceof Error ? e.message : e}`
)
}
return ''
@@ -292,7 +296,9 @@ async function runAllModelsQuery(
return urls
} catch (e) {
Logger.error(
- `Could not get object URLs for project ${ref.projectId}. Error: ${e.message}`
+ `Could not get object URLs for project ${ref.projectId}. Error: ${
+ e instanceof Error ? e.message : e
+ }`
)
}
return ['']
diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts
index 2cbcbb5295..5e00cde802 100644
--- a/packages/viewer/src/modules/Viewer.ts
+++ b/packages/viewer/src/modules/Viewer.ts
@@ -4,31 +4,32 @@ import EventEmitter from './EventEmitter'
import { Clock, Texture } from 'three'
import { Assets } from './Assets'
-import { Optional } from '../helpers/typeHelper'
+import { type Optional } from '../helpers/typeHelper'
import {
DefaultViewerParams,
- IViewer,
- SpeckleView,
- SunLightConfiguration,
+ type IViewer,
+ type SpeckleView,
+ type SunLightConfiguration,
UpdateFlags,
ViewerEvent,
- ViewerParams
+ type ViewerParams,
+ type ViewerEventPayload
} from '../IViewer'
import { World } from './World'
-import { TreeNode, WorldTree } from './tree/WorldTree'
+import { type TreeNode, WorldTree } from './tree/WorldTree'
import SpeckleRenderer from './SpeckleRenderer'
-import { PropertyInfo, PropertyManager } from './filtering/PropertyManager'
-import { DataTree, DataTreeBuilder } from './tree/DataTree'
+import { type PropertyInfo, PropertyManager } from './filtering/PropertyManager'
import Logger from 'js-logger'
-import { Query, QueryArgsResultMap, QueryResult } from './queries/Query'
+import type { Query, QueryArgsResultMap } from './queries/Query'
import { Queries } from './queries/Queries'
-import { Utils } from './Utils'
+import { type Utils } from './Utils'
import { Extension } from './extensions/Extension'
import Input from './input/Input'
import { CameraController } from './extensions/CameraController'
import { SpeckleType } from './loaders/GeometryConverter'
import { Loader } from './loaders/Loader'
import { type Constructor } from 'type-fest'
+import { RenderTree } from './tree/RenderTree'
export class Viewer extends EventEmitter implements IViewer {
/** Container and optional stats element */
@@ -55,7 +56,7 @@ export class Viewer extends EventEmitter implements IViewer {
} = {}
/** various utils/helpers */
- protected utils: Utils
+ protected utils: Utils | undefined
/** Gets the World object. Currently it's used for info mostly */
public get World(): World {
return this.world
@@ -87,21 +88,24 @@ export class Viewer extends EventEmitter implements IViewer {
}
public createExtension(type: Constructor): T {
- const extensionsToInject: Array Extension> =
- type.prototype.inject
+ const extensionsToInject: Array<
+ new (viewer: IViewer, ...args: Extension[]) => Extension
+ > = type.prototype.inject
const injectedExtensions: Array = []
- extensionsToInject.forEach((value: new (viewer: IViewer, ...args) => Extension) => {
- if (this.extensions[value.name]) {
- injectedExtensions.push(this.extensions[value.name])
- return
- }
- for (const k in this.extensions) {
- const prototypeChain = this.getConstructorChain(this.extensions[k])
- if (prototypeChain.includes(value.name)) {
- injectedExtensions.push(this.extensions[k])
+ extensionsToInject.forEach(
+ (value: new (viewer: IViewer, ...args: Extension[]) => Extension) => {
+ if (this.extensions[value.name]) {
+ injectedExtensions.push(this.extensions[value.name])
+ return
+ }
+ for (const k in this.extensions) {
+ const prototypeChain = this.getConstructorChain(this.extensions[k])
+ if (prototypeChain.includes(value.name)) {
+ injectedExtensions.push(this.extensions[k])
+ }
}
}
- })
+ )
const extension = new type(this, ...injectedExtensions)
this.extensions[type.name] = extension
@@ -109,6 +113,18 @@ export class Viewer extends EventEmitter implements IViewer {
}
public getExtension(type: Constructor): T {
+ let extension
+ if ((extension = this.getExtensionInternal(type)) !== null) return extension
+
+ throw new Error(`Could not get Extension of type ${type.name}. Is it created?`)
+ }
+
+ public hasExtension(type: Constructor): boolean {
+ const extension = this.getExtensionInternal(type)
+ return extension ? true : false
+ }
+
+ private getExtensionInternal(type: Constructor): T | null {
if (this.extensions[type.name]) return this.extensions[type.name] as T
else {
for (const k in this.extensions) {
@@ -118,6 +134,7 @@ export class Viewer extends EventEmitter implements IViewer {
}
}
}
+ return null
}
public constructor(
@@ -139,7 +156,7 @@ export class Viewer extends EventEmitter implements IViewer {
this.clock = new Clock()
this.inProgressOperations = 0
- this.speckleRenderer = new SpeckleRenderer(this)
+ this.speckleRenderer = new SpeckleRenderer(this.tree, this)
this.speckleRenderer.create(this.container)
window.addEventListener('resize', this.resize.bind(this), false)
@@ -147,10 +164,6 @@ export class Viewer extends EventEmitter implements IViewer {
this.frame()
this.resize()
-
- this.on(ViewerEvent.LoadCancelled, (url: string) => {
- Logger.warn(`Cancelled load for ${url}`)
- })
}
public getContainer() {
@@ -170,7 +183,7 @@ export class Viewer extends EventEmitter implements IViewer {
})
}
- public requestRender(flags: number = UpdateFlags.RENDER) {
+ public requestRender(flags: UpdateFlags = UpdateFlags.RENDER) {
if (flags & UpdateFlags.RENDER) {
this.speckleRenderer.needsRender = true
this.speckleRenderer.resetPipeline()
@@ -225,26 +238,29 @@ export class Viewer extends EventEmitter implements IViewer {
}
}
- public on(eventType: ViewerEvent, listener: (arg) => void): void {
+ on(
+ eventType: T,
+ listener: (arg: ViewerEventPayload[T]) => void
+ ): void {
super.on(eventType, listener)
}
public getObjectProperties(
- resourceURL: string = null,
+ resourceURL: string | null = null,
bypassCache = true
): Promise {
return this.propertyManager.getProperties(this.tree, resourceURL, bypassCache)
}
- public getDataTree(): DataTree {
- return DataTreeBuilder.build(this.tree)
+ public getDataTree(): void {
+ Logger.error('DataTree has been deprecated! Please use WorldTree')
}
public getWorldTree(): WorldTree {
return this.tree
}
- public query(query: T): QueryArgsResultMap[T['operation']] {
+ public query(query: T): QueryArgsResultMap[T['operation']] | null {
if (Queries.isPointQuery(query)) {
Queries.DefaultPointQuerySolver.setContext(this.speckleRenderer)
return Queries.DefaultPointQuerySolver.solve(query)
@@ -253,11 +269,7 @@ export class Viewer extends EventEmitter implements IViewer {
Queries.DefaultIntersectionQuerySolver.setContext(this.speckleRenderer)
return Queries.DefaultIntersectionQuerySolver.solve(query)
}
- }
- public queryAsync(query: Query): Promise {
- //TO DO
- query
return null
}
@@ -283,11 +295,11 @@ export class Viewer extends EventEmitter implements IViewer {
return new Promise((resolve) => {
// const sectionBoxVisible = this.sectionBox.display.visible
// if (sectionBoxVisible) {
- // this.sectionBox.displayOff()
+ // this.sectionBox.visible = false
// }
const screenshot = this.speckleRenderer.renderer.domElement.toDataURL('image/png')
// if (sectionBoxVisible) {
- // this.sectionBox.displayOn()
+ // this.sectionBox.visible = true
// }
resolve(screenshot)
})
@@ -298,14 +310,20 @@ export class Viewer extends EventEmitter implements IViewer {
*/
public async loadObject(loader: Loader, zoomToObject = true) {
- if (++this.inProgressOperations === 1)
- (this as EventEmitter).emit(ViewerEvent.Busy, true)
+ if (++this.inProgressOperations === 1) this.emit(ViewerEvent.Busy, true)
this.loaders[loader.resource] = loader
const treeBuilt = await loader.load()
if (treeBuilt) {
+ const renderTree: RenderTree | null = this.tree.getRenderTree(loader.resource)
+ /** Catering to typescript
+ * The render tree can't be null, we've just built it
+ */
+ if (!renderTree) {
+ throw new Error(`Could not get render tree ${loader.resource}`)
+ }
const t0 = performance.now()
- for await (const step of this.speckleRenderer.addRenderTree(loader.resource)) {
+ for await (const step of this.speckleRenderer.addRenderTree(renderTree)) {
step
if (zoomToObject) {
const extension = this.getExtension(CameraController)
@@ -324,26 +342,23 @@ export class Viewer extends EventEmitter implements IViewer {
if (this.loaders[loader.resource]) this.loaders[loader.resource].dispose()
delete this.loaders[loader.resource]
- if (--this.inProgressOperations === 0)
- (this as EventEmitter).emit(ViewerEvent.Busy, false)
+ if (--this.inProgressOperations === 0) this.emit(ViewerEvent.Busy, false)
}
public async cancelLoad(resource: string, unload = false) {
this.loaders[resource].cancel()
- this.tree.getRenderTree(resource).cancelBuild(resource)
+ this.tree.getRenderTree(resource)?.cancelBuild()
this.speckleRenderer.cancelRenderTree(resource)
if (unload) {
await this.unloadObject(resource)
} else {
- if (--this.inProgressOperations === 0)
- (this as EventEmitter).emit(ViewerEvent.Busy, false)
+ if (--this.inProgressOperations === 0) this.emit(ViewerEvent.Busy, false)
}
}
public async unloadObject(resource: string) {
try {
- if (++this.inProgressOperations === 1)
- (this as EventEmitter).emit(ViewerEvent.Busy, true)
+ if (++this.inProgressOperations === 1) this.emit(ViewerEvent.Busy, true)
if (this.tree.findSubtree(resource)) {
if (this.loaders[resource]) {
await this.cancelLoad(resource, true)
@@ -351,38 +366,37 @@ export class Viewer extends EventEmitter implements IViewer {
}
delete this.loaders[resource]
this.speckleRenderer.removeRenderTree(resource)
- this.tree.getRenderTree(resource).purge()
+ this.tree.getRenderTree(resource)?.purge()
this.tree.purge(resource)
this.requestRender(UpdateFlags.RENDER | UpdateFlags.SHADOWS)
}
} finally {
if (--this.inProgressOperations === 0) {
- ;(this as EventEmitter).emit(ViewerEvent.Busy, false)
+ this.emit(ViewerEvent.Busy, false)
Logger.warn(`Removed subtree ${resource}`)
- ;(this as EventEmitter).emit(ViewerEvent.UnloadComplete, resource)
+ this.emit(ViewerEvent.UnloadComplete, resource)
}
}
}
public async unloadAll() {
try {
- if (++this.inProgressOperations === 1)
- (this as EventEmitter).emit(ViewerEvent.Busy, true)
+ if (++this.inProgressOperations === 1) this.emit(ViewerEvent.Busy, true)
for (const key of Object.keys(this.loaders)) {
if (this.loaders[key]) await this.cancelLoad(key, false)
delete this.loaders[key]
}
- this.tree.root.children.forEach((node) => {
+ this.tree.root.children.forEach((node: TreeNode) => {
this.speckleRenderer.removeRenderTree(node.model.id)
- this.tree.getRenderTree().purge()
+ this.tree.getRenderTree()?.purge()
})
this.tree.purge()
} finally {
if (--this.inProgressOperations === 0) {
- ;(this as EventEmitter).emit(ViewerEvent.Busy, false)
+ this.emit(ViewerEvent.Busy, false)
Logger.warn(`Removed all subtrees`)
- ;(this as EventEmitter).emit(ViewerEvent.UnloadAllComplete)
+ this.emit(ViewerEvent.UnloadAllComplete)
}
}
}
diff --git a/packages/viewer/src/modules/batching/Batch.ts b/packages/viewer/src/modules/batching/Batch.ts
index c880ed9149..f95e2f6396 100644
--- a/packages/viewer/src/modules/batching/Batch.ts
+++ b/packages/viewer/src/modules/batching/Batch.ts
@@ -1,5 +1,5 @@
import { Box3, Material, Object3D, WebGLRenderer } from 'three'
-import { FilterMaterialOptions } from '../materials/Materials'
+import { type FilterMaterialOptions } from '../materials/Materials'
import { NodeRenderView } from '../tree/NodeRenderView'
export enum GeometryType {
@@ -10,6 +10,12 @@ export enum GeometryType {
TEXT
}
+export interface DrawGroup {
+ start: number
+ count: number
+ materialIndex: number
+}
+
/** TO DO: Unify point and mesh batch implementations */
export interface Batch {
id: string
@@ -31,22 +37,22 @@ export interface Batch {
getCount(): number
setBatchMaterial(material: Material): void
- setBatchBuffers(...range: BatchUpdateRange[]): void
- setVisibleRange(...range: BatchUpdateRange[])
+ setBatchBuffers(range: BatchUpdateRange[]): void
+ setVisibleRange(range: BatchUpdateRange[]): void
getVisibleRange(): BatchUpdateRange
- setDrawRanges(...ranges: BatchUpdateRange[])
- resetDrawRanges()
- buildBatch()
- getRenderView(index: number): NodeRenderView
- getMaterialAtIndex(index: number): Material
- getMaterial(rv: NodeRenderView): Material
+ setDrawRanges(ranges: BatchUpdateRange[]): void
+ resetDrawRanges(): void
+ buildBatch(): void
+ getRenderView(index: number): NodeRenderView | null
+ getMaterialAtIndex(index: number): Material | null
+ getMaterial(rv: NodeRenderView): Material | null
getOpaque(): BatchUpdateRange
getTransparent(): BatchUpdateRange
getStencil(): BatchUpdateRange
getDepth(): BatchUpdateRange
- onUpdate(deltaTime: number)
- onRender?(renderer: WebGLRenderer)
- purge()
+ onUpdate(deltaTime?: number): void
+ onRender?(renderer: WebGLRenderer): void
+ purge(): void
}
export interface BatchUpdateRange {
@@ -65,16 +71,6 @@ export const AllBatchUpdateRange = {
offset: 0,
count: Infinity
} as BatchUpdateRange
-export interface DrawGroup {
- start: number
- count: number
- materialIndex?: number
-}
-export interface DrawGroup {
- start: number
- count: number
- materialIndex?: number
-}
export const INSTANCE_TRANSFORM_BUFFER_STRIDE = 16
export const INSTANCE_GRADIENT_BUFFER_STRIDE = 1
diff --git a/packages/viewer/src/modules/batching/BatchObject.ts b/packages/viewer/src/modules/batching/BatchObject.ts
index db612722cb..bc7c7b4337 100644
--- a/packages/viewer/src/modules/batching/BatchObject.ts
+++ b/packages/viewer/src/modules/batching/BatchObject.ts
@@ -12,6 +12,8 @@ export type VectorLike =
| { x: number; y: number; z?: number; w?: number }
| undefined
| null
+export type Vector3Like = VectorLike & { z: number }
+export type Vector4Like = Vector3Like & { w: number }
export class BatchObject {
protected _renderView: NodeRenderView
@@ -21,8 +23,8 @@ export class BatchObject {
public transform: Matrix4
public transformInv: Matrix4
- public tasVertIndexStart: number
- public tasVertIndexEnd: number
+ public tasVertIndexStart!: number
+ public tasVertIndexEnd!: number
public quaternion: Quaternion = new Quaternion()
public eulerValue: Euler = new Euler()
@@ -53,14 +55,13 @@ export class BatchObject {
return this._batchIndex
}
- public get speckleId(): string {
- return this._renderView.renderData.id
- }
-
public get aabb(): Box3 {
- const box = new Box3().copy(this.renderView.aabb)
- box.applyMatrix4(this.transform)
- return box
+ if (this.renderView.aabb) {
+ const box = new Box3().copy(this.renderView.aabb)
+ box.applyMatrix4(this.transform)
+ return box
+ }
+ return new Box3()
}
public get localOrigin(): Vector3 {
@@ -117,11 +118,13 @@ export class BatchObject {
transform.invert()
if (!bvh) {
- const indices = this._renderView.renderData.geometry.attributes.INDEX
- const position = this._renderView.renderData.geometry.attributes.POSITION
+ const indices: number[] | undefined =
+ this._renderView.renderData.geometry.attributes?.INDEX
+ const position: number[] | undefined =
+ this._renderView.renderData.geometry.attributes?.POSITION
bvh = AccelerationStructure.buildBVH(
indices,
- new Float32Array(position),
+ position,
DefaultBVHOptions,
transform
)
@@ -137,10 +140,10 @@ export class BatchObject {
}
public transformTRS(
- translation: VectorLike,
- euler: VectorLike,
- scale: VectorLike,
- pivot: VectorLike
+ translation: Vector3Like,
+ euler?: Vector3Like,
+ scale?: Vector3Like,
+ pivot?: Vector3Like
) {
let T: Matrix4 = BatchObject.matBuff0.identity()
let R: Matrix4 = BatchObject.matBuff1.identity()
diff --git a/packages/viewer/src/modules/batching/Batcher.ts b/packages/viewer/src/modules/batching/Batcher.ts
index 1085c95aff..772ed53793 100644
--- a/packages/viewer/src/modules/batching/Batcher.ts
+++ b/packages/viewer/src/modules/batching/Batcher.ts
@@ -1,8 +1,17 @@
import { MathUtils } from 'three'
import LineBatch from './LineBatch'
-import Materials, { FilterMaterialType } from '../materials/Materials'
+import Materials, {
+ FilterMaterialType,
+ type DisplayStyle,
+ type RenderMaterial
+} from '../materials/Materials'
import { NodeRenderView } from '../tree/NodeRenderView'
-import { Batch, BatchUpdateRange, GeometryType, NoneBatchUpdateRange } from './Batch'
+import {
+ type Batch,
+ type BatchUpdateRange,
+ GeometryType,
+ NoneBatchUpdateRange
+} from './Batch'
import { Material, WebGLRenderer } from 'three'
import Logger from 'js-logger'
import { AsyncPause } from '../World'
@@ -10,12 +19,20 @@ import { RenderTree } from '../tree/RenderTree'
import TextBatch from './TextBatch'
import SpeckleMesh, { TransformStorage } from '../objects/SpeckleMesh'
import { SpeckleType } from '../loaders/GeometryConverter'
-import { TreeNode, WorldTree } from '../..'
+import { type TreeNode, WorldTree } from '../..'
import { InstancedMeshBatch } from './InstancedMeshBatch'
import { Geometry } from '../converter/Geometry'
import { MeshBatch } from './MeshBatch'
import { PointBatch } from './PointBatch'
+type BatchTypeMap = {
+ [GeometryType.MESH]: MeshBatch
+ [GeometryType.LINE]: LineBatch
+ [GeometryType.POINT]: PointBatch
+ [GeometryType.POINT_CLOUD]: PointBatch
+ [GeometryType.TEXT]: TextBatch
+}
+
export default class Batcher {
private maxHardwareUniformCount = 0
private floatTextures = false
@@ -41,7 +58,6 @@ export default class Batcher {
speckleType: SpeckleType[],
batchType?: GeometryType
) {
- const start = performance.now()
let min = Number.MAX_SAFE_INTEGER,
max = -1,
average = 0,
@@ -51,7 +67,6 @@ export default class Batcher {
const instancedBatches: { [id: string]: Array } = {}
const pause = new AsyncPause()
- const startInstancedGathering = performance.now()
for (const g in instanceGroups) {
pause.tick(100)
if (pause.needsWait) {
@@ -81,13 +96,10 @@ export default class Batcher {
}
instancedBatches[vertCount].push(g)
}
- const instancedGathering = performance.now() - startInstancedGathering
-
- let deInstancing = 0
- let instanceBuild = 0
for (const v in instancedBatches) {
for (let k = 0; k < instancedBatches[v].length; k++) {
const nodes = worldTree.findId(instancedBatches[v][k])
+ if (!nodes) continue
/** Make sure entire instance set is instanced */
let instanced = true
nodes.every((node: TreeNode) => (instanced &&= node.model.instanced))
@@ -98,7 +110,6 @@ export default class Batcher {
.filter((rv) => rv)
if (Number.parseInt(v) < this.minInstancedBatchVertices || !instanced) {
- const t0 = performance.now()
rvs.forEach((nodeRv) => {
const geometry = nodeRv.renderData.geometry
geometry.instanced = false
@@ -118,28 +129,24 @@ export default class Batcher {
Geometry.transformGeometryData(geometry, geometry.transform)
nodeRv.computeAABB()
})
- deInstancing += performance.now() - t0
continue
}
- const t1 = performance.now()
const materialHash = rvs[0].renderMaterialHash
const instancedBatch = await this.buildInstancedBatch(
renderTree,
rvs,
materialHash
)
- instanceBuild += performance.now() - t1
+ if (!instancedBatch) continue
this.batches[instancedBatch.id] = instancedBatch
min = Math.min(min, instancedBatch.renderViews.length)
max = Math.max(max, instancedBatch.renderViews.length)
- average += instancedBatch.renderViews.length
batchCount++
yield this.batches[instancedBatch.id]
}
}
- const totalInstanced = performance.now() - start
const renderViews = renderTree
.getRenderableNodes(...speckleType)
@@ -193,6 +200,8 @@ export default class Batcher {
batchType
)
+ if (!batch) continue
+
this.batches[batch.id] = batch
min = Math.min(min, batch.renderViews.length)
max = Math.max(max, batch.renderViews.length)
@@ -206,10 +215,6 @@ export default class Batcher {
average / materialHashes.length
}`
)
- Logger.warn('Total instanced -> ', totalInstanced)
- Logger.warn('Instance gathering -> ', instancedGathering)
- Logger.warn('De-instancing -> ', deInstancing)
- Logger.warn('Instanced build -> ', instanceBuild)
}
private splitBatch(
@@ -217,19 +222,29 @@ export default class Batcher {
vertCount: number
): NodeRenderView[][] {
/** We're first splitting based on the batch's max vertex count */
- const vSplit = []
+ const vSplit: Array> = []
const vDiv = Math.floor(vertCount / this.maxBatchVertices)
if (vDiv > 0) {
let count = 0
let index = 0
vSplit.push([])
for (let k = 0; k < renderViews.length; k++) {
+ /** Catering to typescript.
+ * RenderViews are prefiltered based on valid geometry before reaching this point
+ */
+ const ervee = renderViews[k]
+ const nextErvee = renderViews[k + 1]
+ if (!ervee.renderData.geometry.attributes) {
+ throw new Error(
+ `Invalid geometry on render view ${renderViews[k].renderData.id}`
+ )
+ }
vSplit[index].push(renderViews[k])
- count += renderViews[k].renderData.geometry.attributes.POSITION.length / 3
+ count += ervee.renderData.geometry.attributes.POSITION.length / 3
const nexCount =
count +
- (renderViews[k + 1]
- ? renderViews[k + 1].renderData.geometry.attributes.POSITION.length / 3
+ (nextErvee && nextErvee.renderData.geometry.attributes
+ ? nextErvee.renderData.geometry.attributes.POSITION.length / 3
: 0)
if (nexCount >= this.maxBatchVertices && renderViews[k + 1]) {
vSplit.push([])
@@ -267,7 +282,7 @@ export default class Batcher {
renderTree: RenderTree,
renderViews: NodeRenderView[],
materialHash: number
- ): Promise {
+ ): Promise {
if (!renderViews.length) {
/** This is for the case when all renderviews have invalid geometries, and it generally
* means there is something wrong with the stream
@@ -294,7 +309,7 @@ export default class Batcher {
renderViews: NodeRenderView[],
materialHash: number,
batchType?: GeometryType
- ): Promise {
+ ): Promise {
if (!renderViews.length) {
/** This is for the case when all renderviews have invalid geometries, and it generally
* means there is something wrong with the stream
@@ -308,7 +323,8 @@ export default class Batcher {
const geometryType =
batchType !== undefined ? batchType : renderViews[0].geometryType
- let matRef = null
+ let matRef: RenderMaterial | DisplayStyle | null =
+ renderViews[0].renderData.renderMaterial
if (geometryType === GeometryType.MESH) {
matRef = renderViews[0].renderData.renderMaterial
@@ -325,7 +341,7 @@ export default class Batcher {
const material = this.materials.getMaterial(materialHash, matRef, geometryType)
const batchID = MathUtils.generateUUID()
- let geometryBatch: Batch = null
+ let geometryBatch: Batch | null = null
switch (geometryType) {
case GeometryType.MESH:
geometryBatch = new MeshBatch(
@@ -365,12 +381,13 @@ export default class Batcher {
public render(renderer: WebGLRenderer) {
for (const batchId in this.batches) {
- if (this.batches[batchId].onRender) this.batches[batchId].onRender(renderer)
+ const batch = this.batches[batchId]
+ if (batch.onRender) batch.onRender(renderer)
}
}
public saveVisiblity(): Record {
- const visibilityRanges = {}
+ const visibilityRanges: Record = {}
for (const k in this.batches) {
const batch: Batch = this.batches[k]
visibilityRanges[k] = batch.getVisibleRange()
@@ -383,15 +400,15 @@ export default class Batcher {
const batch: Batch = this.batches[k]
const range = ranges[k]
if (!range) {
- batch.setVisibleRange(NoneBatchUpdateRange)
+ batch.setVisibleRange([NoneBatchUpdateRange])
} else {
- batch.setVisibleRange(range)
+ batch.setVisibleRange([range])
}
}
}
public getTransparent(): Record {
- const visibilityRanges = {}
+ const visibilityRanges: Record = {}
for (const k in this.batches) {
visibilityRanges[k] = this.batches[k].getTransparent()
}
@@ -399,7 +416,7 @@ export default class Batcher {
}
public getStencil(): Record {
- const visibilityRanges = {}
+ const visibilityRanges: Record = {}
for (const k in this.batches) {
visibilityRanges[k] = this.batches[k].getStencil()
}
@@ -407,7 +424,7 @@ export default class Batcher {
}
public getOpaque(): Record {
- const visibilityRanges = {}
+ const visibilityRanges: Record = {}
for (const k in this.batches) {
visibilityRanges[k] = this.batches[k].getOpaque()
}
@@ -415,7 +432,7 @@ export default class Batcher {
}
public getDepth(): Record {
- const visibilityRanges = {}
+ const visibilityRanges: Record = {}
for (const k in this.batches) {
visibilityRanges[k] = this.batches[k].getDepth()
}
@@ -450,13 +467,38 @@ export default class Batcher {
}
}
- public getBatches(subtreeId?: string, geometryType?: GeometryType) {
- return Object.values(this.batches).filter((value: Batch) => {
+ public getBatches(
+ subtreeId?: string,
+ geometryType?: K
+ ): BatchTypeMap[K][] {
+ const batches: Batch[] = Object.values(this.batches)
+ return batches.filter((value: Batch) => {
const subtree = subtreeId !== undefined ? value.subtreeId === subtreeId : true
const type =
- geometryType !== undefined ? value.geometryType === geometryType : true
+ geometryType !== undefined ? this.isBatchType(value, geometryType) : true
return subtree && type
- })
+ }) as BatchTypeMap[K][]
+ }
+
+ private isBatchType(
+ batch: Batch,
+ geometryType?: K
+ ): batch is BatchTypeMap[K] {
+ if (geometryType === undefined) return true
+ switch (geometryType) {
+ case GeometryType.MESH:
+ return batch instanceof MeshBatch
+ case GeometryType.LINE:
+ return batch instanceof LineBatch
+ case GeometryType.POINT:
+ return batch instanceof PointBatch
+ case GeometryType.POINT_CLOUD:
+ return batch instanceof PointBatch
+ case GeometryType.TEXT:
+ return batch instanceof TextBatch
+ default:
+ return false
+ }
}
public getBatch(rv: NodeRenderView) {
@@ -490,32 +532,18 @@ export default class Batcher {
/**
* Used for debuggin only
*/
-
- public async isolateRenderViewBatch(id: string, renderTree: RenderTree) {
- const rv = renderTree.getRenderViewForNodeId(id)
- for (const k in this.batches) {
- if (k !== rv.batchId) {
- this.batches[k].setDrawRanges({
- offset: 0,
- count: this.batches[k].getCount(),
- material: this.materials.getFilterMaterial(this.batches[k].renderViews[0], {
- filterType: FilterMaterialType.GHOST
- })
- })
- }
- }
- }
-
public async isolateBatch(batchId: string) {
for (const k in this.batches) {
if (k !== batchId) {
- this.batches[k].setDrawRanges({
- offset: 0,
- count: this.batches[k].getCount(),
- material: this.materials.getFilterMaterial(this.batches[k].renderViews[0], {
- filterType: FilterMaterialType.GHOST
- })
- })
+ this.batches[k].setDrawRanges([
+ {
+ offset: 0,
+ count: this.batches[k].getCount(),
+ material: this.materials.getFilterMaterial(this.batches[k].renderViews[0], {
+ filterType: FilterMaterialType.GHOST
+ }) as Material
+ }
+ ])
}
}
}
diff --git a/packages/viewer/src/modules/batching/DrawRanges.ts b/packages/viewer/src/modules/batching/DrawRanges.ts
index 219030dedc..17f2979165 100644
--- a/packages/viewer/src/modules/batching/DrawRanges.ts
+++ b/packages/viewer/src/modules/batching/DrawRanges.ts
@@ -1,6 +1,6 @@
import { Material } from 'three'
-import { BatchUpdateRange } from './Batch'
-import { DrawGroup } from './Batch'
+import { type BatchUpdateRange } from './Batch'
+import { type DrawGroup } from './Batch'
export class DrawRanges {
public integrateRanges(
@@ -12,14 +12,14 @@ export class DrawRanges {
groups.sort((a, b) => a.start - b.start)
ranges.sort((a, b) => a.offset - b.offset)
- const edgesForward = {}
- const edgesBackwards = {}
+ const edgesForward: { [key: number]: number } = {}
+ const edgesBackwards: { [key: number]: number } = {}
for (let k = 0, l = groups.length - 1; k < groups.length; k++, l--) {
const groupForward = groups[k]
const groupBackwards = groups[l]
- edgesForward[groupForward.start] = groupForward.materialIndex
+ edgesForward[groupForward.start] = groupForward.materialIndex as number
edgesBackwards[groupBackwards.start + groupBackwards.count] =
- groupBackwards.materialIndex
+ groupBackwards.materialIndex as number
}
_flatRanges = groups.map((group: DrawGroup) => {
@@ -43,7 +43,7 @@ export class DrawRanges {
})
_flatRanges = [...new Set(_flatRanges)]
- const materialIndex = materials.indexOf(range.material)
+ const materialIndex = materials.indexOf(range.material as Material)
edgesForward[r0] = materialIndex
edgesForward[r1] = r1 >= next ? edgesForward[next] : edgesBackwards[next]
}
diff --git a/packages/viewer/src/modules/batching/InstancedBatchObject.ts b/packages/viewer/src/modules/batching/InstancedBatchObject.ts
index ec8b17ef12..f599d27d18 100644
--- a/packages/viewer/src/modules/batching/InstancedBatchObject.ts
+++ b/packages/viewer/src/modules/batching/InstancedBatchObject.ts
@@ -1,5 +1,5 @@
/* eslint-disable camelcase */
-import { BatchObject, VectorLike } from './BatchObject'
+import { BatchObject, type Vector3Like } from './BatchObject'
import { Matrix4 } from 'three'
import { NodeRenderView } from '../tree/NodeRenderView'
@@ -9,17 +9,18 @@ export class InstancedBatchObject extends BatchObject {
public constructor(renderView: NodeRenderView, batchIndex: number) {
super(renderView, batchIndex)
- this.instanceTransform.copy(renderView.renderData.geometry.transform)
+ if (renderView.renderData.geometry.transform)
+ this.instanceTransform.copy(renderView.renderData.geometry.transform)
this.transform.copy(this.instanceTransform)
this.transformInv.copy(new Matrix4().copy(this.instanceTransform).invert())
this.transformDirty = false
}
public transformTRS(
- translation: VectorLike,
- euler: VectorLike,
- scale: VectorLike,
- pivot: VectorLike
+ translation: Vector3Like,
+ euler: Vector3Like,
+ scale: Vector3Like,
+ pivot: Vector3Like
) {
super.transformTRS(translation, euler, scale, pivot)
this.transform.multiply(this.instanceTransform)
diff --git a/packages/viewer/src/modules/batching/InstancedMeshBatch.ts b/packages/viewer/src/modules/batching/InstancedMeshBatch.ts
index 736b7a2715..f283d74e95 100644
--- a/packages/viewer/src/modules/batching/InstancedMeshBatch.ts
+++ b/packages/viewer/src/modules/batching/InstancedMeshBatch.ts
@@ -14,8 +14,9 @@ import { Geometry } from '../converter/Geometry'
import { NodeRenderView } from '../tree/NodeRenderView'
import {
AllBatchUpdateRange,
- Batch,
- BatchUpdateRange,
+ type Batch,
+ type BatchUpdateRange,
+ type DrawGroup,
GeometryType,
INSTANCE_TRANSFORM_BUFFER_STRIDE,
NoneBatchUpdateRange
@@ -31,7 +32,6 @@ import Logger from 'js-logger'
import Materials from '../materials/Materials'
import { DrawRanges } from './DrawRanges'
import SpeckleStandardColoredMaterial from '../materials/SpeckleStandardColoredMaterial'
-import { DrawGroup } from './Batch'
export class InstancedMeshBatch implements Batch {
public id: string
@@ -40,12 +40,12 @@ export class InstancedMeshBatch implements Batch {
private geometry: BufferGeometry
public batchMaterial: Material
public mesh: SpeckleInstancedMesh
- private drawRanges: DrawRanges = new DrawRanges()
+ protected drawRanges: DrawRanges = new DrawRanges()
- private instanceTransformBuffer0: Float32Array = null
- private instanceTransformBuffer1: Float32Array = null
+ private instanceTransformBuffer0!: Float32Array
+ private instanceTransformBuffer1!: Float32Array
private transformBufferIndex: number = 0
- private instanceGradientBuffer: Float32Array = null
+ private instanceGradientBuffer!: Float32Array
private needsShuffle = false
@@ -67,7 +67,11 @@ export class InstancedMeshBatch implements Batch {
}
public get triCount(): number {
- return (this.geometry.index.count / 3) * this.renderViews.length
+ /** Catering to typescript
+ * There is no unniverse where the geometry is non-indexed. We're **explicitly** setting the index at creation time
+ */
+ const indexCount = this.geometry.index ? this.geometry.index.count : 0
+ return (indexCount / 3) * this.renderViews.length
}
public get vertCount(): number {
@@ -125,7 +129,7 @@ export class InstancedMeshBatch implements Batch {
}
/** Note: You can only set visibility on ranges that exist as draw groups! */
- public setVisibleRange(...ranges: BatchUpdateRange[]) {
+ public setVisibleRange(ranges: BatchUpdateRange[]) {
/** Entire batch needs to NOT be drawn */
if (ranges.length === 1 && ranges[0] === NoneBatchUpdateRange) {
this.mesh.children.forEach((instance) => (instance.visible = false))
@@ -139,14 +143,15 @@ export class InstancedMeshBatch implements Batch {
this.mesh.children.forEach((instance) => (instance.visible = false))
ranges.forEach((range) => {
- const instanceIndex = this.groups.indexOf(
- this.groups.find(
- (group: DrawGroup) =>
- range.offset === group.start &&
- range.offset + range.count === group.start + group.count
- )
+ const foundInstance = this.groups.find(
+ (group: DrawGroup) =>
+ range.offset === group.start &&
+ range.offset + range.count === group.start + group.count
)
- if (instanceIndex !== -1) this.mesh.children[instanceIndex].visible = true
+ if (foundInstance) {
+ const instanceIndex = this.groups.indexOf(foundInstance)
+ if (instanceIndex !== -1) this.mesh.children[instanceIndex].visible = true
+ }
})
}
@@ -166,6 +171,7 @@ export class InstancedMeshBatch implements Batch {
public getOpaque(): BatchUpdateRange {
/** If there is any transparent or hidden group return the update range up to it's offset */
const transparentOrHiddenGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
return (
Materials.isTransparent(this.materials[value.materialIndex]) ||
this.materials[value.materialIndex].visible === false
@@ -185,6 +191,7 @@ export class InstancedMeshBatch implements Batch {
public getDepth(): BatchUpdateRange {
/** If there is any transparent or hidden group return the update range up to it's offset */
const transparentOrHiddenGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
return (
Materials.isTransparent(this.materials[value.materialIndex]) ||
this.materials[value.materialIndex].visible === false ||
@@ -205,10 +212,12 @@ export class InstancedMeshBatch implements Batch {
public getTransparent(): BatchUpdateRange {
/** Look for a transparent group */
const transparentGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
return Materials.isTransparent(this.materials[value.materialIndex])
})
/** Look for a hidden group */
const hiddenGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
return this.materials[value.materialIndex].visible === false
})
/** If there is a transparent group return it's range */
@@ -234,6 +243,7 @@ export class InstancedMeshBatch implements Batch {
if (this.materials[0].stencilWrite === true) return AllBatchUpdateRange
}
const stencilGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
return this.materials[value.materialIndex].stencilWrite === true
})
if (stencilGroup) {
@@ -246,28 +256,33 @@ export class InstancedMeshBatch implements Batch {
return NoneBatchUpdateRange
}
- public setBatchBuffers(...range: BatchUpdateRange[]): void {
- for (let k = 0; k < range.length; k++) {
- if (range[k].materialOptions) {
- if (range[k].materialOptions.rampIndex !== undefined) {
- const start = range[k].offset
+ public setBatchBuffers(ranges: BatchUpdateRange[]): void {
+ for (let k = 0; k < ranges.length; k++) {
+ const range = ranges[k]
+ if (range.materialOptions) {
+ if (
+ range.materialOptions.rampIndex !== undefined &&
+ range.materialOptions.rampWidth !== undefined
+ ) {
+ const start = ranges[k].offset
/** The ramp indices specify the *begining* of each ramp color. When sampling with Nearest filter (since we don't want filtering)
* we'll always be sampling right at the edge between texels. Most GPUs will sample consistently, but some won't and we end up with
* a ton of artifacts. To avoid this, we are shifting the sampling indices so they're right on the center of each texel, so no inconsistent
* sampling can occur.
*/
- const shiftedIndex =
- range[k].materialOptions.rampIndex +
- 0.5 / range[k].materialOptions.rampWidth
- this.updateGradientIndexBufferData(start / 16, shiftedIndex)
+ if (range.materialOptions.rampIndex && range.materialOptions.rampWidth) {
+ const shiftedIndex =
+ range.materialOptions.rampIndex + 0.5 / range.materialOptions.rampWidth
+ this.updateGradientIndexBufferData(start / 16, shiftedIndex)
+ }
}
/** We need to update the texture here, because each batch uses it's own clone for any material we use on it
* because otherwise three.js won't properly update our custom uniforms
*/
- if (range[k].materialOptions.rampTexture !== undefined) {
- if (range[k].material instanceof SpeckleStandardColoredMaterial) {
- ;(range[k].material as SpeckleStandardColoredMaterial).setGradientTexture(
- range[k].materialOptions.rampTexture
+ if (range.materialOptions.rampTexture !== undefined) {
+ if (range.material instanceof SpeckleStandardColoredMaterial) {
+ ;(range.material as SpeckleStandardColoredMaterial).setGradientTexture(
+ range.materialOptions.rampTexture
)
}
}
@@ -275,15 +290,15 @@ export class InstancedMeshBatch implements Batch {
}
}
- public setDrawRanges(...ranges: BatchUpdateRange[]) {
+ public setDrawRanges(ranges: BatchUpdateRange[]) {
ranges.forEach((value: BatchUpdateRange) => {
if (value.material) {
value.material = this.mesh.getCachedMaterial(value.material)
}
})
- const materials = ranges.map((val) => {
- return val.material
+ const materials: Array = ranges.map((val: BatchUpdateRange) => {
+ return val.material as Material
})
const uniqueMaterials = [...Array.from(new Set(materials.map((value) => value)))]
@@ -303,7 +318,7 @@ export class InstancedMeshBatch implements Batch {
if (count !== this.renderViews.length * 16) {
Logger.error(`Draw groups invalid on ${this.id}`)
}
- this.setBatchBuffers(...ranges)
+ this.setBatchBuffers(ranges)
this.cleanMaterials()
/** We shuffle only when above a certain fragmentation threshold. We don't want to be shuffling every single time */
if (this.drawCalls > this.maxDrawCalls) {
@@ -335,7 +350,7 @@ export class InstancedMeshBatch implements Batch {
}
}
- private shuffleDrawGroups() {
+ private shuffleDrawGroups(): void {
const groups = this.groups
.sort((a, b) => {
return a.start - b.start
@@ -354,10 +369,12 @@ export class InstancedMeshBatch implements Batch {
return transparentOrder
})
- const materialOrder = []
+ const materialOrder: Array = []
groups.reduce((previousValue, currentValue) => {
- if (previousValue.indexOf(currentValue.materialIndex) === -1) {
- previousValue.push(currentValue.materialIndex)
+ if (currentValue.materialIndex !== undefined) {
+ if (previousValue.indexOf(currentValue.materialIndex) === -1) {
+ previousValue.push(currentValue.materialIndex)
+ }
}
return previousValue
}, materialOrder)
@@ -438,17 +455,20 @@ export class InstancedMeshBatch implements Batch {
/** Solve hidden groups */
const hiddenGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
return this.materials[value.materialIndex].visible === false
})
if (hiddenGroup) {
- this.setVisibleRange({
- offset: 0,
- count: hiddenGroup.start
- })
+ this.setVisibleRange([
+ {
+ offset: 0,
+ count: hiddenGroup.start
+ }
+ ])
}
}
- public resetDrawRanges() {
+ public resetDrawRanges(): void {
this.groups.length = 0
this.materials.length = 0
this.groups.push({
@@ -457,7 +477,7 @@ export class InstancedMeshBatch implements Batch {
materialIndex: 0
})
this.materials.push(this.batchMaterial)
- this.setVisibleRange(AllBatchUpdateRange)
+ this.setVisibleRange([AllBatchUpdateRange])
this.mesh.updateDrawGroups(
this.getCurrentTransformBuffer(),
this.getCurrentGradientBuffer()
@@ -480,7 +500,7 @@ export class InstancedMeshBatch implements Batch {
return this.instanceGradientBuffer
}
- public buildBatch() {
+ public buildBatch(): void {
const batchObjects = []
let instanceBVH = null
this.instanceTransformBuffer0 = new Float32Array(
@@ -492,7 +512,17 @@ export class InstancedMeshBatch implements Batch {
const targetInstanceTransformBuffer = this.getCurrentTransformBuffer()
for (let k = 0; k < this.renderViews.length; k++) {
- this.renderViews[k].renderData.geometry.transform.toArray(
+ /** Catering to typescript
+ * There is no unniverse where an instanced render view does not have a transform
+ * It's against it's definition
+ */
+ const ervee = this.renderViews[k]
+ if (!ervee.renderData.geometry.transform) {
+ throw new Error(
+ `Instanced Render view with id ${ervee.renderData.id} has null transform!`
+ )
+ }
+ ervee.renderData.geometry.transform.toArray(
targetInstanceTransformBuffer,
k * INSTANCE_TRANSFORM_BUFFER_STRIDE
)
@@ -509,11 +539,13 @@ export class InstancedMeshBatch implements Batch {
batchObject.localOrigin.z
)
transform.invert()
- const indices = this.renderViews[k].renderData.geometry.attributes.INDEX
- const position = this.renderViews[k].renderData.geometry.attributes.POSITION
+ const indices: number[] | undefined =
+ this.renderViews[k].renderData.geometry.attributes?.INDEX
+ const position: number[] | undefined = this.renderViews[k].renderData.geometry
+ .attributes?.POSITION as number[]
instanceBVH = AccelerationStructure.buildBVH(
indices,
- new Float32Array(position),
+ position,
DefaultBVHOptions,
transform
)
@@ -524,25 +556,32 @@ export class InstancedMeshBatch implements Batch {
batchObjects.push(batchObject)
}
- const indices = new Uint32Array(
- this.renderViews[0].renderData.geometry.attributes.INDEX
- )
- const positions = new Float64Array(
- this.renderViews[0].renderData.geometry.attributes.POSITION
- )
- const colors = new Float32Array(
- this.renderViews[0].renderData.geometry.attributes.COLOR
- )
+ const indices: number[] | undefined =
+ this.renderViews[0].renderData.geometry.attributes?.INDEX
+
+ const positions: number[] | undefined =
+ this.renderViews[0].renderData.geometry.attributes?.POSITION
- this.makeInstancedMeshGeometry(indices, positions, colors)
+ const colors: number[] | undefined =
+ this.renderViews[0].renderData.geometry.attributes?.COLOR
+
+ /** Catering to typescript
+ * There is no unniverse where indices or positions are undefined at this point
+ */
+ if (!indices || !positions) {
+ throw new Error(`Cannot build batch ${this.id}. Undefined indices or positions`)
+ }
+ this.makeInstancedMeshGeometry(
+ positions.length >= 65535 || indices.length >= 65535
+ ? new Uint32Array(indices)
+ : new Uint16Array(indices),
+ new Float64Array(positions),
+ colors ? new Float32Array(colors) : undefined
+ )
this.mesh = new SpeckleInstancedMesh(this.geometry)
this.mesh.setBatchObjects(batchObjects)
this.mesh.setBatchMaterial(this.batchMaterial)
this.mesh.buildTAS()
- const bounds = new Box3()
- for (let k = 0; k < this.renderViews.length; k++) {
- bounds.union(this.renderViews[k].aabb)
- }
this.geometry.boundingBox = this.mesh.TAS.getBoundingBox(new Box3())
this.geometry.boundingSphere = this.geometry.boundingBox.getBoundingSphere(
@@ -564,19 +603,19 @@ export class InstancedMeshBatch implements Batch {
)
}
- public getRenderView(index: number): NodeRenderView {
+ public getRenderView(index: number): NodeRenderView | null {
index
Logger.warn('Deprecated! Use InstancedBatchObject')
return null
}
- public getMaterialAtIndex(index: number): Material {
+ public getMaterialAtIndex(index: number): Material | null {
index
Logger.warn('Deprecated! Use InstancedBatchObject')
return null
}
- public getMaterial(rv: NodeRenderView): Material {
+ public getMaterial(rv: NodeRenderView): Material | null {
const group = this.groups.find((value) => {
return (
rv.batchStart >= value.start &&
@@ -629,10 +668,9 @@ export class InstancedMeshBatch implements Batch {
data[index] = value
}
- public purge() {
+ public purge(): void {
this.renderViews.length = 0
this.geometry.dispose()
this.batchMaterial.dispose()
- this.mesh = null
}
}
diff --git a/packages/viewer/src/modules/batching/LineBatch.ts b/packages/viewer/src/modules/batching/LineBatch.ts
index 420ae26834..d911298477 100644
--- a/packages/viewer/src/modules/batching/LineBatch.ts
+++ b/packages/viewer/src/modules/batching/LineBatch.ts
@@ -1,9 +1,9 @@
import {
+ Box3,
Color,
DynamicDrawUsage,
InstancedInterleavedBuffer,
InterleavedBufferAttribute,
- Line,
Material,
Object3D,
Vector4,
@@ -16,28 +16,28 @@ import SpeckleLineMaterial from '../materials/SpeckleLineMaterial'
import { NodeRenderView } from '../tree/NodeRenderView'
import {
AllBatchUpdateRange,
- Batch,
- BatchUpdateRange,
+ type Batch,
+ type BatchUpdateRange,
+ type DrawGroup,
GeometryType,
NoneBatchUpdateRange
} from './Batch'
import { ObjectLayers } from '../../IViewer'
-import { DrawGroup } from './Batch'
import Materials from '../materials/Materials'
export default class LineBatch implements Batch {
public id: string
public subtreeId: string
public renderViews: NodeRenderView[]
- private geometry: LineSegmentsGeometry
+ protected geometry: LineSegmentsGeometry
public batchMaterial: SpeckleLineMaterial
- private mesh: LineSegments2 | Line
- public colorBuffer: InstancedInterleavedBuffer
+ protected mesh: LineSegments2
+ public colorBuffer!: InstancedInterleavedBuffer
private static readonly vector4Buffer: Vector4 = new Vector4()
- public get bounds() {
+ public get bounds(): Box3 {
if (!this.geometry.boundingBox) this.geometry.computeBoundingBox()
- return this.geometry.boundingBox
+ return this.geometry.boundingBox ? this.geometry.boundingBox : new Box3()
}
public get drawCalls(): number {
@@ -65,7 +65,11 @@ export default class LineBatch implements Batch {
return 0
}
public get lineCount(): number {
- return (this.geometry.index.count / 3) * this.geometry['_maxInstanceCount']
+ /** Catering to typescript
+ * There is no unniverse where the geometry is non-indexed. LineSegments2 are **explicitly** indexed
+ */
+ const indexCount = this.geometry.index ? this.geometry.index.count : 0
+ return (indexCount / 3) * (this.geometry as never)['_maxInstanceCount']
}
public get renderObject(): Object3D {
@@ -73,11 +77,11 @@ export default class LineBatch implements Batch {
}
public get geometryType(): GeometryType {
- return this.renderViews[0].geometryType
+ return GeometryType.LINE
}
public get materials(): Material[] {
- return this.mesh.material as Material[]
+ return this.mesh.material as unknown as Material[]
}
public get groups(): DrawGroup[] {
@@ -100,7 +104,7 @@ export default class LineBatch implements Batch {
renderer.getDrawingBufferSize(this.batchMaterial.resolution)
}
- public setVisibleRange(...ranges: BatchUpdateRange[]) {
+ public setVisibleRange(ranges: BatchUpdateRange[]) {
if (
ranges.length === 1 &&
ranges[0].offset === NoneBatchUpdateRange.offset &&
@@ -163,7 +167,7 @@ export default class LineBatch implements Batch {
return NoneBatchUpdateRange
}
- public setBatchBuffers(...ranges: BatchUpdateRange[]): void {
+ public setBatchBuffers(ranges: BatchUpdateRange[]): void {
const data = this.colorBuffer.array as number[]
for (let i = 0; i < ranges.length; i++) {
@@ -193,16 +197,18 @@ export default class LineBatch implements Batch {
this.geometry.attributes['instanceColorEnd'].needsUpdate = true
}
- public setDrawRanges(...ranges: BatchUpdateRange[]) {
- this.setBatchBuffers(...ranges)
+ public setDrawRanges(ranges: BatchUpdateRange[]) {
+ this.setBatchBuffers(ranges)
}
public resetDrawRanges() {
- this.setDrawRanges({
- offset: 0,
- count: Infinity,
- material: this.batchMaterial
- })
+ this.setDrawRanges([
+ {
+ offset: 0,
+ count: Infinity,
+ material: this.batchMaterial
+ }
+ ])
this.mesh.material = this.batchMaterial
this.mesh.visible = true
this.batchMaterial.transparent = false
@@ -210,17 +216,22 @@ export default class LineBatch implements Batch {
public buildBatch() {
let attributeCount = 0
- this.renderViews.forEach(
- (val: NodeRenderView) =>
- (attributeCount += val.needsSegmentConversion
- ? (val.renderData.geometry.attributes.POSITION.length - 3) * 2
- : val.renderData.geometry.attributes.POSITION.length)
- )
+ this.renderViews.forEach((val: NodeRenderView) => {
+ if (!val.renderData.geometry.attributes) {
+ throw new Error(`Cannot build batch ${this.id}. Invalid geometry`)
+ }
+ attributeCount += val.needsSegmentConversion
+ ? (val.renderData.geometry.attributes.POSITION.length - 3) * 2
+ : val.renderData.geometry.attributes.POSITION.length
+ })
const position = new Float64Array(attributeCount)
let offset = 0
for (let k = 0; k < this.renderViews.length; k++) {
const geometry = this.renderViews[k].renderData.geometry
- let points = null
+ if (!geometry.attributes) {
+ throw new Error(`Cannot build batch ${this.id}. Invalid geometry`)
+ }
+ let points: Array
/** We need to make sure the line geometry has a layout of :
* start(x,y,z), end(x,y,z), start(x,y,z), end(x,y,z)... etc
* Some geometries have that inherent form, some don't
@@ -239,7 +250,7 @@ export default class LineBatch implements Batch {
points[2 * i + 5] = geometry.attributes.POSITION[i + 5]
}
} else {
- points = geometry.attributes.POSITION
+ points = geometry.attributes.POSITION as number[]
}
position.set(points, offset)
@@ -259,7 +270,7 @@ export default class LineBatch implements Batch {
this.mesh.layers.set(ObjectLayers.STREAM_CONTENT_LINE)
}
- public getRenderView(index: number): NodeRenderView {
+ public getRenderView(index: number): NodeRenderView | null {
for (let k = 0; k < this.renderViews.length; k++) {
if (
index >= this.renderViews[k].batchStart &&
@@ -270,6 +281,7 @@ export default class LineBatch implements Batch {
return this.renderViews[k]
}
}
+ return null
}
public getMaterialAtIndex(index: number): Material {
@@ -336,7 +348,6 @@ export default class LineBatch implements Batch {
this.renderViews.length = 0
this.geometry.dispose()
this.batchMaterial.dispose()
- this.mesh = null
this.colorBuffer.length = 0
}
}
diff --git a/packages/viewer/src/modules/batching/MeshBatch.ts b/packages/viewer/src/modules/batching/MeshBatch.ts
index e22ae85188..6d4e847f17 100644
--- a/packages/viewer/src/modules/batching/MeshBatch.ts
+++ b/packages/viewer/src/modules/batching/MeshBatch.ts
@@ -9,37 +9,33 @@ import {
DynamicDrawUsage,
Sphere
} from 'three'
-import { GeometryType, BatchUpdateRange } from './Batch'
-import { DrawGroup } from './Batch'
import { PrimitiveBatch } from './PrimitiveBatch'
import SpeckleMesh, { TransformStorage } from '../objects/SpeckleMesh'
import Logger from 'js-logger'
import { DrawRanges } from './DrawRanges'
import { NodeRenderView } from '../tree/NodeRenderView'
+import { type BatchUpdateRange, type DrawGroup, GeometryType } from './Batch'
import { BatchObject } from './BatchObject'
import { Geometry } from '../converter/Geometry'
import { ObjectLayers } from '../../IViewer'
export class MeshBatch extends PrimitiveBatch {
- protected primitive: SpeckleMesh
+ protected primitive!: SpeckleMesh
protected transformStorage: TransformStorage
- private indexBuffer0: BufferAttribute
- private indexBuffer1: BufferAttribute
+ private indexBuffer0!: BufferAttribute
+ private indexBuffer1!: BufferAttribute
private indexBufferIndex = 0
- private drawRanges: DrawRanges = new DrawRanges()
+ protected drawRanges: DrawRanges = new DrawRanges()
get bounds(): Box3 {
return this.primitive.TAS.getBoundingBox(new Box3())
}
get minDrawCalls(): number {
- return [
- ...Array.from(
- new Set(this.primitive.geometry.groups.map((value) => value.materialIndex))
- )
- ].length
+ return [...Array.from(new Set(this.groups.map((value) => value.materialIndex)))]
+ .length
}
get triCount(): number {
@@ -100,6 +96,9 @@ export class MeshBatch extends PrimitiveBatch {
end: number,
value: number
): { minIndex: number; maxIndex: number } {
+ if (!this.primitive.geometry.index) {
+ throw new Error(`Invalid geometry on batch ${this.id}`)
+ }
const index = this.primitive.geometry.index.array as number[]
const data = this.gradientIndexBuffer.array as number[]
let minVertexIndex = Infinity
@@ -122,7 +121,7 @@ export class MeshBatch extends PrimitiveBatch {
}
}
- public setDrawRanges(...ranges: BatchUpdateRange[]) {
+ public setDrawRanges(ranges: BatchUpdateRange[]) {
// const current = this.groups.slice()
// const incoming = ranges.slice()
ranges.forEach((value: BatchUpdateRange) => {
@@ -130,24 +129,22 @@ export class MeshBatch extends PrimitiveBatch {
value.material = this.primitive.getCachedMaterial(value.material)
}
})
- const materials = ranges.map((val) => {
- return val.material
+ const materials: Array = ranges.map((val: BatchUpdateRange) => {
+ return val.material as Material
})
- const uniqueMaterials = [...Array.from(new Set(materials.map((value) => value)))]
+ const uniqueMaterials: Array = [
+ ...Array.from(new Set(materials.map((value: Material) => value)))
+ ]
for (let k = 0; k < uniqueMaterials.length; k++) {
if (!this.materials.includes(uniqueMaterials[k]))
this.materials.push(uniqueMaterials[k])
}
- this.primitive.geometry.groups = this.drawRanges.integrateRanges(
- this.groups,
- this.materials,
- ranges
- )
+ this.groups = this.drawRanges.integrateRanges(this.groups, this.materials, ranges)
let count = 0
- this.primitive.geometry.groups.forEach((value) => (count += value.count))
+ this.groups.forEach((value) => (count += value.count))
if (count !== this.getCount()) {
// Logger.error('Current -> ', current)
// Logger.error('Incoming -> ', incoming)
@@ -158,7 +155,7 @@ export class MeshBatch extends PrimitiveBatch {
}, ${this.getCount()}, ${this.getCount() - count}`
)
}
- this.setBatchBuffers(...ranges)
+ this.setBatchBuffers(ranges)
this.cleanMaterials()
if (this.drawCalls > this.minDrawCalls + 2) {
@@ -196,13 +193,22 @@ export class MeshBatch extends PrimitiveBatch {
let indicesCount = 0
let attributeCount = 0
for (let k = 0; k < this.renderViews.length; k++) {
- indicesCount += this.renderViews[k].renderData.geometry.attributes.INDEX.length
- attributeCount +=
- this.renderViews[k].renderData.geometry.attributes.POSITION.length
+ const ervee = this.renderViews[k]
+ /** Catering to typescript
+ * There is no unniverse where indices or positions are undefined at this point
+ */
+ if (
+ !ervee.renderData.geometry.attributes ||
+ !ervee.renderData.geometry.attributes.INDEX
+ ) {
+ throw new Error(`Cannot build batch ${this.id}. Invalid geometry, or indices`)
+ }
+ indicesCount += ervee.renderData.geometry.attributes.INDEX.length
+ attributeCount += ervee.renderData.geometry.attributes.POSITION.length
}
const hasVertexColors =
- this.renderViews[0].renderData.geometry.attributes.COLOR !== undefined
+ this.renderViews[0].renderData.geometry.attributes?.COLOR !== undefined
const indices = new Uint32Array(indicesCount)
const position = new Float64Array(attributeCount)
const color = new Float32Array(hasVertexColors ? attributeCount : 0)
@@ -215,6 +221,12 @@ export class MeshBatch extends PrimitiveBatch {
for (let k = 0; k < this.renderViews.length; k++) {
const geometry = this.renderViews[k].renderData.geometry
+ /** Catering to typescript
+ * There is no unniverse where indices or positions are undefined at this point
+ */
+ if (!geometry.attributes || !geometry.attributes.INDEX) {
+ throw new Error(`Cannot build batch ${this.id}. Invalid geometry, or indices`)
+ }
indices.set(
geometry.attributes.INDEX.map((val) => val + offset / 3),
arrayOffset
@@ -246,7 +258,7 @@ export class MeshBatch extends PrimitiveBatch {
indices,
position,
batchIndices,
- hasVertexColors ? color : null
+ hasVertexColors ? color : undefined
)
this.primitive = new SpeckleMesh(geometry)
@@ -310,12 +322,12 @@ export class MeshBatch extends PrimitiveBatch {
return geometry
}
- public getRenderView(index: number): NodeRenderView {
+ public getRenderView(index: number): NodeRenderView | null {
index
Logger.warn('Deprecated! Use BatchObject')
return null
}
- public getMaterialAtIndex(index: number): Material {
+ public getMaterialAtIndex(index: number): Material | null {
index
Logger.warn('Deprecated! Use BatchObject')
return null
diff --git a/packages/viewer/src/modules/batching/PointBatch.ts b/packages/viewer/src/modules/batching/PointBatch.ts
index ad54faa3f1..60da0ab4eb 100644
--- a/packages/viewer/src/modules/batching/PointBatch.ts
+++ b/packages/viewer/src/modules/batching/PointBatch.ts
@@ -9,17 +9,16 @@ import {
Uint16BufferAttribute,
DynamicDrawUsage
} from 'three'
-import { NodeRenderView } from '../..'
-import { GeometryType, BatchUpdateRange } from './Batch'
-import { DrawGroup } from './Batch'
+import { Geometry } from '../converter/Geometry'
+import { NodeRenderView } from '../tree/NodeRenderView'
+import { type BatchUpdateRange, type DrawGroup, GeometryType } from './Batch'
import { PrimitiveBatch } from './PrimitiveBatch'
import { DrawRanges } from './DrawRanges'
import Logger from 'js-logger'
-import { Geometry } from '../converter/Geometry'
import { ObjectLayers } from '../../IViewer'
export class PointBatch extends PrimitiveBatch {
- protected primitive: Points
+ protected primitive!: Points
protected drawRanges: DrawRanges = new DrawRanges()
public get geometryType(): GeometryType {
@@ -29,6 +28,8 @@ export class PointBatch extends PrimitiveBatch {
if (!this.primitive.geometry.boundingBox)
this.primitive.geometry.computeBoundingBox()
return this.primitive.geometry.boundingBox
+ ? this.primitive.geometry.boundingBox
+ : new Box3()
}
public get minDrawCalls(): number {
@@ -54,9 +55,9 @@ export class PointBatch extends PrimitiveBatch {
this.renderViews = renderViews
}
- public setDrawRanges(...ranges: BatchUpdateRange[]) {
- const materials = ranges.map((val) => {
- return val.material
+ public setDrawRanges(ranges: BatchUpdateRange[]) {
+ const materials: Array = ranges.map((val: BatchUpdateRange) => {
+ return val.material as Material
})
const uniqueMaterials = [...Array.from(new Set(materials.map((value) => value)))]
@@ -65,18 +66,14 @@ export class PointBatch extends PrimitiveBatch {
this.materials.push(uniqueMaterials[k])
}
- this.primitive.geometry.groups = this.drawRanges.integrateRanges(
- this.groups,
- this.materials,
- ranges
- )
+ this.groups = this.drawRanges.integrateRanges(this.groups, this.materials, ranges)
let count = 0
this.groups.forEach((value) => (count += value.count))
if (count !== this.getCount()) {
Logger.error(`Draw groups invalid on ${this.id}`)
}
- this.setBatchBuffers(...ranges)
+ this.setBatchBuffers(ranges)
this.cleanMaterials()
if (this.drawCalls > this.minDrawCalls + 2) {
@@ -108,10 +105,22 @@ export class PointBatch extends PrimitiveBatch {
}
protected getCurrentIndexBuffer(): BufferAttribute {
+ /** Catering to typescript
+ * There is no unniverse where the geometry is non-indexed. We're **explicitly** setting the index at creation time
+ */
+ if (!this.primitive.geometry.index) {
+ throw new Error(`Invalid index buffer for batch ${this.id}`)
+ }
return this.primitive.geometry.index
}
protected getNextIndexBuffer(): BufferAttribute {
+ /** Catering to typescript
+ * There is no unniverse where the geometry is non-indexed. We're **explicitly** setting the index at creation time
+ */
+ if (!this.primitive.geometry.index) {
+ throw new Error(`Invalid index buffer for batch ${this.id}`)
+ }
return new BufferAttribute(
(this.primitive.geometry.index.array as Uint16Array | Uint32Array).slice(),
this.primitive.geometry.index.itemSize
@@ -149,8 +158,14 @@ export class PointBatch extends PrimitiveBatch {
public buildBatch(): void {
let attributeCount = 0
for (let k = 0; k < this.renderViews.length; k++) {
- attributeCount +=
- this.renderViews[k].renderData.geometry.attributes.POSITION.length
+ const ervee = this.renderViews[k]
+ /** Catering to typescript
+ * There is no unniverse where indices or positions are undefined at this point
+ */
+ if (!ervee.renderData.geometry.attributes) {
+ throw new Error(`Cannot build batch ${this.id}. Invalid geometry, or indices`)
+ }
+ attributeCount += ervee.renderData.geometry.attributes.POSITION.length
}
const position = new Float64Array(attributeCount)
const color = new Float32Array(attributeCount).fill(1)
@@ -159,11 +174,14 @@ export class PointBatch extends PrimitiveBatch {
let indexOffset = 0
for (let k = 0; k < this.renderViews.length; k++) {
const geometry = this.renderViews[k].renderData.geometry
+ if (!geometry.attributes) {
+ throw new Error(`Cannot build batch ${this.id}. Invalid geometry, or indices`)
+ }
position.set(geometry.attributes.POSITION, offset)
if (geometry.attributes.COLOR) color.set(geometry.attributes.COLOR, offset)
index.set(
new Int32Array(geometry.attributes.POSITION.length / 3).map(
- (value, index) => index + indexOffset
+ (_value, index) => index + indexOffset
),
indexOffset
)
@@ -218,7 +236,7 @@ export class PointBatch extends PrimitiveBatch {
return geometry
}
- public getRenderView(index: number): NodeRenderView {
+ public getRenderView(index: number): NodeRenderView | null {
for (let k = 0; k < this.renderViews.length; k++) {
if (
index >= this.renderViews[k].batchStart &&
@@ -227,8 +245,9 @@ export class PointBatch extends PrimitiveBatch {
return this.renderViews[k]
}
}
+ return null
}
- public getMaterialAtIndex(index: number): Material {
+ public getMaterialAtIndex(index: number): Material | null {
for (let k = 0; k < this.renderViews.length; k++) {
if (
index >= this.renderViews[k].batchStart &&
@@ -248,5 +267,6 @@ export class PointBatch extends PrimitiveBatch {
return this.materials[group.materialIndex]
}
}
+ return null
}
}
diff --git a/packages/viewer/src/modules/batching/PrimitiveBatch.ts b/packages/viewer/src/modules/batching/PrimitiveBatch.ts
index 5250a8ebff..f1ed9088b8 100644
--- a/packages/viewer/src/modules/batching/PrimitiveBatch.ts
+++ b/packages/viewer/src/modules/batching/PrimitiveBatch.ts
@@ -2,33 +2,32 @@ import { Material, Object3D, BufferGeometry, BufferAttribute, Box3 } from 'three
import { NodeRenderView } from '../..'
import {
AllBatchUpdateRange,
- Batch,
- BatchUpdateRange,
+ type Batch,
+ type BatchUpdateRange,
GeometryType,
NoneBatchUpdateRange
} from './Batch'
-import { DrawGroup } from './Batch'
+import { type DrawGroup } from './Batch'
import Materials from '../materials/Materials'
import SpeckleStandardColoredMaterial from '../materials/SpeckleStandardColoredMaterial'
-import Logger from 'js-logger'
export abstract class Primitive<
TGeometry extends BufferGeometry = BufferGeometry,
TMaterial extends Material | Material[] = Material | Material[]
> extends Object3D {
- geometry: TGeometry
- material: TMaterial
- visible: boolean
+ geometry!: TGeometry
+ material!: TMaterial
+ visible!: boolean
}
export abstract class PrimitiveBatch implements Batch {
- public id: string
- public subtreeId: string
- public renderViews: NodeRenderView[]
- public batchMaterial: Material
+ public id!: string
+ public subtreeId!: string
+ public renderViews!: NodeRenderView[]
+ public batchMaterial!: Material
protected abstract primitive: Primitive
- protected gradientIndexBuffer: BufferAttribute
+ protected gradientIndexBuffer!: BufferAttribute
protected needsShuffle: boolean = false
abstract get geometryType(): GeometryType
@@ -43,7 +42,17 @@ export abstract class PrimitiveBatch implements Batch {
}
public get groups(): DrawGroup[] {
- return this.primitive.geometry.groups
+ /** We always write to geomtry.groups via the set accessor
+ * which takes a DrawGroup[], so geometry.groups will always
+ * be an array of DrawGroup.
+ * Not to mention that **all our draw groupd are DrawGroup because
+ * they always have a materialIndex defined** by design and convention!!!
+ */
+ return this.primitive.geometry.groups as DrawGroup[]
+ }
+
+ public set groups(value: DrawGroup[]) {
+ this.primitive.geometry.groups = value
}
public get renderObject(): Object3D {
@@ -59,22 +68,21 @@ export abstract class PrimitiveBatch implements Batch {
}
public getCount(): number {
- return this.primitive.geometry.index.count
+ return this.primitive.geometry.index?.count || 0
}
public setBatchMaterial(material: Material): void {
this.batchMaterial = material
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- public onUpdate(deltaTime: number) {
+ public onUpdate() {
if (this.needsShuffle) {
this.shuffleDrawGroups()
this.needsShuffle = false
}
}
- public setVisibleRange(...ranges: BatchUpdateRange[]) {
+ public setVisibleRange(ranges: BatchUpdateRange[]) {
/** Entire batch needs to NOT be drawn */
if (ranges.length === 1 && ranges[0] === NoneBatchUpdateRange) {
this.primitive.geometry.setDrawRange(0, 0)
@@ -97,17 +105,17 @@ export abstract class PrimitiveBatch implements Batch {
maxOffset = Math.max(maxOffset, range.offset)
})
+ const offset = ranges.find((val) => val.offset === maxOffset)
this.primitive.geometry.setDrawRange(
minOffset,
- maxOffset - minOffset + ranges.find((val) => val.offset === maxOffset).count
+ maxOffset - minOffset + (offset ? offset.count : 0)
)
this.primitive.visible = true
}
public getVisibleRange(): BatchUpdateRange {
/** Entire batch is visible */
- if (this.primitive.geometry.groups.length === 1 && this.primitive.visible)
- return AllBatchUpdateRange
+ if (this.groups.length === 1 && this.primitive.visible) return AllBatchUpdateRange
/** Entire batch is hidden */
if (!this.primitive.visible) return NoneBatchUpdateRange
/** Parts of the batch are visible */
@@ -120,6 +128,7 @@ export abstract class PrimitiveBatch implements Batch {
public getOpaque(): BatchUpdateRange {
/** If there is any transparent or hidden group return the update range up to it's offset */
const transparentOrHiddenGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
return (
Materials.isTransparent(this.materials[value.materialIndex]) ||
this.materials[value.materialIndex].visible === false
@@ -139,6 +148,7 @@ export abstract class PrimitiveBatch implements Batch {
public getDepth(): BatchUpdateRange {
/** If there is any transparent or hidden group return the update range up to it's offset */
const transparentOrHiddenGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
return (
Materials.isTransparent(this.materials[value.materialIndex]) ||
this.materials[value.materialIndex].visible === false ||
@@ -159,10 +169,12 @@ export abstract class PrimitiveBatch implements Batch {
public getTransparent(): BatchUpdateRange {
/** Look for a transparent group */
const transparentGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
return Materials.isTransparent(this.materials[value.materialIndex])
})
/** Look for a hidden group */
const hiddenGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
return this.materials[value.materialIndex].visible === false
})
/** If there is a transparent group return it's range */
@@ -185,6 +197,7 @@ export abstract class PrimitiveBatch implements Batch {
if (this.materials[0].stencilWrite === true) return AllBatchUpdateRange
}
const stencilGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
return this.materials[value.materialIndex].stencilWrite === true
})
if (stencilGroup) {
@@ -197,39 +210,44 @@ export abstract class PrimitiveBatch implements Batch {
return NoneBatchUpdateRange
}
- public setBatchBuffers(...range: BatchUpdateRange[]): void {
+ public setBatchBuffers(ranges: BatchUpdateRange[]): void {
let minGradientIndex = Infinity
let maxGradientIndex = 0
- for (let k = 0; k < range.length; k++) {
- if (range[k].materialOptions) {
- if (range[k].materialOptions.rampIndex !== undefined) {
- const start = range[k].offset
- const len = range[k].offset + range[k].count
+ for (let k = 0; k < ranges.length; k++) {
+ const range = ranges[k]
+ if (range.materialOptions) {
+ if (
+ range.materialOptions.rampIndex !== undefined &&
+ range.materialOptions.rampWidth !== undefined
+ ) {
+ const start = ranges[k].offset
+ const len = ranges[k].offset + ranges[k].count
/** The ramp indices specify the *begining* of each ramp color. When sampling with Nearest filter (since we don't want filtering)
* we'll always be sampling right at the edge between texels. Most GPUs will sample consistently, but some won't and we end up with
* a ton of artifacts. To avoid this, we are shifting the sampling indices so they're right on the center of each texel, so no inconsistent
* sampling can occur.
*/
- const shiftedIndex =
- range[k].materialOptions.rampIndex +
- 0.5 / range[k].materialOptions.rampWidth
- const minMaxIndices = this.updateGradientIndexBufferData(
- start,
- range[k].count === Infinity
- ? this.primitive.geometry.attributes['gradientIndex'].array.length
- : len,
- shiftedIndex
- )
- minGradientIndex = Math.min(minGradientIndex, minMaxIndices.minIndex)
- maxGradientIndex = Math.max(maxGradientIndex, minMaxIndices.maxIndex)
+ if (range.materialOptions.rampIndex && range.materialOptions.rampWidth) {
+ const shiftedIndex =
+ range.materialOptions.rampIndex + 0.5 / range.materialOptions.rampWidth
+ const minMaxIndices = this.updateGradientIndexBufferData(
+ start,
+ range.count === Infinity
+ ? this.primitive.geometry.attributes['gradientIndex'].array.length
+ : len,
+ shiftedIndex
+ )
+ minGradientIndex = Math.min(minGradientIndex, minMaxIndices.minIndex)
+ maxGradientIndex = Math.max(maxGradientIndex, minMaxIndices.maxIndex)
+ }
}
/** We need to update the texture here, because each batch uses it's own clone for any material we use on it
* because otherwise three.js won't properly update our custom uniforms
*/
- if (range[k].materialOptions.rampTexture !== undefined) {
- if (range[k].material instanceof SpeckleStandardColoredMaterial) {
- ;(range[k].material as SpeckleStandardColoredMaterial).setGradientTexture(
- range[k].materialOptions.rampTexture
+ if (range.materialOptions.rampTexture !== undefined) {
+ if (range.material instanceof SpeckleStandardColoredMaterial) {
+ ;(range.material as SpeckleStandardColoredMaterial).setGradientTexture(
+ range.materialOptions.rampTexture
)
}
}
@@ -242,7 +260,12 @@ export abstract class PrimitiveBatch implements Batch {
protected cleanMaterials() {
const materialsInUse = [
...Array.from(
- new Set(this.groups.map((value) => this.materials[value.materialIndex]))
+ new Set(
+ this.groups.map((value) => {
+ if (value.materialIndex === undefined) return undefined
+ return this.materials[value.materialIndex]
+ })
+ )
)
]
let k = 0
@@ -250,6 +273,7 @@ export abstract class PrimitiveBatch implements Batch {
if (!materialsInUse.includes(this.materials[k])) {
this.materials.splice(k, 1)
this.groups.forEach((value: DrawGroup) => {
+ if (value.materialIndex === undefined) return
if (value.materialIndex > k) value.materialIndex--
})
k = 0
@@ -264,13 +288,15 @@ export abstract class PrimitiveBatch implements Batch {
protected abstract shuffleMaterialOrder(a: DrawGroup, b: DrawGroup): number
private shuffleDrawGroups() {
- const groups = this.primitive.geometry.groups.slice()
+ const groups = this.groups.slice()
groups.sort(this.shuffleMaterialOrder.bind(this))
- const materialOrder = []
+ const materialOrder: Array = []
groups.reduce((previousValue, currentValue) => {
- if (previousValue.indexOf(currentValue.materialIndex) === -1) {
- previousValue.push(currentValue.materialIndex)
+ if (currentValue.materialIndex !== undefined) {
+ if (previousValue.indexOf(currentValue.materialIndex) === -1) {
+ previousValue.push(currentValue.materialIndex)
+ }
}
return previousValue
}, materialOrder)
@@ -332,7 +358,7 @@ export abstract class PrimitiveBatch implements Batch {
materialIndex: materialGroup[0].materialIndex
})
}
- this.primitive.geometry.groups = []
+ this.groups = []
for (let i = 0; i < newGroups.length; i++) {
this.primitive.geometry.addGroup(
newGroups[i].offset,
@@ -342,16 +368,22 @@ export abstract class PrimitiveBatch implements Batch {
}
this.primitive.geometry.setIndex(targetIBO)
- this.primitive.geometry.index.needsUpdate = true
+ /** Catering to typescript
+ * The line above literally makes sure the index is set. Absurd
+ */
+ if (this.primitive.geometry.index) this.primitive.geometry.index.needsUpdate = true
- const hiddenGroup = this.primitive.geometry.groups.find((value) => {
- return this.primitive.material[value.materialIndex].visible === false
+ const hiddenGroup = this.groups.find((value) => {
+ if (value.materialIndex === undefined) return false
+ return this.materials[value.materialIndex].visible === false
})
if (hiddenGroup) {
- this.setVisibleRange({
- offset: 0,
- count: hiddenGroup.start
- })
+ this.setVisibleRange([
+ {
+ offset: 0,
+ count: hiddenGroup.start
+ }
+ ])
}
// console.log('Final -> ', this.id, this.groups.slice())
}
@@ -372,7 +404,7 @@ export abstract class PrimitiveBatch implements Batch {
this.primitive.geometry.attributes['gradientIndex'].needsUpdate = true
}
- public abstract setDrawRanges(...ranges: BatchUpdateRange[])
+ public abstract setDrawRanges(ranges: BatchUpdateRange[]): void
public resetDrawRanges(): void {
this.primitive.visible = true
@@ -382,29 +414,21 @@ export abstract class PrimitiveBatch implements Batch {
}
public abstract buildBatch(): void
- public abstract getRenderView(index: number): NodeRenderView
- public abstract getMaterialAtIndex(index: number): Material
- public getMaterial(rv: NodeRenderView): Material {
- for (let k = 0; k < this.primitive.geometry.groups.length; k++) {
- try {
- if (
- rv.batchStart >= this.primitive.geometry.groups[k].start &&
- rv.batchEnd <=
- this.primitive.geometry.groups[k].start +
- this.primitive.geometry.groups[k].count
- ) {
- return this.materials[this.primitive.geometry.groups[k].materialIndex]
- }
- } catch (e) {
- Logger.error('Failed to get material')
+ public abstract getRenderView(index: number): NodeRenderView | null
+ public abstract getMaterialAtIndex(index: number): Material | null
+ public getMaterial(rv: NodeRenderView): Material | null {
+ for (let k = 0; k < this.groups.length; k++) {
+ const group = this.groups[k]
+ if (rv.batchStart >= group.start && rv.batchEnd <= group.start + group.count) {
+ return this.materials[group.materialIndex]
}
}
+ return null
}
public purge(): void {
this.renderViews.length = 0
this.primitive.geometry.dispose()
this.batchMaterial.dispose()
- this.primitive = null
}
}
diff --git a/packages/viewer/src/modules/batching/TextBatch.ts b/packages/viewer/src/modules/batching/TextBatch.ts
index e330ec58c7..c993632432 100644
--- a/packages/viewer/src/modules/batching/TextBatch.ts
+++ b/packages/viewer/src/modules/batching/TextBatch.ts
@@ -4,23 +4,23 @@ import { Box3, Material, Object3D, WebGLRenderer } from 'three'
import { NodeRenderView } from '../tree/NodeRenderView'
import {
AllBatchUpdateRange,
- Batch,
- BatchUpdateRange,
+ type Batch,
+ type BatchUpdateRange,
+ type DrawGroup,
GeometryType,
NoneBatchUpdateRange
} from './Batch'
import { SpeckleText } from '../objects/SpeckleText'
import { ObjectLayers } from '../../IViewer'
-import { DrawGroup } from './Batch'
import Materials from '../materials/Materials'
export default class TextBatch implements Batch {
public id: string
public subtreeId: string
public renderViews: NodeRenderView[]
- public batchMaterial: Material
- public mesh: SpeckleText
+ public batchMaterial!: Material
+ public mesh!: SpeckleText
public get bounds(): Box3 {
return new Box3().setFromObject(this.mesh)
@@ -68,7 +68,7 @@ export default class TextBatch implements Batch {
public getCount(): number {
return (
this.mesh.textMesh.geometry.index.count +
- this.mesh.backgroundMesh?.geometry.index.count
+ this.mesh.backgroundMesh?.geometry.index?.count
)
}
@@ -92,7 +92,8 @@ export default class TextBatch implements Batch {
renderer
}
- public setVisibleRange(...ranges: BatchUpdateRange[]) {
+ public setVisibleRange(ranges: BatchUpdateRange[]) {
+ ranges
// TO DO
}
@@ -116,11 +117,12 @@ export default class TextBatch implements Batch {
return NoneBatchUpdateRange
}
- public setBatchBuffers(...range: BatchUpdateRange[]): void {
+ public setBatchBuffers(range: BatchUpdateRange[]): void {
+ range
throw new Error('Method not implemented.')
}
- public setDrawRanges(...ranges: BatchUpdateRange[]) {
+ public setDrawRanges(ranges: BatchUpdateRange[]) {
this.mesh.textMesh.material = ranges[0].material
if (ranges[0].materialOptions && ranges[0].materialOptions.rampIndexColor) {
this.mesh.textMesh.material.color.copy(ranges[0].materialOptions.rampIndexColor)
@@ -130,11 +132,15 @@ export default class TextBatch implements Batch {
public resetDrawRanges() {
this.mesh.textMesh.material = this.batchMaterial
this.mesh.textMesh.visible = true
- // this.geometry.clearGroups()
- // this.geometry.setDrawRange(0, Infinity)
}
public async buildBatch() {
+ /** Catering to typescript
+ * There is no unniverse where there is no metadata
+ */
+ if (!this.renderViews[0].renderData.geometry.metaData) {
+ throw new Error(`Cannot build batch ${this.id}. Metadata`)
+ }
this.mesh = new SpeckleText(this.id, ObjectLayers.STREAM_CONTENT_TEXT)
this.mesh.matrixAutoUpdate = false
await this.mesh.update(
@@ -142,7 +148,8 @@ export default class TextBatch implements Batch {
this.renderViews[0].renderData.geometry.metaData
)
)
- this.mesh.matrix.copy(this.renderViews[0].renderData.geometry.bakeTransform)
+ if (this.renderViews[0].renderData.geometry.bakeTransform)
+ this.mesh.matrix.copy(this.renderViews[0].renderData.geometry.bakeTransform)
this.renderViews[0].setBatchData(
this.id,
0,
@@ -152,14 +159,17 @@ export default class TextBatch implements Batch {
}
public getRenderView(index: number): NodeRenderView {
+ index
return this.renderViews[0]
}
public getMaterialAtIndex(index: number): Material {
+ index
return this.batchMaterial
}
public getMaterial(rv: NodeRenderView): Material {
+ rv
return this.batchMaterial
}
@@ -167,6 +177,5 @@ export default class TextBatch implements Batch {
this.renderViews.length = 0
this.batchMaterial.dispose()
this.mesh.geometry.dispose()
- this.mesh = null
}
}
diff --git a/packages/viewer/src/modules/converter/Geometry.ts b/packages/viewer/src/modules/converter/Geometry.ts
index 907a8d219f..83ebd50f94 100644
--- a/packages/viewer/src/modules/converter/Geometry.ts
+++ b/packages/viewer/src/modules/converter/Geometry.ts
@@ -8,7 +8,7 @@ import {
Matrix4,
Vector3
} from 'three'
-import { SpeckleObject } from '../tree/DataTree'
+import { type SpeckleObject } from '../../IViewer'
export enum GeometryAttributes {
POSITION = 'POSITION',
@@ -20,9 +20,12 @@ export enum GeometryAttributes {
}
export interface GeometryData {
- attributes: Partial>
- bakeTransform: Matrix4
- transform: Matrix4
+ attributes:
+ | (Record &
+ Partial>)
+ | null
+ bakeTransform: Matrix4 | null
+ transform: Matrix4 | null
metaData?: SpeckleObject
instanced?: boolean
}
@@ -74,26 +77,33 @@ export class Geometry {
}
static mergeGeometryAttribute(
- attributes: number[][],
+ attributes: (number[] | undefined)[],
target: Float32Array | Float64Array
): ArrayLike {
let offset = 0
for (let k = 0; k < attributes.length; k++) {
- target.set(attributes[k], offset)
- offset += attributes[k].length
+ const attribute = attributes[k]
+ if (!attribute || !target) {
+ throw new Error('Cannot merge geometries. Indices or positions are undefined')
+ }
+ target.set(attribute, offset)
+ offset += attribute.length
}
return target
}
static mergeIndexAttribute(
- indexAttributes: number[][],
- positionAttributes: number[][]
+ indexAttributes: (number[] | undefined)[],
+ positionAttributes: (number[] | undefined)[]
): number[] {
let indexOffset = 0
const mergedIndex = []
for (let i = 0; i < indexAttributes.length; ++i) {
const index = indexAttributes[i]
+ if (!index || !positionAttributes) {
+ throw new Error('Cannot merge geometries. Indices or positions are undefined')
+ }
for (let j = 0; j < index.length; ++j) {
mergedIndex.push(index[j] + indexOffset)
@@ -113,46 +123,74 @@ export class Geometry {
} as GeometryData
for (let i = 0; i < geometries.length; i++) {
- if (geometries[i].bakeTransform)
+ /** Catering to typescript */
+ if (geometries[i].bakeTransform !== null)
Geometry.transformGeometryData(geometries[i], geometries[i].bakeTransform)
}
- if (sampleAttributes[GeometryAttributes.INDEX]) {
- const indexAttributes = geometries.map(
- (item) => item.attributes[GeometryAttributes.INDEX]
- )
- const positionAttributes = geometries.map(
- (item) => item.attributes[GeometryAttributes.POSITION]
+ if (sampleAttributes && sampleAttributes[GeometryAttributes.INDEX]) {
+ const indexAttributes: (number[] | undefined)[] = geometries.map(
+ (item: GeometryData) => {
+ /** Catering to typescript */
+ if (!item.attributes) return
+ return item.attributes[GeometryAttributes.INDEX]
+ }
)
- mergedGeometry.attributes[GeometryAttributes.INDEX] =
- Geometry.mergeIndexAttribute(indexAttributes, positionAttributes)
+ const positionAttributes: (number[] | undefined)[] = geometries.map((item) => {
+ /** Catering to typescript */
+ if (!item.attributes) return
+ return item.attributes[GeometryAttributes.POSITION]
+ })
+ /** o_0 Catering to typescript*/
+ if (mergedGeometry.attributes)
+ mergedGeometry.attributes[GeometryAttributes.INDEX] =
+ Geometry.mergeIndexAttribute(indexAttributes, positionAttributes)
}
for (const k in sampleAttributes) {
if (k !== GeometryAttributes.INDEX) {
- const attributes = geometries.map((item) => {
- return item.attributes[k]
+ const attributes: (number[] | undefined)[] = geometries.map((item) => {
+ /** Catering to typescript */
+ if (!item.attributes) return
+ return item.attributes[k as GeometryAttributes] as number[]
})
- mergedGeometry.attributes[k] = Geometry.mergeGeometryAttribute(
- attributes,
- k === GeometryAttributes.POSITION
- ? new Float64Array(attributes.reduce((prev, cur) => prev + cur.length, 0))
- : new Float32Array(attributes.reduce((prev, cur) => prev + cur.length, 0))
- )
+ /** Catering to typescript */
+ if (mergedGeometry.attributes)
+ mergedGeometry.attributes[k as GeometryAttributes] =
+ Geometry.mergeGeometryAttribute(
+ attributes,
+ k === GeometryAttributes.POSITION
+ ? new Float64Array(
+ attributes.reduce((prev, cur) => {
+ /** Catering to typescript */
+ if (!cur) return 0
+ return prev + cur.length
+ }, 0)
+ )
+ : new Float32Array(
+ attributes.reduce((prev, cur) => {
+ /** Catering to typescript */
+ if (!cur) return 0
+ return prev + cur.length
+ }, 0)
+ )
+ ) as number[]
}
}
geometries.forEach((geometry) => {
for (const k in geometry.attributes) {
- delete geometry.attributes[k]
+ delete geometry.attributes[k as GeometryAttributes]
}
})
return mergedGeometry
}
- public static transformGeometryData(geometryData: GeometryData, m: Matrix4) {
+ public static transformGeometryData(geometryData: GeometryData, m: Matrix4 | null) {
+ if (!geometryData.attributes) return
if (!geometryData.attributes.POSITION) return
+ if (!m) return
const e = m.elements
diff --git a/packages/viewer/src/modules/extensions/CameraController.ts b/packages/viewer/src/modules/extensions/CameraController.ts
index 0fb417d672..ddd3ed1a23 100644
--- a/packages/viewer/src/modules/extensions/CameraController.ts
+++ b/packages/viewer/src/modules/extensions/CameraController.ts
@@ -4,10 +4,10 @@ import { Extension } from './Extension'
import { SpeckleCameraControls } from '../objects/SpeckleCameraControls'
import { Box3, OrthographicCamera, PerspectiveCamera, Sphere, Vector3 } from 'three'
import { KeyboardKeyHold, HOLD_EVENT_TYPE } from 'hold-event'
-import { CameraProjection } from '../objects/SpeckleCamera'
-import { CameraEvent, SpeckleCamera } from '../objects/SpeckleCamera'
+import { CameraProjection, type CameraEventPayload } from '../objects/SpeckleCamera'
+import { CameraEvent, type SpeckleCamera } from '../objects/SpeckleCamera'
import Logger from 'js-logger'
-import { IViewer, SpeckleView } from '../../IViewer'
+import type { IViewer, SpeckleView } from '../../IViewer'
export type CanonicalView =
| 'front'
@@ -33,10 +33,10 @@ export type PolarView = {
}
export class CameraController extends Extension implements SpeckleCamera {
- protected _renderingCamera: PerspectiveCamera | OrthographicCamera = null
- protected perspectiveCamera: PerspectiveCamera = null
- protected orthographicCamera: OrthographicCamera = null
- protected _controls: SpeckleCameraControls = null
+ protected _renderingCamera!: PerspectiveCamera | OrthographicCamera
+ protected perspectiveCamera: PerspectiveCamera
+ protected orthographicCamera: OrthographicCamera
+ protected _controls: SpeckleCameraControls
get renderingCamera(): PerspectiveCamera | OrthographicCamera {
return this._renderingCamera
@@ -46,15 +46,15 @@ export class CameraController extends Extension implements SpeckleCamera {
this._renderingCamera = value
}
- public get enabled() {
+ public get enabled(): boolean {
return this._controls.enabled
}
- public set enabled(val) {
+ public set enabled(val: boolean) {
this._controls.enabled = val
}
- public get fieldOfView() {
+ public get fieldOfView(): number {
return this.perspectiveCamera.fov
}
@@ -63,11 +63,11 @@ export class CameraController extends Extension implements SpeckleCamera {
this.perspectiveCamera.updateProjectionMatrix()
}
- public get aspect() {
+ public get aspect(): number {
return this.perspectiveCamera.aspect
}
- public get controls() {
+ public get controls(): SpeckleCameraControls {
return this._controls
}
@@ -131,14 +131,33 @@ export class CameraController extends Extension implements SpeckleCamera {
this.viewer.getRenderer().speckleCamera = this
}
- setCameraView(objectIds: string[], transition: boolean, fit?: number): void
+ public on(
+ eventType: T,
+ listener: (arg: CameraEventPayload[T]) => void
+ ): void {
+ super.on(eventType, listener)
+ }
+
+ setCameraView(
+ objectIds: string[] | undefined,
+ transition: boolean | undefined,
+ fit?: number
+ ): void
setCameraView(
view: CanonicalView | SpeckleView | InlineView | PolarView,
- transition: boolean
+ transition: boolean | undefined,
+ fit?: number
): void
- setCameraView(bounds: Box3, transition: boolean): void
+ setCameraView(bounds: Box3, transition: boolean | undefined, fit?: number): void
setCameraView(
- arg0: string[] | CanonicalView | SpeckleView | InlineView | PolarView | Box3,
+ arg0:
+ | string[]
+ | CanonicalView
+ | SpeckleView
+ | InlineView
+ | PolarView
+ | Box3
+ | undefined,
arg1 = true,
arg2 = 1.2
): void {
@@ -194,19 +213,19 @@ export class CameraController extends Extension implements SpeckleCamera {
this.viewer.requestRender()
}
- public setOrthoCameraOn() {
+ public setOrthoCameraOn(): void {
if (this._renderingCamera === this.orthographicCamera) return
this.renderingCamera = this.orthographicCamera
this.setupOrthoCamera()
this.viewer.requestRender()
}
- public toggleCameras() {
+ public toggleCameras(): void {
if (this._renderingCamera === this.perspectiveCamera) this.setOrthoCameraOn()
else this.setPerspectiveCameraOn()
}
- protected setupOrthoCamera() {
+ protected setupOrthoCamera(): void {
this._controls.mouseButtons.wheel = CameraControls.ACTION.ZOOM
const lineOfSight = new Vector3()
@@ -252,11 +271,11 @@ export class CameraController extends Extension implements SpeckleCamera {
this.emit(CameraEvent.ProjectionChanged, CameraProjection.PERSPECTIVE)
}
- public disableRotations() {
+ public disableRotations(): void {
this._controls.mouseButtons.left = CameraControls.ACTION.TRUCK
}
- public enableRotations() {
+ public enableRotations(): void {
this._controls.mouseButtons.left = CameraControls.ACTION.ROTATE
}
@@ -269,7 +288,7 @@ export class CameraController extends Extension implements SpeckleCamera {
const dKey = new KeyboardKeyHold(KEYCODE.D, 16.666)
const isTruckingGroup = new Array(4)
- const setTrucking = (index, value) => {
+ const setTrucking = (index: number, value: boolean) => {
isTruckingGroup[index] = value
if (isTruckingGroup.every((element) => element === false)) {
this._controls.isTrucking = false
@@ -277,21 +296,15 @@ export class CameraController extends Extension implements SpeckleCamera {
} else this._controls.isTrucking = true
}
- aKey.addEventListener(
- HOLD_EVENT_TYPE.HOLD_START,
- function () {
- this.controls.dispatchEvent({ type: 'controlstart' })
- }.bind(this)
- )
- aKey.addEventListener(
- 'holding',
- function (event) {
- if (this.viewer.mouseOverRenderer === false) return
- setTrucking(0, true)
- this.controls.truck(-0.01 * event.deltaTime, 0, false)
- return
- }.bind(this)
- )
+ aKey.addEventListener(HOLD_EVENT_TYPE.HOLD_START, () => {
+ this.controls['dispatchEvent']({ type: 'controlstart' })
+ })
+ aKey.addEventListener('holding', (event) => {
+ if (!event) return
+ setTrucking(0, true)
+ this.controls.truck(-0.01 * event.deltaTime, 0, false)
+ return
+ })
aKey.addEventListener(
HOLD_EVENT_TYPE.HOLD_END,
function () {
@@ -299,21 +312,15 @@ export class CameraController extends Extension implements SpeckleCamera {
}.bind(this)
)
- dKey.addEventListener(
- HOLD_EVENT_TYPE.HOLD_START,
- function () {
- this.controls.dispatchEvent({ type: 'controlstart' })
- }.bind(this)
- )
- dKey.addEventListener(
- 'holding',
- function (event) {
- if (this.viewer.mouseOverRenderer === false) return
- setTrucking(1, true)
- this.controls.truck(0.01 * event.deltaTime, 0, false)
- return
- }.bind(this)
- )
+ dKey.addEventListener(HOLD_EVENT_TYPE.HOLD_START, () => {
+ this.controls['dispatchEvent']({ type: 'controlstart' })
+ })
+ dKey.addEventListener('holding', (event) => {
+ if (!event) return
+ setTrucking(1, true)
+ this.controls.truck(0.01 * event.deltaTime, 0, false)
+ return
+ })
dKey.addEventListener(
HOLD_EVENT_TYPE.HOLD_END,
function () {
@@ -321,21 +328,15 @@ export class CameraController extends Extension implements SpeckleCamera {
}.bind(this)
)
- wKey.addEventListener(
- HOLD_EVENT_TYPE.HOLD_START,
- function () {
- this.controls.dispatchEvent({ type: 'controlstart' })
- }.bind(this)
- )
- wKey.addEventListener(
- 'holding',
- function (event) {
- if (this.viewer.mouseOverRenderer === false) return
- setTrucking(2, true)
- this.controls.forward(0.01 * event.deltaTime, false)
- return
- }.bind(this)
- )
+ wKey.addEventListener(HOLD_EVENT_TYPE.HOLD_START, () => {
+ this.controls['dispatchEvent']({ type: 'controlstart' })
+ })
+ wKey.addEventListener('holding', (event) => {
+ if (!event) return
+ setTrucking(2, true)
+ this.controls.forward(0.01 * event.deltaTime, false)
+ return
+ })
wKey.addEventListener(
HOLD_EVENT_TYPE.HOLD_END,
function () {
@@ -343,21 +344,15 @@ export class CameraController extends Extension implements SpeckleCamera {
}.bind(this)
)
- sKey.addEventListener(
- HOLD_EVENT_TYPE.HOLD_START,
- function () {
- this.controls.dispatchEvent({ type: 'controlstart' })
- }.bind(this)
- )
- sKey.addEventListener(
- 'holding',
- function (event) {
- if (this.viewer.mouseOverRenderer === false) return
- setTrucking(3, true)
- this.controls.forward(-0.01 * event.deltaTime, false)
- return
- }.bind(this)
- )
+ sKey.addEventListener(HOLD_EVENT_TYPE.HOLD_START, () => {
+ this.controls['dispatchEvent']({ type: 'controlstart' })
+ })
+ sKey.addEventListener('holding', (event) => {
+ if (!event) return
+ setTrucking(3, true)
+ this.controls.forward(-0.01 * event.deltaTime, false)
+ return
+ })
sKey.addEventListener(
HOLD_EVENT_TYPE.HOLD_END,
function () {
@@ -418,7 +413,7 @@ export class CameraController extends Extension implements SpeckleCamera {
// this.viewer.controls.setBoundary( box )
}
- private zoomToBox(box, fit = 1.2, transition = true) {
+ private zoomToBox(box: Box3, fit = 1.2, transition = true) {
if (box.max.x === Infinity || box.max.x === -Infinity) {
box = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1))
}
@@ -472,8 +467,10 @@ export class CameraController extends Extension implements SpeckleCamera {
)
}
- private isBox3(view: unknown): view is Box3 {
- return view['isBox3']
+ private isBox3(
+ view: CanonicalView | SpeckleView | InlineView | PolarView | Box3
+ ): view is Box3 {
+ return view instanceof Box3
}
protected setView(
@@ -496,12 +493,12 @@ export class CameraController extends Extension implements SpeckleCamera {
private setViewSpeckle(view: SpeckleView, transition = true) {
this._controls.setLookAt(
- view.view.origin['x'],
- view.view.origin['y'],
- view.view.origin['z'],
- view.view.target['x'],
- view.view.target['y'],
- view.view.target['z'],
+ view.view.origin.x,
+ view.view.origin.y,
+ view.view.origin.z,
+ view.view.target.x,
+ view.view.target.y,
+ view.view.target.z,
transition
)
this.enableRotations()
diff --git a/packages/viewer/src/modules/extensions/DiffExtension.ts b/packages/viewer/src/modules/extensions/DiffExtension.ts
index 46c3b26f07..84d0f8bb2d 100644
--- a/packages/viewer/src/modules/extensions/DiffExtension.ts
+++ b/packages/viewer/src/modules/extensions/DiffExtension.ts
@@ -1,20 +1,18 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
-import { Color, DoubleSide, FrontSide } from 'three'
-import { TreeNode, WorldTree } from '../tree/WorldTree'
+import { Color, DoubleSide, FrontSide, Material } from 'three'
+import { type TreeNode, WorldTree } from '../tree/WorldTree'
import Logger from 'js-logger'
-import _, { omit } from 'underscore'
+import { groupBy } from 'lodash-es'
import { GeometryType } from '../batching/Batch'
import SpeckleLineMaterial from '../materials/SpeckleLineMaterial'
import SpecklePointMaterial from '../materials/SpecklePointMaterial'
import SpeckleStandardMaterial from '../materials/SpeckleStandardMaterial'
import { NodeRenderView } from '../tree/NodeRenderView'
-import { IViewer } from '../../IViewer'
+import { type IViewer } from '../../IViewer'
import { Extension } from './Extension'
import { SpeckleTypeAllRenderables } from '../loaders/GeometryConverter'
import { SpeckleLoader } from '../loaders/Speckle/SpeckleLoader'
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-type SpeckleObject = Record
type SpeckleMaterialType =
| SpeckleStandardMaterial
| SpecklePointMaterial
@@ -26,10 +24,10 @@ export enum VisualDiffMode {
}
export interface DiffResult {
- unchanged: Array
- added: Array
- removed: Array
- modified: Array>
+ unchanged: Array
+ added: Array
+ removed: Array
+ modified: Array>
}
interface VisualDiffResult {
@@ -49,24 +47,30 @@ export class DiffExtension extends Extension {
this._enabled = value
}
- protected tree: WorldTree = null
- private addedMaterialMesh: SpeckleStandardMaterial = null
- private changedNewMaterialMesh: SpeckleStandardMaterial = null
- private changedOldMaterialMesh: SpeckleStandardMaterial = null
- private removedMaterialMesh: SpeckleStandardMaterial = null
+ protected tree: WorldTree
+ private addedMaterialMesh: SpeckleStandardMaterial
+ private changedNewMaterialMesh: SpeckleStandardMaterial
+ private changedOldMaterialMesh: SpeckleStandardMaterial
+ private removedMaterialMesh: SpeckleStandardMaterial
- private addedMaterialPoint: SpecklePointMaterial = null
- private changedNewMaterialPoint: SpecklePointMaterial = null
- private changedOldMaterialPoint: SpecklePointMaterial = null
- private removedMaterialPoint: SpecklePointMaterial = null
+ private addedMaterialPoint: SpecklePointMaterial
+ private changedNewMaterialPoint: SpecklePointMaterial
+ private changedOldMaterialPoint: SpecklePointMaterial
+ private removedMaterialPoint: SpecklePointMaterial
private addedMaterials: Array = []
private changedOldMaterials: Array = []
private changedNewMaterials: Array = []
private removedMaterials: Array = []
- private _materialGroups = null
- private _visualDiff: VisualDiffResult = null
+ private _materialGroups:
+ | {
+ rvs: NodeRenderView[]
+ material: SpeckleMaterialType
+ }[]
+ | null
+
+ private _visualDiff!: VisualDiffResult
private _diffTime = -1
private _diffMode: VisualDiffMode = VisualDiffMode.COLORED
@@ -237,7 +241,7 @@ export class DiffExtension extends Extension {
}
/** Currently, the diff does not store the existing materials. We can do that if we need to */
- public async undiff() {
+ public async undiff(): Promise {
const pipelineOptions = this.viewer.getRenderer().pipelineOptions
pipelineOptions.depthSide = DoubleSide
this.viewer.getRenderer().pipelineOptions = pipelineOptions
@@ -253,12 +257,6 @@ export class DiffExtension extends Extension {
await Promise.all(unloadPromises)
}
- private intersection(o1, o2) {
- const [k1, k2] = [Object.keys(o1), Object.keys(o2)]
- const [first, next] = k1.length > k2.length ? [k2, o1] : [k1, o2]
- return first.filter((k) => k in next)
- }
-
private buildIdMaps(
rvs: Array,
idMap: { [id: string]: { node: TreeNode; applicationId: string } },
@@ -288,93 +286,7 @@ export class DiffExtension extends Extension {
return Promise.resolve(diffResult)
}
- private diffBoolean(urlA: string, urlB: string): Promise {
- const start = performance.now()
- const diffResult: DiffResult = {
- unchanged: [],
- added: [],
- removed: [],
- modified: []
- }
-
- const renderTreeA = this.tree.getRenderTree(urlA)
- const renderTreeB = this.tree.getRenderTree(urlB)
- let rvsA = renderTreeA.getRenderableNodes(...SpeckleTypeAllRenderables)
- let rvsB = renderTreeB.getRenderableNodes(...SpeckleTypeAllRenderables)
-
- rvsA = rvsA.map((value) => {
- return renderTreeA.getAtomicParent(value)
- })
-
- rvsB = rvsB.map((value) => {
- return renderTreeB.getAtomicParent(value)
- })
-
- rvsA = [...Array.from(new Set(rvsA))]
- rvsB = [...Array.from(new Set(rvsB))]
-
- const idMapA = {}
- const appIdMapA = {}
- this.buildIdMaps(rvsA, idMapA, appIdMapA)
-
- const idMapB = {}
- const appIdMapB = {}
- this.buildIdMaps(rvsB, idMapB, appIdMapB)
-
- /** Get the ids which are common between the two maps. This will be objects
- * which have not changed
- */
- const unchanged: Array = this.intersection(idMapA, idMapB)
- /** We remove the unchanged objects from B and end up with changed + added */
- const addedModified = _.omit(idMapB, unchanged)
- /** We remove the unchanged objects from A and end up with changed + removed */
- const removedModified = _.omit(idMapA, unchanged)
- /** We remove the changed objects from B. An object from B is changed if
- * it's application ID exists in A
- */
- const added = _.omit(addedModified, function (value, key, object) {
- return value.applicationId && appIdMapA[value.applicationId] !== undefined
- })
- /** We remove the changed objects from A. An object from A is changed if
- * it's application ID exists in B
- */
- const removed = _.omit(removedModified, function (value, key, object) {
- return value.applicationId && appIdMapB[value.applicationId] !== undefined
- })
- /** We remove the removed objects from A, leaving us only changed objects */
- const modifiedRemoved = _.omit(removedModified, Object.keys(removed))
- /** We remove the removed objects from B, leaving us only changed objects */
- const modifiedAdded = _.omit(addedModified, Object.keys(added))
-
- /** We fill the arrays from here on out */
- const modifiedOld = Object.values(modifiedRemoved).map(
- (value: { node: TreeNode }) => value.node
- )
- const modifiedNew = Object.values(modifiedAdded).map(
- (value: { node: TreeNode }) => value.node
- )
- diffResult.unchanged.push(...unchanged.map((value) => idMapA[value].node))
- diffResult.unchanged.push(...unchanged.map((value) => idMapB[value].node))
- diffResult.removed.push(
- ...Object.values(removed).map((value: { node: TreeNode }) => value.node)
- )
- diffResult.added.push(
- ...Object.values(added).map((value: { node: TreeNode }) => value.node)
- )
-
- modifiedOld.forEach((value, index) => {
- value
- diffResult.modified.push([modifiedOld[index], modifiedNew[index]])
- })
- console.warn('Boolean Time -> ', performance.now() - start)
- return Promise.resolve(diffResult)
- }
-
private diffIterative(urlA: string, urlB: string): Promise {
- const start = performance.now()
- const modifiedNew: Array = []
- const modifiedOld: Array = []
-
const diffResult: DiffResult = {
unchanged: [],
added: [],
@@ -384,8 +296,18 @@ export class DiffExtension extends Extension {
const renderTreeA = this.tree.getRenderTree(urlA)
const renderTreeB = this.tree.getRenderTree(urlB)
- let rvsA = renderTreeA.getRenderableNodes(...SpeckleTypeAllRenderables)
- let rvsB = renderTreeB.getRenderableNodes(...SpeckleTypeAllRenderables)
+ if (!renderTreeA) {
+ return Promise.reject(
+ `Could not make diff. Resource ${urlA} could not be fetched`
+ )
+ }
+ if (!renderTreeB) {
+ return Promise.reject(
+ `Could not make diff. Resource ${urlB} could not be fetched`
+ )
+ }
+ let rvsA: TreeNode[] = renderTreeA.getRenderableNodes(...SpeckleTypeAllRenderables)
+ let rvsB: TreeNode[] = renderTreeB.getRenderableNodes(...SpeckleTypeAllRenderables)
rvsA = rvsA.map((value) => {
return renderTreeA.getAtomicParent(value)
@@ -398,12 +320,12 @@ export class DiffExtension extends Extension {
rvsA = [...Array.from(new Set(rvsA))]
rvsB = [...Array.from(new Set(rvsB))]
- const idMapA = {}
- const appIdMapA = {}
+ const idMapA: { [id: string]: { node: TreeNode; applicationId: string } } = {}
+ const appIdMapA: { [id: string]: TreeNode } = {}
this.buildIdMaps(rvsA, idMapA, appIdMapA)
- const idMapB = {}
- const appIdMapB = {}
+ const idMapB: { [id: string]: { node: TreeNode; applicationId: string } } = {}
+ const appIdMapB: { [id: string]: TreeNode } = {}
this.buildIdMaps(rvsB, idMapB, appIdMapB)
for (let k = 0; k < rvsB.length; k++) {
@@ -448,28 +370,27 @@ export class DiffExtension extends Extension {
}
}
- console.warn('Interative Time -> ', performance.now() - start)
-
return Promise.resolve(diffResult)
}
- public updateVisualDiff(time?: number, mode?: VisualDiffMode) {
- if (
- (mode !== undefined && mode !== this._diffMode) ||
- this._materialGroups === null
- ) {
+ public updateVisualDiff(time?: number, mode?: VisualDiffMode): void {
+ if ((mode !== undefined && mode !== this._diffMode) || !this._materialGroups) {
this.resetMaterialGroups()
- this.buildMaterialGroups(mode)
- this._diffMode = mode
+ /** Catering to typescript */
+ if (mode !== undefined) {
+ this.buildMaterialGroups(mode)
+ this._diffMode = mode
+ }
}
if (time !== undefined && time !== this._diffTime) {
this.setDiffTime(time)
this._diffTime = time
}
- this._materialGroups.forEach((value) => {
- this.viewer.getRenderer().setMaterial(value.rvs, value.material)
- })
+ if (this._materialGroups)
+ this._materialGroups.forEach((value) => {
+ this.viewer.getRenderer().setMaterial(value.rvs, value.material)
+ })
this.viewer.requestRender()
}
@@ -479,7 +400,9 @@ export class DiffExtension extends Extension {
this.addedMaterials.forEach((mat) => {
mat.opacity =
- mat['clampOpacity'] !== undefined ? Math.min(from, mat['clampOpacity']) : from
+ (mat as never)['clampOpacity'] !== undefined
+ ? Math.min(from, (mat as never)['clampOpacity'])
+ : from
mat.depthWrite = from < 0.5 ? false : true
mat.transparent = mat.opacity < 1
mat.needsCopy = true
@@ -487,7 +410,9 @@ export class DiffExtension extends Extension {
this.changedOldMaterials.forEach((mat) => {
mat.opacity =
- mat['clampOpacity'] !== undefined ? Math.min(to, mat['clampOpacity']) : to
+ (mat as never)['clampOpacity'] !== undefined
+ ? Math.min(to, (mat as never)['clampOpacity'])
+ : to
mat.depthWrite = to < 0.5 ? false : true
mat.transparent = mat.opacity < 1
mat.needsCopy = true
@@ -495,7 +420,9 @@ export class DiffExtension extends Extension {
this.changedNewMaterials.forEach((mat) => {
mat.opacity =
- mat['clampOpacity'] !== undefined ? Math.min(from, mat['clampOpacity']) : from
+ (mat as never)['clampOpacity'] !== undefined
+ ? Math.min(from, (mat as never)['clampOpacity'])
+ : from
mat.depthWrite = from < 0.5 ? false : true
mat.transparent = mat.opacity < 1
mat.needsCopy = true
@@ -503,7 +430,9 @@ export class DiffExtension extends Extension {
this.removedMaterials.forEach((mat) => {
mat.opacity =
- mat['clampOpacity'] !== undefined ? Math.min(to, mat['clampOpacity']) : to
+ (mat as never)['clampOpacity'] !== undefined
+ ? Math.min(to, (mat as never)['clampOpacity'])
+ : to
mat.depthWrite = to < 0.5 ? false : true
mat.transparent = mat.opacity < 1
mat.needsCopy = true
@@ -535,31 +464,25 @@ export class DiffExtension extends Extension {
const renderTree = this.tree.getRenderTree()
const addedRvs = diffResult.added.flatMap((value) => {
- return renderTree.getRenderViewsForNode(value as TreeNode, value as TreeNode)
+ return renderTree.getRenderViewsForNode(value as TreeNode)
})
const removedRvs = diffResult.removed.flatMap((value) => {
- return renderTree.getRenderViewsForNode(value as TreeNode, value as TreeNode)
+ return renderTree.getRenderViewsForNode(value as TreeNode)
})
const unchangedRvs = diffResult.unchanged.flatMap((value) => {
- return renderTree.getRenderViewsForNode(value as TreeNode, value as TreeNode)
+ return renderTree.getRenderViewsForNode(value as TreeNode)
})
const modifiedOldRvs = diffResult.modified
.flatMap((value) => {
- return renderTree.getRenderViewsForNode(
- value[0] as TreeNode,
- value[0] as TreeNode
- )
+ return renderTree.getRenderViewsForNode(value[0] as TreeNode)
})
.filter((value) => {
return !unchangedRvs.includes(value) && !removedRvs.includes(value)
})
const modifiedNewRvs = diffResult.modified
.flatMap((value) => {
- return renderTree.getRenderViewsForNode(
- value[1] as TreeNode,
- value[1] as TreeNode
- )
+ return renderTree.getRenderViewsForNode(value[1] as TreeNode)
})
.filter((value) => {
return !unchangedRvs.includes(value) && !addedRvs.includes(value)
@@ -665,23 +588,42 @@ export class DiffExtension extends Extension {
const changedOld = this.getBatchesSubgroups(visualDiffResult.modifiedOld)
const changedNew = this.getBatchesSubgroups(visualDiffResult.modifiedNew)
const removed = this.getBatchesSubgroups(visualDiffResult.removed)
- this.addedMaterials = added.map((value) => value.material)
- this.changedOldMaterials = changedOld.map((value) => value.material)
- this.changedNewMaterials = changedNew.map((value) => value.material)
- this.removedMaterials = removed.map((value) => value.material)
+ this.addedMaterials = added.map(
+ (value: { rvs: NodeRenderView[]; material: SpeckleMaterialType }) =>
+ value.material
+ )
+ this.changedOldMaterials = changedOld.map(
+ (value: { rvs: NodeRenderView[]; material: SpeckleMaterialType }) =>
+ value.material
+ )
+ this.changedNewMaterials = changedNew.map(
+ (value: { rvs: NodeRenderView[]; material: SpeckleMaterialType }) =>
+ value.material
+ )
+ this.removedMaterials = removed.map(
+ (value: { rvs: NodeRenderView[]; material: SpeckleMaterialType }) =>
+ value.material
+ )
return [...added, ...changedOld, ...changedNew, ...removed]
}
- private getBatchesSubgroups(subgroup: Array) {
- const groupBatches = _.groupBy(subgroup, 'batchId')
+ private getBatchesSubgroups(subgroup: Array): {
+ rvs: NodeRenderView[]
+ material: SpeckleMaterialType
+ }[] {
+ const groupBatches = groupBy(subgroup, 'batchId')
- const materialGroup = []
+ const materialGroup: {
+ rvs: NodeRenderView[]
+ material: SpeckleMaterialType
+ }[] = []
for (const k in groupBatches) {
- const matClone = this.viewer
- .getRenderer()
- .getBatchMaterial(groupBatches[k][0])
- .clone()
- matClone['clampOpacity'] = matClone.opacity
+ const matClone: SpeckleMaterialType = (
+ this.viewer.getRenderer().getBatchMaterial(groupBatches[k][0]) as Material
+ ).clone() as SpeckleMaterialType
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ ;(matClone as any)['clampOpacity'] = matClone.opacity
matClone.opacity = 0.5
matClone.transparent = true
materialGroup.push({
@@ -692,4 +634,114 @@ export class DiffExtension extends Extension {
return materialGroup
}
+
+ /** Keeping this for reference */
+ // private intersection(o1: object, o2: object) {
+ // const [k1, k2] = [Object.keys(o1), Object.keys(o2)]
+ // const [first, next] = k1.length > k2.length ? [k2, o1] : [k1, o2]
+ // return first.filter((k) => k in next)
+ // }
+
+ // private diffBoolean(urlA: string, urlB: string): Promise {
+ // const diffResult: DiffResult = {
+ // unchanged: [],
+ // added: [],
+ // removed: [],
+ // modified: []
+ // }
+
+ // const renderTreeA = this.tree!.getRenderTree(urlA)
+ // const renderTreeB = this.tree!.getRenderTree(urlB)
+ // if (!renderTreeA) {
+ // return Promise.reject(
+ // `Could not make diff. Resource ${urlA} could not be fetched`
+ // )
+ // }
+ // if (!renderTreeB) {
+ // return Promise.reject(
+ // `Could not make diff. Resource ${urlB} could not be fetched`
+ // )
+ // }
+ // let rvsA: TreeNode[] = renderTreeA.getRenderableNodes(...SpeckleTypeAllRenderables)
+ // let rvsB: TreeNode[] = renderTreeB.getRenderableNodes(...SpeckleTypeAllRenderables)
+
+ // rvsA = rvsA.map((value: TreeNode) => {
+ // return renderTreeA.getAtomicParent(value)
+ // })
+
+ // rvsB = rvsB.map((value) => {
+ // return renderTreeB.getAtomicParent(value)
+ // })
+
+ // rvsA = [...Array.from(new Set(rvsA))]
+ // rvsB = [...Array.from(new Set(rvsB))]
+
+ // const idMapA: { [id: string]: { node: TreeNode; applicationId: string } } = {}
+ // const appIdMapA: { [id: string]: TreeNode } = {}
+ // this.buildIdMaps(rvsA, idMapA, appIdMapA)
+
+ // const idMapB: { [id: string]: { node: TreeNode; applicationId: string } } = {}
+ // const appIdMapB: { [id: string]: TreeNode } = {}
+ // this.buildIdMaps(rvsB, idMapB, appIdMapB)
+
+ // /** Get the ids which are common between the two maps. This will be objects
+ // * which have not changed
+ // */
+ // const unchanged: Array = this.intersection(idMapA, idMapB)
+ // /** We remove the unchanged objects from B and end up with changed + added */
+ // const addedModified = _.omit(idMapB, unchanged)
+ // /** We remove the unchanged objects from A and end up with changed + removed */
+ // const removedModified = _.omit(idMapA, unchanged)
+ // /** We remove the changed objects from B. An object from B is changed if
+ // * it's application ID exists in A
+ // */
+ // const added = _.omit(addedModified, function (value: { applicationId: string }) {
+ // return (
+ // value.applicationId !== undefined &&
+ // appIdMapA[value.applicationId] !== undefined
+ // )
+ // })
+ // /** We remove the changed objects from A. An object from A is changed if
+ // * it's application ID exists in B
+ // */
+ // const removed = _.omit(
+ // removedModified,
+ // function (value: { applicationId: string }) {
+ // return (
+ // value.applicationId !== undefined &&
+ // appIdMapB[value.applicationId] !== undefined
+ // )
+ // }
+ // )
+ // /** We remove the removed objects from A, leaving us only changed objects */
+ // const modifiedRemoved = _.omit(removedModified, Object.keys(removed))
+ // /** We remove the removed objects from B, leaving us only changed objects */
+ // const modifiedAdded = _.omit(addedModified, Object.keys(added))
+
+ // /** We fill the arrays from here on out */
+ // const modifiedOld = (Object.values(modifiedRemoved) as { node: TreeNode }[]).map(
+ // (value: { node: TreeNode }) => value.node
+ // )
+ // const modifiedNew = (Object.values(modifiedAdded) as { node: TreeNode }[]).map(
+ // (value: { node: TreeNode }) => value.node
+ // )
+ // diffResult.unchanged.push(...unchanged.map((value) => idMapA[value].node))
+ // diffResult.unchanged.push(...unchanged.map((value) => idMapB[value].node))
+ // diffResult.removed.push(
+ // ...(Object.values(removed) as { node: TreeNode }[]).map(
+ // (value: { node: TreeNode }) => value.node
+ // )
+ // )
+ // diffResult.added.push(
+ // ...(Object.values(added) as { node: TreeNode }[]).map(
+ // (value: { node: TreeNode }) => value.node
+ // )
+ // )
+
+ // modifiedOld.forEach((value, index) => {
+ // value
+ // diffResult.modified.push([modifiedOld[index], modifiedNew[index]])
+ // })
+ // return Promise.resolve(diffResult)
+ // }
}
diff --git a/packages/viewer/src/modules/extensions/Extension.ts b/packages/viewer/src/modules/extensions/Extension.ts
index 011b281731..d40c7b6bc4 100644
--- a/packages/viewer/src/modules/extensions/Extension.ts
+++ b/packages/viewer/src/modules/extensions/Extension.ts
@@ -1,13 +1,14 @@
-import { IViewer } from '../..'
+import type { Constructor } from 'type-fest'
+import { type IViewer } from '../..'
import EventEmitter from '../EventEmitter'
export class Extension extends EventEmitter {
- public get inject(): Array Extension> {
+ public get inject(): Array> {
return []
}
protected viewer: IViewer
- protected _enabled: boolean
+ protected _enabled: boolean = false
public get enabled(): boolean {
return this._enabled
diff --git a/packages/viewer/src/modules/extensions/FilteringExtension.ts b/packages/viewer/src/modules/extensions/FilteringExtension.ts
index dcbfd04912..a3755c4cb8 100644
--- a/packages/viewer/src/modules/extensions/FilteringExtension.ts
+++ b/packages/viewer/src/modules/extensions/FilteringExtension.ts
@@ -6,19 +6,24 @@ import SpeckleRenderer from '../SpeckleRenderer'
import { FilterMaterialType } from '../materials/Materials'
import { NodeRenderView } from '../tree/NodeRenderView'
import { Extension } from './Extension'
-import { TreeNode, WorldTree } from '../tree/WorldTree'
-import { IViewer, UpdateFlags, ViewerEvent } from '../../IViewer'
-import {
+import { type TreeNode, WorldTree } from '../tree/WorldTree'
+import { type IViewer, UpdateFlags, ViewerEvent } from '../../IViewer'
+import type {
NumericPropertyInfo,
PropertyInfo,
StringPropertyInfo
} from '../filtering/PropertyManager'
+/** TO DO: Should remove selectedObjects entirely*/
export type FilteringState = {
selectedObjects?: string[]
hiddenObjects?: string[]
isolatedObjects?: string[]
- colorGroups?: Record[]
+ colorGroups?: {
+ value: string
+ color: string
+ ids: string[]
+ }[]
userColorGroups?: { ids: string[]; color: string }[]
activePropFilterKey?: string
passMin?: number | null
@@ -28,12 +33,12 @@ export type FilteringState = {
export class FilteringExtension extends Extension {
public WTI: WorldTree
private Renderer: SpeckleRenderer
- private StateKey: string = null
+ private StateKey: string | undefined = undefined
private VisibilityState = new VisibilityState()
- private ColorStringFilterState = null
- private ColorNumericFilterState = null
- private UserspaceColorState = new UserspaceColorState()
+ private ColorStringFilterState: ColorStringFilterState | null = null
+ private ColorNumericFilterState: ColorNumericFilterState | null = null
+ private UserspaceColorState: UserspaceColorState | null = new UserspaceColorState()
private CurrentFilteringState: FilteringState = {} as FilteringState
public get filteringState(): FilteringState {
@@ -55,7 +60,7 @@ export class FilteringExtension extends Extension {
public hideObjects(
objectIds: string[],
- stateKey: string = null,
+ stateKey: string | undefined = undefined,
includeDescendants = false,
ghost = false
): FilteringState {
@@ -70,7 +75,7 @@ export class FilteringExtension extends Extension {
public showObjects(
objectIds: string[],
- stateKey: string = null,
+ stateKey: string | undefined = undefined,
includeDescendants = false
): FilteringState {
return this.setVisibilityState(
@@ -83,7 +88,7 @@ export class FilteringExtension extends Extension {
public isolateObjects(
objectIds: string[],
- stateKey: string = null,
+ stateKey: string | undefined = undefined,
includeDescendants = true,
ghost = true
): FilteringState {
@@ -98,7 +103,7 @@ export class FilteringExtension extends Extension {
public unIsolateObjects(
objectIds: string[],
- stateKey: string = null,
+ stateKey: string | undefined = undefined,
includeDescendants = true,
ghost = true
): FilteringState {
@@ -113,7 +118,7 @@ export class FilteringExtension extends Extension {
private setVisibilityState(
objectIds: string[],
- stateKey: string = null,
+ stateKey: string | undefined = undefined,
command: Command,
includeDescendants = false,
ghost = false
@@ -143,7 +148,10 @@ export class FilteringExtension extends Extension {
}
if (command === Command.HIDE || command === Command.ISOLATE) {
- const res = objectIds.reduce((acc, curr) => ((acc[curr] = 1), acc), {})
+ const res = objectIds.reduce(
+ (acc: Record, curr: string) => ((acc[curr] = 1), acc),
+ {}
+ )
Object.assign(this.VisibilityState.ids, res)
}
@@ -159,10 +167,10 @@ export class FilteringExtension extends Extension {
this.WTI.walk(this.visibilityWalk.bind(this))
if (command === Command.ISOLATE || command === Command.UNISOLATE) {
// this.WTI.walk(this.isolationWalk.bind(this))
- const rvMap = {}
+ const rvMap: Record = {}
this.WTI.walk((node: TreeNode) => {
if (!node.model.atomic || this.WTI.isRoot(node)) return true
- const rvNodes = this.WTI.getRenderTree().getRenderViewNodesForNode(node, node)
+ const rvNodes = this.WTI.getRenderTree().getRenderViewNodesForNode(node)
if (!this.VisibilityState.ids[node.model.raw.id]) {
rvNodes.forEach((rvNode: TreeNode) => {
rvMap[rvNode.model.id] = rvNode.model.renderView
@@ -181,42 +189,28 @@ export class FilteringExtension extends Extension {
}
private visibilityWalk(node: TreeNode): boolean {
- // if (!node.model.atomic) return true
if (this.VisibilityState.ids[node.model.id]) {
this.VisibilityState.rvs.push(
- ...this.WTI.getRenderTree().getRenderViewsForNode(node, node)
- )
- }
- return true
- }
-
- private isolationWalk(node: TreeNode): boolean {
- if (!node.model.atomic || this.WTI.isRoot(node)) return true
- const rvs = this.WTI.getRenderTree().getRenderViewsForNode(node, node)
- if (!this.VisibilityState.ids[node.model.raw.id]) {
- this.VisibilityState.rvs.push(...rvs)
- } else {
- this.VisibilityState.rvs = this.VisibilityState.rvs.filter(
- (rv) => !rvs.includes(rv)
+ ...this.WTI.getRenderTree().getRenderViewsForNode(node)
)
}
return true
}
- public setColorFilter(prop: PropertyInfo, ghost = true) {
+ public setColorFilter(prop: PropertyInfo, ghost = true): FilteringState {
if (prop.type === 'number') {
this.ColorStringFilterState = null
- this.ColorNumericFilterState = new ColorNumericFilterState()
return this.setNumericColorFilter(prop as NumericPropertyInfo, ghost)
}
if (prop.type === 'string') {
this.ColorNumericFilterState = null
- this.ColorStringFilterState = new ColorStringFilterState()
return this.setStringColorFilter(prop as StringPropertyInfo, ghost)
}
+ return this.filteringState
}
- private setNumericColorFilter(numProp: NumericPropertyInfo, ghost) {
+ private setNumericColorFilter(numProp: NumericPropertyInfo, ghost: boolean) {
+ this.ColorNumericFilterState = new ColorNumericFilterState()
this.ColorNumericFilterState.currentProp = numProp
const passMin = numProp.passMin || numProp.min
@@ -246,7 +240,7 @@ export class FilteringExtension extends Extension {
* as in, if there is an id clash (which will happen for instances), the old implementation's indexOf
* would return the first value. Here we choose to do the same
*/
- const matchingIds = {}
+ const matchingIds: Record = {}
for (let k = 0; k < numProp.valueGroups.length; k++) {
if (matchingIds[numProp.valueGroups[k].id]) {
continue
@@ -265,7 +259,7 @@ export class FilteringExtension extends Extension {
if (!node.model.atomic || this.WTI.isRoot(node) || this.WTI.isSubtreeRoot(node))
return true
- const rvs = this.WTI.getRenderTree().getRenderViewsForNode(node, node)
+ const rvs = this.WTI.getRenderTree().getRenderViewsForNode(node)
const idx = matchingIds[node.model.raw.id]
if (!idx) {
nonMatchingRvs.push(...rvs)
@@ -275,6 +269,7 @@ export class FilteringExtension extends Extension {
value: (idx - passMin) / (passMax - passMin)
})
}
+ return true
})
this.ColorNumericFilterState.colorGroups = colorGroups
@@ -284,21 +279,23 @@ export class FilteringExtension extends Extension {
return this.setFilters()
}
- private setStringColorFilter(stringProp: StringPropertyInfo, ghost) {
+ private setStringColorFilter(stringProp: StringPropertyInfo, ghost: boolean) {
+ this.ColorStringFilterState = new ColorStringFilterState()
this.ColorStringFilterState.currentProp = stringProp
const valueGroupColors: ValueGroupColorItemStringProps[] = []
for (const vg of stringProp.valueGroups) {
const col = stc(vg.value) // TODO: smarter way needed.
- const entry = {
+ const entry: ValueGroupColorItemStringProps = {
...vg,
color: new Color(col),
- rvs: []
+ rvs: [],
+ idMap: {}
}
/** This is to avoid indexOf inside the walk callback which is ridiculously slow */
- entry['idMap'] = {}
+ entry.idMap = {}
for (let k = 0; k < vg.ids.length; k++) {
- entry['idMap'][vg.ids[k]] = 1
+ entry.idMap[vg.ids[k]] = 1
}
valueGroupColors.push(entry)
}
@@ -311,17 +308,16 @@ export class FilteringExtension extends Extension {
// they are identified as a different category.
// 07.05.2023: Attempt on fixing the issue described above. This fixes #1525, but it does
// add a bit of overhead. Not 100% sure if it breaks anything else tho'
- const nonMatchingMap = {}
+ const nonMatchingMap: Record = {}
this.WTI.walk((node: TreeNode) => {
if (!node.model.atomic || this.WTI.isRoot(node) || this.WTI.isSubtreeRoot(node)) {
return true
}
- const vg = valueGroupColors.find((v) => {
- return v['idMap'][node.model.raw.id]
+ const vg = valueGroupColors.find((v: ValueGroupColorItemStringProps) => {
+ return v.idMap[node.model.raw.id]
})
- const rvNodes = this.WTI.getRenderTree().getRenderViewNodesForNode(node, node)
-
+ const rvNodes = this.WTI.getRenderTree().getRenderViewNodesForNode(node)
if (!vg) {
rvNodes.forEach(
(rvNode) =>
@@ -331,7 +327,7 @@ export class FilteringExtension extends Extension {
return true
}
- const rvs = []
+ const rvs: Array = []
rvNodes.forEach((value: TreeNode) => {
if (this.WTI.getRenderTree().getAtomicParent(value) === node) {
@@ -348,7 +344,10 @@ export class FilteringExtension extends Extension {
const nonMatchingRvs: NodeRenderView[] = Object.values(nonMatchingMap)
/** Deleting this since we're not going to use it further */
for (const vg of valueGroupColors) {
- delete vg['idMap']
+ /** Adamant on this one */
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ delete vg.idMap
}
this.ColorStringFilterState.colorGroups = valueGroupColors
this.ColorStringFilterState.rampTexture = rampTexture
@@ -366,7 +365,9 @@ export class FilteringExtension extends Extension {
return this.filteringState
}
- public setUserObjectColors(groups: { objectIds: string[]; color: string }[]) {
+ public setUserObjectColors(
+ groups: { objectIds: string[]; color: string }[]
+ ): FilteringState {
this.UserspaceColorState = new UserspaceColorState()
// Resetting any other filtering color ops as they're not compatible
this.ColorNumericFilterState = null
@@ -388,7 +389,7 @@ export class FilteringExtension extends Extension {
group.nodes.push(...nodes)
nodes.forEach((node: TreeNode) => {
const rvsNodes = this.WTI.getRenderTree()
- .getRenderViewNodesForNode(node, node)
+ .getRenderViewNodesForNode(node)
.map((rvNode) => rvNode.model.renderView)
if (rvsNodes) group.rvs.push(...rvsNodes)
})
@@ -404,17 +405,17 @@ export class FilteringExtension extends Extension {
return this.setFilters()
}
- public removeUserObjectColors() {
+ public removeUserObjectColors(): FilteringState {
this.UserspaceColorState = null
return this.setFilters()
}
- public resetFilters(): FilteringState {
+ public resetFilters(): FilteringState | null {
this.VisibilityState = new VisibilityState()
this.ColorStringFilterState = null
this.ColorNumericFilterState = null
this.UserspaceColorState = null
- this.StateKey = null
+ this.StateKey = undefined
this.Renderer.resetMaterials()
this.viewer.requestRender(UpdateFlags.RENDER | UpdateFlags.SHADOWS)
return null
@@ -441,8 +442,9 @@ export class FilteringExtension extends Extension {
color: group.color.getHexString(),
ids: group.ids
})
- this.CurrentFilteringState.activePropFilterKey =
- this.ColorStringFilterState.currentProp.key
+ if (this.ColorStringFilterState.currentProp)
+ this.CurrentFilteringState.activePropFilterKey =
+ this.ColorStringFilterState.currentProp.key
}
}
// Number based colors
@@ -550,7 +552,9 @@ export class FilteringExtension extends Extension {
if (this.idCache[key] && this.idCache[key].length) return this.idCache[key]
for (let k = 0; k < objectIds.length; k++) {
- const node = this.WTI.findId(objectIds[k])[0]
+ const nodes = this.WTI.findId(objectIds[k])
+ if (!nodes) continue
+ const node = nodes[0]
const subtree = node.all((node) => {
return node.model.raw !== undefined
})
@@ -597,16 +601,16 @@ class VisibilityState {
}
class ColorStringFilterState {
- public currentProp: StringPropertyInfo
+ public currentProp: StringPropertyInfo | null
public colorGroups: ValueGroupColorItemStringProps[]
public nonMatchingRvs: NodeRenderView[]
- public rampTexture: Texture
+ public rampTexture: Texture | undefined
public ghost = true
public reset() {
this.currentProp = null
this.colorGroups = []
this.nonMatchingRvs = []
- this.rampTexture = null
+ this.rampTexture = undefined
}
}
@@ -615,14 +619,15 @@ type ValueGroupColorItemStringProps = {
ids: string[]
color: Color
rvs: NodeRenderView[]
+ idMap: Record
}
class ColorNumericFilterState {
- public currentProp: NumericPropertyInfo
- public nonMatchingRvs: NodeRenderView[]
- public colorGroups: ValueGroupColorItemNumericProps[]
+ public currentProp!: NumericPropertyInfo
+ public nonMatchingRvs!: NodeRenderView[]
+ public colorGroups!: ValueGroupColorItemNumericProps[]
public ghost = true
- public matchingIds: string[]
+ public matchingIds!: Record
}
type ValueGroupColorItemNumericProps = {
@@ -637,7 +642,7 @@ class UserspaceColorState {
nodes: TreeNode[]
rvs: NodeRenderView[]
}[] = []
- public rampTexture: Texture
+ public rampTexture!: Texture
public reset() {
this.groups = []
}
diff --git a/packages/viewer/src/modules/extensions/SectionOutlines.ts b/packages/viewer/src/modules/extensions/SectionOutlines.ts
index 2a7abe1994..43d15b3872 100644
--- a/packages/viewer/src/modules/extensions/SectionOutlines.ts
+++ b/packages/viewer/src/modules/extensions/SectionOutlines.ts
@@ -5,6 +5,7 @@ import {
Group,
InterleavedBufferAttribute,
Line3,
+ Material,
Plane,
Vector2,
Vector3
@@ -15,7 +16,7 @@ import { Geometry } from '../converter/Geometry'
import SpeckleGhostMaterial from '../materials/SpeckleGhostMaterial'
import SpeckleLineMaterial from '../materials/SpeckleLineMaterial'
import { Extension } from './Extension'
-import { IViewer } from '../..'
+import { type IViewer } from '../..'
import { SectionTool, SectionToolEvent } from './SectionTool'
import { GeometryType } from '../batching/Batch'
import { ObjectLayers } from '../../IViewer'
@@ -43,7 +44,6 @@ export class SectionOutlines extends Extension {
private static readonly INITIAL_BUFFER_SIZE = 60000 // Must be a multiple of 6
private tmpVec: Vector3 = new Vector3()
- private tmpVec2: Vector3 = new Vector3()
private up: Vector3 = new Vector3(0, 1, 0)
private down: Vector3 = new Vector3(0, -1, 0)
private left: Vector3 = new Vector3(-1, 0, 0)
@@ -94,7 +94,7 @@ export class SectionOutlines extends Extension {
this.sectionProvider.on(SectionToolEvent.Updated, this.sectionUpdated.bind(this))
}
- public getPlaneOutline(planeId: PlaneId) {
+ private getPlaneOutline(planeId: PlaneId) {
return this.planeOutlines[planeId]
}
@@ -129,6 +129,10 @@ export class SectionOutlines extends Extension {
const tempVector4 = new Vector3()
const tempLine = new Line3()
const planeId = this.getPlaneId(_plane)
+ if (!planeId) {
+ Logger.error(`Invalid plane! Aborting section outline update`)
+ return
+ }
const clipOutline = this.planeOutlines[planeId].renderable
let index = 0
let posAttr = (
@@ -154,13 +158,17 @@ export class SectionOutlines extends Extension {
const localPlane = plane
return localPlane.intersectsBox(box)
},
- intersectsTriangle(tri, i, contained, depth, batchObject) {
- i
- contained
- depth
+ intersectsTriangle(tri, _i, _contained, _depth, batchObject) {
+ /** Catering to typescript */
+ /** We're intersecting the AS for meshes. There will always be a batchObject */
+ if (!batchObject) {
+ throw new Error('Null batch object in AS intersection!')
+ }
// check each triangle edge to see if it intersects with the plane. If so then
// add it to the list of segments.
- const material = batches[b].mesh.getBatchObjectMaterial(batchObject)
+ const material = batches[b].mesh.getBatchObjectMaterial(
+ batchObject
+ ) as Material
if (
material instanceof SpeckleGhostMaterial ||
material.visible === false ||
@@ -349,9 +357,7 @@ export class SectionOutlines extends Extension {
)
for (let k = 0; k < planes.length; k++) {
this.updatePlaneOutline(
- this.viewer
- .getRenderer()
- .batcher.getBatches(undefined, GeometryType.MESH) as MeshBatch[],
+ this.viewer.getRenderer().batcher.getBatches(undefined, GeometryType.MESH),
planes[k],
outlineOffset
)
@@ -374,7 +380,7 @@ export class SectionOutlines extends Extension {
Geometry.updateRTEGeometry(outline.renderable.geometry, buffer)
}
- private getPlaneId(plane: Plane) {
+ private getPlaneId(plane: Plane): PlaneId | undefined {
this.tmpVec.set(
Math.round(plane.normal.x),
Math.round(plane.normal.y),
@@ -386,5 +392,7 @@ export class SectionOutlines extends Extension {
if (this.tmpVec.equals(this.down)) return PlaneId.NEGATIVE_Y
if (this.tmpVec.equals(this.back)) return PlaneId.NEGATIVE_Z
if (this.tmpVec.equals(this.forward)) return PlaneId.POSITIVE_Z
+
+ return undefined
}
}
diff --git a/packages/viewer/src/modules/extensions/SectionTool.ts b/packages/viewer/src/modules/extensions/SectionTool.ts
index 561daa9428..7e5ea75858 100644
--- a/packages/viewer/src/modules/extensions/SectionTool.ts
+++ b/packages/viewer/src/modules/extensions/SectionTool.ts
@@ -13,10 +13,12 @@ import {
BufferAttribute,
Raycaster,
DoubleSide,
- SphereGeometry
+ SphereGeometry,
+ type Intersection,
+ Vector2
} from 'three'
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
-import { IViewer, ObjectLayers } from '../../IViewer'
+import { type IViewer, ObjectLayers } from '../../IViewer'
import { Extension } from './Extension'
import { CameraEvent } from '../objects/SpeckleCamera'
import { InputEvent } from '../input/Input'
@@ -28,6 +30,12 @@ export enum SectionToolEvent {
Updated = 'section-box-changed'
}
+export interface SectionToolEventPayload {
+ [SectionToolEvent.DragStart]: void
+ [SectionToolEvent.DragEnd]: void
+ [SectionToolEvent.Updated]: Plane[]
+}
+
export class SectionTool extends Extension {
public get inject() {
return [CameraController]
@@ -39,20 +47,20 @@ export class SectionTool extends Extension {
protected boxMaterial: MeshStandardMaterial
protected boxMesh: Mesh
protected boxMeshHelper: Box3Helper
- protected boxMeshHelperMaterial: LineBasicMaterial
+ protected boxMeshHelperMaterial!: LineBasicMaterial
protected plane: PlaneGeometry
protected hoverPlane: Mesh
protected sphere: Mesh
protected sidesSimple: { [id: string]: { verts: number[]; axis: string } }
- protected currentRange: number[]
- protected planes: Plane[]
+ protected currentRange: number[] | null
+ protected planes!: Plane[]
- protected prevPosition: Vector3
+ protected prevPosition: Vector3 | null
protected attachedToBox: boolean
- protected controls: TransformControls
- protected allowSelection: boolean
+ protected controls!: TransformControls
+ protected allowSelection!: boolean
protected raycaster: Raycaster
@@ -68,6 +76,14 @@ export class SectionTool extends Extension {
this.viewer.requestRender()
}
+ public get visible(): boolean {
+ return this.display.visible
+ }
+
+ public set visible(value: boolean) {
+ this.display.visible = value
+ }
+
constructor(viewer: IViewer, protected cameraProvider: CameraController) {
super(viewer)
this.viewer = viewer
@@ -94,7 +110,7 @@ export class SectionTool extends Extension {
this.display.add(this.boxMesh)
- this.boxMeshHelper = new Box3Helper(this.boxGeometry.boundingBox)
+ this.boxMeshHelper = new Box3Helper(this.boxGeometry.boundingBox || new Box3())
this.boxMeshHelper.material = new LineBasicMaterial({
color: 0x0a66ff,
opacity: 0.4
@@ -162,15 +178,27 @@ export class SectionTool extends Extension {
this.cameraProvider.on(CameraEvent.FrameUpdate, (data: boolean) => {
this.allowSelection = !data
})
- this.viewer.getRenderer().input.on(InputEvent.Click, this._clickHandler.bind(this))
+ this.viewer.getRenderer().input.on(InputEvent.Click, this.clickHandler.bind(this))
this.enabled = false
}
+ public on(
+ eventType: T,
+ listener: (arg: SectionToolEventPayload[T]) => void
+ ): void {
+ super.on(eventType, listener)
+ }
+
private _setupControls() {
+ const camera = this.viewer.getRenderer().renderingCamera
+ if (!camera) {
+ throw new Error('Cannot create SectionTool extension. No rendering camera found')
+ }
+
this.controls?.dispose()
this.controls?.detach()
this.controls = new TransformControls(
- this.viewer.getRenderer().renderingCamera,
+ camera,
this.viewer.getRenderer().renderer.domElement
)
for (let k = 0; k < this.controls?.children.length; k++) {
@@ -203,7 +231,7 @@ export class SectionTool extends Extension {
private _draggingChangeHandler() {
if (!this.display.visible) return
this.boxGeometry.computeBoundingBox()
- this.boxMeshHelper.box.copy(this.boxGeometry.boundingBox)
+ this.boxMeshHelper.box.copy(this.boxGeometry.boundingBox || new Box3())
// Dragging a side / plane
if (this.dragging && this.currentRange) {
@@ -247,16 +275,16 @@ export class SectionTool extends Extension {
this.prevPosition = this.sphere.position.clone()
}
this.viewer.getRenderer().clippingPlanes = this.planes
- this.viewer.getRenderer().clippingVolume = this.getCurrentBox()
+ this.viewer.getRenderer().clippingVolume = this.getBox()
this.emit(SectionToolEvent.Updated, this.planes)
this.viewer.requestRender()
}
- private _clickHandler(args) {
+ private clickHandler(args: Vector2 & { event: PointerEvent; multiSelect: boolean }) {
if (!this.allowSelection || this.dragging) return
this.raycaster.setFromCamera(args, this.cameraProvider.renderingCamera)
- let intersectedObjects = []
+ let intersectedObjects: Array = []
if (this.display.visible) {
intersectedObjects = this.raycaster.intersectObject(this.boxMesh)
}
@@ -272,8 +300,14 @@ export class SectionTool extends Extension {
this.hoverPlane.visible = true
const side =
this.sidesSimple[
- `${intersectedObjects[0].face.a}${intersectedObjects[0].face.b}${intersectedObjects[0].face.c}`
+ `${intersectedObjects[0].face?.a}${intersectedObjects[0].face?.b}${intersectedObjects[0].face?.c}`
]
+ /** Catering to typescript
+ * We're intersection an indexed mesh. There will always be an intersected face
+ */
+ if (!side) {
+ throw new Error('Cannot determine section side')
+ }
this.controls.showX = side.axis === 'x'
this.controls.showY = side.axis === 'y'
this.controls.showZ = side.axis === 'z'
@@ -413,12 +447,12 @@ export class SectionTool extends Extension {
this.controls.showZ = true
}
- public getCurrentBox() {
+ public getBox(): Box3 {
if (!this.display.visible) return new Box3()
- return this.boxGeometry.boundingBox
+ return this.boxGeometry.boundingBox || new Box3()
}
- public setBox(targetBox, offset = 0) {
+ public setBox(targetBox: Box3, offset = 0): void {
let box
if (targetBox) box = targetBox
@@ -479,22 +513,14 @@ export class SectionTool extends Extension {
this.boxGeometry.computeBoundingSphere()
this._generateOrUpdatePlanes()
this._attachControlsToBox()
- this.boxMeshHelper.box.copy(this.boxGeometry.boundingBox)
+ this.boxMeshHelper.box.copy(this.boxGeometry.boundingBox || new Box3())
this.emit(SectionToolEvent.Updated, this.planes)
this.viewer.getRenderer().clippingPlanes = this.planes
- this.viewer.getRenderer().clippingVolume = this.getCurrentBox()
+ this.viewer.getRenderer().clippingVolume = this.getBox()
this.viewer.requestRender()
}
- public toggle() {
+ public toggle(): void {
this.enabled = !this._enabled
}
-
- public displayOff() {
- this.display.visible = false
- }
-
- public displayOn() {
- this.display.visible = true
- }
}
diff --git a/packages/viewer/src/modules/extensions/SelectionExtension.ts b/packages/viewer/src/modules/extensions/SelectionExtension.ts
index 025504b33c..5b7624d09d 100644
--- a/packages/viewer/src/modules/extensions/SelectionExtension.ts
+++ b/packages/viewer/src/modules/extensions/SelectionExtension.ts
@@ -1,20 +1,23 @@
-import { ExtendedIntersection } from '../objects/SpeckleRaycaster'
+import { type ExtendedIntersection } from '../objects/SpeckleRaycaster'
import { Extension } from './Extension'
import { NodeRenderView } from '../tree/NodeRenderView'
-import { Material } from 'three'
+import { Material, Vector2 } from 'three'
import { InputEvent } from '../input/Input'
import { MathUtils } from 'three'
import {
- IViewer,
+ type IViewer,
ObjectLayers,
- SelectionEvent,
+ type SelectionEvent,
UpdateFlags,
ViewerEvent
} from '../../IViewer'
-import Materials, { DisplayStyle, RenderMaterial } from '../materials/Materials'
+import Materials, {
+ type DisplayStyle,
+ type RenderMaterial
+} from '../materials/Materials'
import { StencilOutlineType } from '../../IViewer'
-import { MaterialOptions } from '../materials/MaterialOptions'
-import { TreeNode } from '../tree/WorldTree'
+import { type MaterialOptions } from '../materials/MaterialOptions'
+import { type TreeNode } from '../tree/WorldTree'
import { CameraController } from './CameraController'
export interface SelectionExtensionOptions {
@@ -55,21 +58,23 @@ export class SelectionExtension extends Extension {
protected selectedNodes: Array = []
protected selectionRvs: { [id: string]: NodeRenderView } = {}
protected selectionMaterials: { [id: string]: Material } = {}
- protected options: SelectionExtensionOptions
- protected hoverRv: NodeRenderView
- protected hoverMaterial: Material
- protected selectionMaterialData: RenderMaterial & DisplayStyle & MaterialOptions
- protected hoverMaterialData: RenderMaterial & DisplayStyle & MaterialOptions
- protected transparentSelectionMaterialData: RenderMaterial &
+ protected hoverRv!: NodeRenderView | null
+ protected hoverMaterial!: Material | null
+ protected selectionMaterialData!: RenderMaterial & DisplayStyle & MaterialOptions
+ protected hoverMaterialData!: RenderMaterial & DisplayStyle & MaterialOptions
+ protected transparentSelectionMaterialData!: RenderMaterial &
DisplayStyle &
MaterialOptions
- protected transparentHoverMaterialData: RenderMaterial &
+ protected transparentHoverMaterialData!: RenderMaterial &
+ DisplayStyle &
+ MaterialOptions
+ protected hiddenSelectionMaterialData!: RenderMaterial &
DisplayStyle &
MaterialOptions
- protected hiddenSelectionMaterialData: RenderMaterial & DisplayStyle & MaterialOptions
protected _enabled = true
+ protected _options!: SelectionExtensionOptions
- public get enabled() {
+ public get enabled(): boolean {
return this._enabled
}
@@ -77,19 +82,12 @@ export class SelectionExtension extends Extension {
this._enabled = value
}
- public constructor(viewer: IViewer, protected cameraProvider: CameraController) {
- super(viewer)
- this.viewer.on(ViewerEvent.ObjectClicked, this.onObjectClicked.bind(this))
- this.viewer.on(ViewerEvent.ObjectDoubleClicked, this.onObjectDoubleClick.bind(this))
- this.viewer
- .getRenderer()
- .input.on(InputEvent.PointerMove, this.onPointerMove.bind(this))
- this.setOptions(DefaultSelectionExtensionOptions)
+ public get options(): SelectionExtensionOptions {
+ return this._options
}
- public setOptions(options: SelectionExtensionOptions) {
- this.options = options
- /** Opaque selection */
+ public set options(value: SelectionExtensionOptions) {
+ this._options = value
this.selectionMaterialData = Object.assign({}, this.options.selectionMaterialData)
/** Transparent selection */
this.transparentSelectionMaterialData = Object.assign(
@@ -114,11 +112,24 @@ export class SelectionExtension extends Extension {
this.transparentHoverMaterialData.opacity = 0.5
}
- public getSelectedObjects() {
+ public constructor(viewer: IViewer, protected cameraProvider: CameraController) {
+ super(viewer)
+ this.viewer.on(ViewerEvent.ObjectClicked, this.onObjectClicked.bind(this))
+ this.viewer.on(ViewerEvent.ObjectDoubleClicked, this.onObjectDoubleClick.bind(this))
+ this.viewer
+ .getRenderer()
+ .input.on(InputEvent.PointerMove, this.onPointerMove.bind(this))
+ this.options = DefaultSelectionExtensionOptions
+ }
+
+ public getSelectedObjects(): Array> {
return this.selectedNodes.map((v) => v.model.raw)
}
+ public getSelectedNodes(): Array {
+ return this.selectedNodes
+ }
- public selectObjects(ids: Array, multiSelect = false) {
+ public selectObjects(ids: Array, multiSelect = false): void {
if (!this._enabled) return
if (!multiSelect) {
@@ -126,18 +137,21 @@ export class SelectionExtension extends Extension {
}
for (let k = 0; k < ids.length; k++) {
- this.selectedNodes.push(...this.viewer.getWorldTree().findId(ids[k]))
+ const foundNodes = this.viewer.getWorldTree().findId(ids[k])
+ if (foundNodes) this.selectedNodes.push(...foundNodes)
}
this.applySelection()
}
- public unselectObjects(ids: Array) {
+ /**TO DO: This is redundant */
+ public unselectObjects(ids: Array): void {
if (!this._enabled) return
const nodes = []
for (let k = 0; k < ids.length; k++) {
- nodes.push(...this.viewer.getWorldTree().findId(ids[k]))
+ const foundNodes = this.viewer.getWorldTree().findId(ids[k])
+ if (foundNodes) nodes.push(...foundNodes)
}
this.clearSelection(nodes)
}
@@ -149,10 +163,10 @@ export class SelectionExtension extends Extension {
return
}
- const rvs = []
+ const rvs: Array = []
nodes.forEach((node: TreeNode) => {
rvs.push(
- ...this.viewer.getWorldTree().getRenderTree().getRenderViewsForNode(node, node)
+ ...this.viewer.getWorldTree().getRenderTree().getRenderViewsForNode(node)
)
})
this.removeSelection(rvs)
@@ -162,7 +176,7 @@ export class SelectionExtension extends Extension {
)
}
- protected onObjectClicked(selection: SelectionEvent) {
+ protected onObjectClicked(selection: SelectionEvent | null) {
if (!this._enabled) return
if (!selection) {
@@ -177,7 +191,7 @@ export class SelectionExtension extends Extension {
this.applySelection()
}
- protected onObjectDoubleClick(selectionInfo: SelectionEvent) {
+ protected onObjectDoubleClick(selectionInfo: SelectionEvent | null) {
if (!this._enabled) return
if (!selectionInfo) {
@@ -190,8 +204,10 @@ export class SelectionExtension extends Extension {
)
}
- protected onPointerMove(e) {
+ protected onPointerMove(e: Vector2 & { event: Event }) {
if (!this._enabled) return
+ const camera = this.viewer.getRenderer().renderingCamera
+ if (!camera) return
if (!this.options.hoverMaterialData) return
const result =
@@ -199,16 +215,16 @@ export class SelectionExtension extends Extension {
.getRenderer()
.intersections.intersect(
this.viewer.getRenderer().scene,
- this.viewer.getRenderer().renderingCamera,
+ camera,
e,
- true,
- this.viewer.getRenderer().clippingVolume,
[
ObjectLayers.STREAM_CONTENT_MESH,
ObjectLayers.STREAM_CONTENT_POINT,
ObjectLayers.STREAM_CONTENT_LINE,
ObjectLayers.STREAM_CONTENT_TEXT
- ]
+ ],
+ true,
+ this.viewer.getRenderer().clippingVolume
) as ExtendedIntersection[]) || []
/* TEMPORARY */
@@ -228,17 +244,21 @@ export class SelectionExtension extends Extension {
const rvs = this.viewer
.getWorldTree()
.getRenderTree()
- .getRenderViewsForNode(this.selectedNodes[k], this.selectedNodes[k])
+ .getRenderViewsForNode(this.selectedNodes[k])
rvs.forEach((rv: NodeRenderView) => {
if (!this.selectionRvs[rv.guid]) this.selectionRvs[rv.guid] = rv
- if (!this.selectionMaterials[rv.guid])
- this.selectionMaterials[rv.guid] = this.viewer.getRenderer().getMaterial(rv)
+ if (!this.selectionMaterials[rv.guid]) {
+ this.selectionMaterials[rv.guid] = this.viewer
+ .getRenderer()
+ .getMaterial(rv) as Material
+ }
})
}
const rvs = Object.values(this.selectionRvs)
const opaqueRvs = rvs.filter(
(value) =>
+ this.selectionMaterials[value.guid] &&
this.selectionMaterials[value.guid].visible &&
this.selectionMaterials[value.guid] &&
!(
@@ -248,13 +268,16 @@ export class SelectionExtension extends Extension {
)
const transparentRvs = rvs.filter(
(value) =>
+ this.selectionMaterials[value.guid] &&
this.selectionMaterials[value.guid].visible &&
this.selectionMaterials[value.guid] &&
this.selectionMaterials[value.guid].transparent &&
this.selectionMaterials[value.guid].opacity < 1
)
const hiddenRvs = rvs.filter(
- (value) => this.selectionMaterials[value.guid].visible === false
+ (value) =>
+ this.selectionMaterials[value.guid] &&
+ this.selectionMaterials[value.guid].visible === false
)
this.viewer.getRenderer().setMaterial(opaqueRvs, this.selectionMaterialData)
@@ -268,7 +291,7 @@ export class SelectionExtension extends Extension {
protected removeSelection(rvs?: Array) {
this.removeHover()
- const materialMap = {}
+ const materialMap: Record = {}
rvs = rvs ? rvs : Object.values(this.selectionRvs)
rvs.forEach((rv: NodeRenderView) => {
const material = this.selectionMaterials[rv.guid]
@@ -293,7 +316,7 @@ export class SelectionExtension extends Extension {
}
}
- protected applyHover(renderView: NodeRenderView) {
+ protected applyHover(renderView: NodeRenderView | null) {
this.removeHover()
if (!renderView) return
@@ -303,7 +326,7 @@ export class SelectionExtension extends Extension {
this.removeHover()
this.hoverRv = renderView
- this.hoverMaterial = this.viewer.getRenderer().getMaterial(this.hoverRv)
+ this.hoverMaterial = this.viewer.getRenderer().getMaterial(this.hoverRv) as Material
this.viewer
.getRenderer()
.setMaterial(
@@ -317,7 +340,7 @@ export class SelectionExtension extends Extension {
}
protected removeHover() {
- if (this.hoverRv)
+ if (this.hoverRv && this.hoverMaterial)
this.viewer.getRenderer().setMaterial([this.hoverRv], this.hoverMaterial)
this.hoverRv = null
this.hoverMaterial = null
diff --git a/packages/viewer/src/modules/extensions/measurements/Measurement.ts b/packages/viewer/src/modules/extensions/measurements/Measurement.ts
index 479898f1b2..a4591561ea 100644
--- a/packages/viewer/src/modules/extensions/measurements/Measurement.ts
+++ b/packages/viewer/src/modules/extensions/measurements/Measurement.ts
@@ -1,6 +1,14 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
-/* eslint-disable @typescript-eslint/no-empty-function */
-import { Box3, Camera, Object3D, Plane, Vector2, Vector3, Vector4 } from 'three'
+import {
+ Box3,
+ Camera,
+ Object3D,
+ Plane,
+ Raycaster,
+ Vector2,
+ Vector3,
+ Vector4,
+ type Intersection
+} from 'three'
export enum MeasurementState {
HIDDEN,
@@ -14,8 +22,8 @@ export abstract class Measurement extends Object3D {
public endPoint: Vector3 = new Vector3()
public startNormal: Vector3 = new Vector3()
public endNormal: Vector3 = new Vector3()
- public startLineLength: number
- public endLineLength: number
+ public startLineLength!: number
+ public endLineLength!: number
public value = 0
public units = 'm'
public precision = 2
@@ -31,7 +39,7 @@ export abstract class Measurement extends Object3D {
protected static vec2Buff0: Vector2 = new Vector2()
protected _state: MeasurementState = MeasurementState.HIDDEN
- protected renderingCamera: Camera
+ protected renderingCamera: Camera | null
protected renderingSize: Vector2 = new Vector2()
public set state(value: MeasurementState) {
@@ -42,18 +50,19 @@ export abstract class Measurement extends Object3D {
return this._state
}
- public set isVisible(value: boolean) {}
+ public abstract set isVisible(value: boolean)
public get bounds(): Box3 {
return new Box3().expandByPoint(this.startPoint).expandByPoint(this.endPoint)
}
- public frameUpdate(camera: Camera, size: Vector2, bounds: Box3) {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ public frameUpdate(camera: Camera | null, size: Vector2, _bounds: Box3) {
this.renderingCamera = camera
this.renderingSize.copy(size)
}
- public update() {}
- public raycast(raycaster, intersects) {}
- public highlight(value: boolean) {}
- public updateClippingPlanes(planes: Plane[]) {}
+ public abstract update(): void
+ public abstract raycast(_raycaster: Raycaster, _intersects: Array): void
+ public abstract highlight(_value: boolean): void
+ public abstract updateClippingPlanes(_planes: Plane[]): void
}
diff --git a/packages/viewer/src/modules/extensions/measurements/MeasurementPointGizmo.ts b/packages/viewer/src/modules/extensions/measurements/MeasurementPointGizmo.ts
index a9904b31e8..8c3c012c6f 100644
--- a/packages/viewer/src/modules/extensions/measurements/MeasurementPointGizmo.ts
+++ b/packages/viewer/src/modules/extensions/measurements/MeasurementPointGizmo.ts
@@ -14,8 +14,10 @@ import {
PerspectiveCamera,
Plane,
Quaternion,
+ Raycaster,
Vector2,
- Vector3
+ Vector3,
+ type Intersection
} from 'three'
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2.js'
import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry.js'
@@ -90,7 +92,10 @@ export class MeasurementPointGizmo extends Group {
material.polygonOffset = true
material.polygonOffsetFactor = -5
material.polygonOffsetUnits = 5
- material.opacity = this._style.discOpacity
+ material.opacity =
+ this._style.discOpacity !== undefined
+ ? this._style.discOpacity
+ : DefaultMeasurementPointGizmoStyle.discOpacity
material.transparent = material.opacity < 1
return material
}
@@ -117,8 +122,11 @@ export class MeasurementPointGizmo extends Group {
}
lineMaterial.linewidth = 2
lineMaterial.worldUnits = false
- lineMaterial.resolution = new Vector2(1513, 1306)
- lineMaterial.opacity = this._style.lineOpacity
+ lineMaterial.resolution = new Vector2(256, 256)
+ lineMaterial.opacity =
+ this._style.lineOpacity !== undefined
+ ? this._style.lineOpacity
+ : DefaultMeasurementPointGizmoStyle.lineOpacity
lineMaterial.transparent = lineMaterial.opacity < 1
lineMaterial.depthTest = false
return lineMaterial
@@ -129,13 +137,18 @@ export class MeasurementPointGizmo extends Group {
{ color: color ? color : this._style.pointColor },
['BILLBOARD_FIXED']
)
- material.opacity = this._style.pointOpacity
+ material.opacity =
+ this._style.pointOpacity !== undefined
+ ? this._style.pointOpacity
+ : DefaultMeasurementPointGizmoStyle.pointOpacity
material.transparent = material.opacity < 1
material.color.convertSRGBToLinear()
material.toneMapped = false
material.depthTest = false
material.billboardPixelHeight =
- this._style.pointPixelHeight * window.devicePixelRatio
+ (this._style.pointPixelHeight !== undefined
+ ? this._style.pointPixelHeight
+ : DefaultMeasurementPointGizmoStyle.pointPixelHeight) * window.devicePixelRatio
material.userData.billboardPos.value.copy(this.point.position)
return material
}
@@ -151,11 +164,16 @@ export class MeasurementPointGizmo extends Group {
)
material.toneMapped = false
material.color.convertSRGBToLinear()
- material.opacity = this._style.textOpacity
+ material.opacity =
+ this._style.textOpacity !== undefined
+ ? this._style.textOpacity
+ : DefaultMeasurementPointGizmoStyle.textOpacity
material.transparent = material.opacity < 1
material.depthTest = false
material.billboardPixelHeight =
- this._style.textPixelHeight * window.devicePixelRatio
+ (this._style.textPixelHeight !== undefined
+ ? this._style.textPixelHeight
+ : DefaultMeasurementPointGizmoStyle.textPixelHeight) * window.devicePixelRatio
material.userData.billboardPos.value.copy(this.text.position)
return material.getDerivedMaterial()
@@ -169,7 +187,7 @@ export class MeasurementPointGizmo extends Group {
const doublePositions = new Float64Array(geometry.attributes.position.array)
Geometry.updateRTEGeometry(geometry, doublePositions)
- this.disc = new Mesh(geometry, null)
+ this.disc = new Mesh(geometry, undefined)
this.disc.layers.set(ObjectLayers.MEASUREMENTS)
const buffer = new Float64Array(18)
@@ -181,7 +199,7 @@ export class MeasurementPointGizmo extends Group {
Geometry.updateRTEGeometry(lineGeometry, buffer)
- this.line = new LineSegments2(lineGeometry, null)
+ this.line = new LineSegments2(lineGeometry, undefined)
this.line.computeLineDistances()
this.line.name = `test-mesurements-line`
this.line.frustumCulled = false
@@ -190,7 +208,7 @@ export class MeasurementPointGizmo extends Group {
const sphereGeometry = new CircleGeometry(1, 16)
- this.point = new Mesh(sphereGeometry, null)
+ this.point = new Mesh(sphereGeometry, undefined)
this.point.layers.set(ObjectLayers.MEASUREMENTS)
this.point.visible = false
this.point.renderOrder = 1
@@ -198,7 +216,10 @@ export class MeasurementPointGizmo extends Group {
const point2 = new Mesh(sphereGeometry, this.getPointMaterial(0xffffff))
point2.renderOrder = 2
point2.material.billboardPixelHeight =
- this._style.pointPixelHeight * window.devicePixelRatio -
+ (this._style.pointPixelHeight !== undefined
+ ? this._style.pointPixelHeight
+ : DefaultMeasurementPointGizmoStyle.pointPixelHeight) *
+ window.devicePixelRatio -
2 * window.devicePixelRatio
point2.layers.set(ObjectLayers.MEASUREMENTS)
this.point.add(point2)
@@ -211,7 +232,7 @@ export class MeasurementPointGizmo extends Group {
this.add(this.line)
this.add(this.text)
- this.style = style
+ this.style = style ? style : DefaultMeasurementPointGizmoStyle
}
public enable(disc: boolean, line: boolean, point: boolean, text: boolean) {
@@ -224,7 +245,12 @@ export class MeasurementPointGizmo extends Group {
}
public frameUpdate(camera: Camera, bounds: Box3) {
- if (camera.type === 'PerspectiveCamera' && +this._style.fixedSize > 0) {
+ if (
+ camera.type === 'PerspectiveCamera' &&
+ +(this._style.fixedSize !== undefined
+ ? this._style.fixedSize
+ : DefaultMeasurementPointGizmoStyle.fixedSize) > 0
+ ) {
const cam = camera as PerspectiveCamera
const cameraObjectDistance = cam.position.distanceTo(this.disc.position)
const worldSize = Math.abs(2 * Math.tan(cam.fov / 2.0) * cameraObjectDistance)
@@ -233,7 +259,12 @@ export class MeasurementPointGizmo extends Group {
this.disc.scale.set(size, size, size)
this.disc.matrixWorldNeedsUpdate = true
}
- if (camera.type === 'OrthographicCamera' && +this._style.fixedSize > 0) {
+ if (
+ camera.type === 'OrthographicCamera' &&
+ +(this._style.fixedSize !== undefined
+ ? this._style.fixedSize
+ : DefaultMeasurementPointGizmoStyle.fixedSize) > 0
+ ) {
const cam = camera as OrthographicCamera
const orthoSize = cam.top - cam.bottom
const size = (orthoSize / cam.zoom) * 0.0075
@@ -309,7 +340,7 @@ export class MeasurementPointGizmo extends Group {
backgroundPixelHeight: 20
}
this.text.setTransform(position, quaternion, scale)
- this.text.backgroundMesh.renderOrder = 3
+ if (this.text.backgroundMesh) this.text.backgroundMesh.renderOrder = 3
this.text.textMesh.renderOrder = 4
})
}
@@ -321,7 +352,7 @@ export class MeasurementPointGizmo extends Group {
this.text.textMesh.material = this.getTextMaterial()
}
- public raycast(raycaster, intersects) {
+ public raycast(raycaster: Raycaster, intersects: Array) {
// this.disc.raycast(raycaster, intersects)
this.line.raycast(raycaster, intersects)
// this.point.raycast(raycaster, intersects)
diff --git a/packages/viewer/src/modules/extensions/measurements/MeasurementsExtension.ts b/packages/viewer/src/modules/extensions/measurements/MeasurementsExtension.ts
index d3f7bd7a24..b29e1f06d4 100644
--- a/packages/viewer/src/modules/extensions/measurements/MeasurementsExtension.ts
+++ b/packages/viewer/src/modules/extensions/measurements/MeasurementsExtension.ts
@@ -1,13 +1,12 @@
import SpeckleRenderer from '../../SpeckleRenderer'
-import { IViewer, ObjectLayers } from '../../../IViewer'
+import { type IViewer, ObjectLayers } from '../../../IViewer'
import { PerpendicularMeasurement } from './PerpendicularMeasurement'
import { Plane, Ray, Raycaster, Vector2, Vector3 } from 'three'
import { PointToPointMeasurement } from './PointToPointMeasurement'
import { Measurement, MeasurementState } from './Measurement'
-import { ExtendedIntersection } from '../../objects/SpeckleRaycaster'
+import { ExtendedMeshIntersection } from '../../objects/SpeckleRaycaster'
import Logger from 'js-logger'
-import SpeckleMesh from '../../objects/SpeckleMesh'
import SpeckleGhostMaterial from '../../materials/SpeckleGhostMaterial'
import { Extension } from '../Extension'
import { InputEvent } from '../../input/Input'
@@ -39,12 +38,12 @@ export class MeasurementsExtension extends Extension {
return [CameraController]
}
- protected renderer: SpeckleRenderer = null
+ protected renderer: SpeckleRenderer
protected measurements: Measurement[] = []
- protected _activeMeasurement: Measurement = null
- protected _selectedMeasurement: Measurement = null
- protected raycaster: Raycaster = null
+ protected _activeMeasurement: Measurement | null = null
+ protected _selectedMeasurement: Measurement | null = null
+ protected raycaster: Raycaster
protected _options: MeasurementOptions = Object.assign({}, DefaultMeasurementsOptions)
private _frameLock = false
@@ -60,10 +59,6 @@ export class MeasurementsExtension extends Extension {
return this._enabled
}
- public get visible(): boolean {
- return this._options.visible
- }
-
public set enabled(value: boolean) {
this._enabled = value
if (this._activeMeasurement) {
@@ -75,8 +70,8 @@ export class MeasurementsExtension extends Extension {
this.renderer.resetPipeline()
}
- public set paused(value: boolean) {
- this._paused = value
+ public get options(): MeasurementOptions {
+ return this._options
}
public set options(options: MeasurementOptions) {
@@ -92,11 +87,11 @@ export class MeasurementsExtension extends Extension {
this.applyOptions()
}
- public get selectedMeasurement(): Measurement {
+ public get selectedMeasurement(): Measurement | null {
return this._selectedMeasurement
}
- public get activeMeasurement(): Measurement {
+ public get activeMeasurement(): Measurement | null {
return this._activeMeasurement
}
@@ -111,25 +106,22 @@ export class MeasurementsExtension extends Extension {
this.renderer.input.on(InputEvent.DoubleClick, this.onPointerDoubleClick.bind(this))
}
- public onLateUpdate(deltaTime: number) {
- deltaTime
+ public onLateUpdate() {
if (!this._enabled) return
+ const camera = this.renderer.renderingCamera
+ if (!camera) return
this._frameLock = false
this.renderer.renderer.getDrawingBufferSize(this.screenBuff0)
if (this._activeMeasurement)
this._activeMeasurement.frameUpdate(
- this.renderer.renderingCamera,
+ camera,
this.screenBuff0,
this.renderer.sceneBox
)
this.measurements.forEach((value: Measurement) => {
- value.frameUpdate(
- this.renderer.renderingCamera,
- this.screenBuff0,
- this.renderer.sceneBox
- )
+ value.frameUpdate(camera, this.screenBuff0, this.renderer.sceneBox)
})
}
@@ -137,28 +129,29 @@ export class MeasurementsExtension extends Extension {
this.renderer.renderer.getDrawingBufferSize(this.screenBuff0)
}
- protected onPointerMove(data) {
+ protected onPointerMove(data: Vector2 & { event: Event }) {
if (!this._enabled || this._paused) return
+ const camera = this.renderer.renderingCamera
+ if (!camera) return
+
if (this._frameLock) {
return
}
- let result =
- (this.renderer.intersections.intersect(
+ let result: ExtendedMeshIntersection[] =
+ this.renderer.intersections.intersect(
this.renderer.scene,
- this.renderer.renderingCamera,
+ camera,
data,
+ ObjectLayers.STREAM_CONTENT_MESH,
true,
- this.renderer.clippingVolume,
- [ObjectLayers.STREAM_CONTENT_MESH]
- ) as ExtendedIntersection[]) || []
+ this.renderer.clippingVolume
+ ) || []
- result = result.filter((value: ExtendedIntersection) => {
- const material = (value.object as unknown as SpeckleMesh).getBatchObjectMaterial(
- value.batchObject
- )
- return !(material instanceof SpeckleGhostMaterial) && material.visible
+ result = result.filter((value: ExtendedMeshIntersection) => {
+ const material = value.object.getBatchObjectMaterial(value.batchObject)
+ return material && !(material instanceof SpeckleGhostMaterial) && material.visible
})
if (!result.length) {
@@ -166,16 +159,21 @@ export class MeasurementsExtension extends Extension {
return
}
- if (!this._activeMeasurement) {
- this.startMeasurement()
- }
- this._activeMeasurement.isVisible = true
-
+ /** Catering to typescript
+ * There will always be an intersected face. We're casting against indexed meshes only
+ */
this.pointBuff.copy(result[0].point)
this.normalBuff.copy(result[0].face.normal)
+
if (this._options.vertexSnap) {
this.snap(result[0], this.pointBuff, this.normalBuff)
}
+
+ if (!this._activeMeasurement) {
+ this._activeMeasurement = this.startMeasurement()
+ this._activeMeasurement.isVisible = true
+ }
+
if (this._activeMeasurement.state === MeasurementState.DANGLING_START) {
this._activeMeasurement.startPoint.copy(this.pointBuff)
this._activeMeasurement.startNormal.copy(this.normalBuff)
@@ -189,9 +187,12 @@ export class MeasurementsExtension extends Extension {
this.renderer.resetPipeline()
this._frameLock = true
this._sceneHit = true
+ // console.log('Time -> ', performance.now() - start)
}
- protected onPointerClick(data) {
+ protected onPointerClick(
+ data: { event: PointerEvent; multiSelect: boolean } & Vector2
+ ) {
if (!this._enabled) return
const measurement = this.pickMeasurement(data)
@@ -216,7 +217,9 @@ export class MeasurementsExtension extends Extension {
}
}
- protected onPointerDoubleClick(data) {
+ protected onPointerDoubleClick(
+ data: Vector2 & { event: PointerEvent; multiSelect: boolean }
+ ) {
const measurement = this.pickMeasurement(data)
if (measurement) {
this.cameraProvider.setCameraView(measurement.bounds, true)
@@ -228,25 +231,24 @@ export class MeasurementsExtension extends Extension {
}
}
- protected autoLazerMeasure(data) {
+ protected autoLazerMeasure(data: Vector2) {
if (!this._activeMeasurement) return
+ if (!this.renderer.renderingCamera) return
this._activeMeasurement.state = MeasurementState.DANGLING_START
- let result =
- (this.renderer.intersections.intersect(
+ let result: ExtendedMeshIntersection[] =
+ this.renderer.intersections.intersect(
this.renderer.scene,
this.renderer.renderingCamera,
data,
+ ObjectLayers.STREAM_CONTENT_MESH,
true,
- this.renderer.clippingVolume,
- [ObjectLayers.STREAM_CONTENT_MESH]
- ) as ExtendedIntersection[]) || []
+ this.renderer.clippingVolume
+ ) || []
result = result.filter((value) => {
- const material = (value.object as unknown as SpeckleMesh).getBatchObjectMaterial(
- value.batchObject
- )
- return !(material instanceof SpeckleGhostMaterial) && material.visible
+ const material = value.object.getBatchObjectMaterial(value.batchObject)
+ return material && !(material instanceof SpeckleGhostMaterial) && material.visible
})
if (!result.length) return
@@ -257,21 +259,19 @@ export class MeasurementsExtension extends Extension {
const offsetPoint = new Vector3()
.copy(startPoint)
.add(new Vector3().copy(startNormal).multiplyScalar(0.000001))
- let perpResult =
- (this.renderer.intersections.intersectRay(
+ let perpResult: ExtendedMeshIntersection[] =
+ this.renderer.intersections.intersectRay(
this.renderer.scene,
this.renderer.renderingCamera,
new Ray(offsetPoint, startNormal),
+ ObjectLayers.STREAM_CONTENT_MESH,
true,
- this.renderer.clippingVolume,
- [ObjectLayers.STREAM_CONTENT_MESH]
- ) as ExtendedIntersection[]) || []
+ this.renderer.clippingVolume
+ ) || []
- perpResult = perpResult.filter((value) => {
- const material = (value.object as unknown as SpeckleMesh).getBatchObjectMaterial(
- value.batchObject
- )
- return !(material instanceof SpeckleGhostMaterial) && material.visible
+ perpResult = perpResult.filter((value: ExtendedMeshIntersection) => {
+ const material = value.object.getBatchObjectMaterial(value.batchObject)
+ return material && !(material instanceof SpeckleGhostMaterial) && material.visible
})
if (!perpResult.length) {
@@ -288,29 +288,34 @@ export class MeasurementsExtension extends Extension {
this.finishMeasurement()
}
- protected startMeasurement() {
+ protected startMeasurement(): Measurement {
+ let measurement: Measurement
if (this._options.type === MeasurementType.PERPENDICULAR)
- this._activeMeasurement = new PerpendicularMeasurement()
+ measurement = new PerpendicularMeasurement()
else if (this._options.type === MeasurementType.POINTTOPOINT)
- this._activeMeasurement = new PointToPointMeasurement()
+ measurement = new PointToPointMeasurement()
+ else throw new Error('Unsupported measurement type!')
- this._activeMeasurement.state = MeasurementState.DANGLING_START
- this._activeMeasurement.frameUpdate(
+ measurement.state = MeasurementState.DANGLING_START
+ measurement.frameUpdate(
this.renderer.renderingCamera,
this.screenBuff0,
this.renderer.sceneBox
)
- this.renderer.scene.add(this._activeMeasurement)
+ this.renderer.scene.add(measurement)
+ return measurement
}
protected cancelMeasurement() {
- this.renderer.scene.remove(this._activeMeasurement)
+ if (this._activeMeasurement) this.renderer.scene.remove(this._activeMeasurement)
this._activeMeasurement = null
this.renderer.needsRender = true
this.renderer.resetPipeline()
}
protected finishMeasurement() {
+ if (!this._activeMeasurement) return
+
this._activeMeasurement.state = MeasurementState.COMPLETE
this._activeMeasurement.update()
if (this._activeMeasurement.value > 0) {
@@ -334,7 +339,7 @@ export class MeasurementsExtension extends Extension {
}
}
- public clearMeasurements() {
+ public clearMeasurements(): void {
this.removeMeasurement()
this.measurements.forEach((measurement: Measurement) => {
this.renderer.scene.remove(measurement)
@@ -347,16 +352,20 @@ export class MeasurementsExtension extends Extension {
let flashCount = 0
const maxFlashCount = 5
const handle = setInterval(() => {
- this._activeMeasurement.highlight(Boolean(flashCount++ % 2))
- if (flashCount >= maxFlashCount) {
- clearInterval(handle)
+ if (this._activeMeasurement) {
+ this._activeMeasurement.highlight(Boolean(flashCount++ % 2))
+ if (flashCount >= maxFlashCount) {
+ clearInterval(handle)
+ }
+ this.renderer.needsRender = true
+ this.renderer.resetPipeline()
}
- this.renderer.needsRender = true
- this.renderer.resetPipeline()
}, 100)
}
- protected pickMeasurement(data): Measurement {
+ protected pickMeasurement(data: Vector2): Measurement | null {
+ if (!this.renderer.renderingCamera) return null
+
this.measurements.forEach((value) => {
value.highlight(false)
})
@@ -372,10 +381,12 @@ export class MeasurementsExtension extends Extension {
}
protected snap(
- intersection: ExtendedIntersection,
+ intersection: ExtendedMeshIntersection,
outPoint: Vector3,
outNormal: Vector3
) {
+ if (!this.renderer.renderingCamera) return
+
const v0 = intersection.batchObject.accelerationStructure
.getVertexAtIndex(intersection.face.a)
.project(this.renderer.renderingCamera)
@@ -407,7 +418,7 @@ export class MeasurementsExtension extends Extension {
}
}
- protected updateClippingPlanes(planes: Plane[]) {
+ protected updateClippingPlanes(planes: Plane[]): void {
this.measurements.forEach((value) => {
value.updateClippingPlanes(planes)
})
@@ -417,8 +428,14 @@ export class MeasurementsExtension extends Extension {
const all = [this._activeMeasurement, ...this.measurements]
all.forEach((value) => {
if (value) {
- value.units = this._options.units
- value.precision = this._options.precision
+ value.units =
+ this._options.units !== undefined
+ ? this._options.units
+ : DefaultMeasurementsOptions.units
+ value.precision =
+ this._options.precision !== undefined
+ ? this._options.precision
+ : DefaultMeasurementsOptions.precision
value.update()
}
})
@@ -441,6 +458,6 @@ export class MeasurementsExtension extends Extension {
measurement.update()
measurement.state = MeasurementState.COMPLETE
measurement.update()
- this.measurements.push(this._activeMeasurement)
+ this.measurements.push(measurement)
}
}
diff --git a/packages/viewer/src/modules/extensions/measurements/PerpendicularMeasurement.ts b/packages/viewer/src/modules/extensions/measurements/PerpendicularMeasurement.ts
index 7279440bb2..c4a53733bb 100644
--- a/packages/viewer/src/modules/extensions/measurements/PerpendicularMeasurement.ts
+++ b/packages/viewer/src/modules/extensions/measurements/PerpendicularMeasurement.ts
@@ -1,18 +1,26 @@
-import { Box3, Camera, Plane, Vector2, Vector3 } from 'three'
+import {
+ Box3,
+ Camera,
+ Plane,
+ Raycaster,
+ Vector2,
+ Vector3,
+ type Intersection
+} from 'three'
import { MeasurementPointGizmo } from './MeasurementPointGizmo'
import { getConversionFactor } from '../../converter/Units'
import { Measurement, MeasurementState } from './Measurement'
import { ObjectLayers } from '../../../IViewer'
export class PerpendicularMeasurement extends Measurement {
- private startGizmo: MeasurementPointGizmo = null
- private endGizmo: MeasurementPointGizmo = null
+ private startGizmo: MeasurementPointGizmo | null = null
+ private endGizmo: MeasurementPointGizmo | null = null
private midPoint: Vector3 = new Vector3()
private normalIndicatorPixelSize = 15 * window.devicePixelRatio
public set isVisible(value: boolean) {
- this.startGizmo.enable(value, value, value, value)
- this.endGizmo.enable(value, value, value, value)
+ this.startGizmo?.enable(value, value, value, value)
+ this.endGizmo?.enable(value, value, value, value)
}
public get bounds(): Box3 {
@@ -35,8 +43,8 @@ export class PerpendicularMeasurement extends Measurement {
public frameUpdate(camera: Camera, size: Vector2, bounds: Box3) {
super.frameUpdate(camera, size, bounds)
- this.startGizmo.frameUpdate(camera, bounds)
- this.endGizmo.frameUpdate(camera, bounds)
+ this.startGizmo?.frameUpdate(camera, bounds)
+ this.endGizmo?.frameUpdate(camera, bounds)
/** Not a fan of this but the camera library fails to tell us when zooming happens
* so we need to update the screen space normal indicator each frame, otherwise it
* won't look correct while zooming
@@ -48,10 +56,11 @@ export class PerpendicularMeasurement extends Measurement {
public update() {
if (isNaN(this.startPoint.length())) return
+ if (!this.renderingCamera) return
- this.startGizmo.updateDisc(this.startPoint, this.startNormal)
- this.startGizmo.updatePoint(this.startPoint)
- this.endGizmo.updateDisc(this.endPoint, this.endNormal)
+ this.startGizmo?.updateDisc(this.startPoint, this.startNormal)
+ this.startGizmo?.updatePoint(this.startPoint)
+ this.endGizmo?.updateDisc(this.endPoint, this.endNormal)
if (this._state === MeasurementState.DANGLING_START) {
const startLine0 = Measurement.vec3Buff0.copy(this.startPoint)
@@ -98,12 +107,12 @@ export class PerpendicularMeasurement extends Measurement {
endNDC
.applyMatrix4(this.renderingCamera.projectionMatrixInverse)
.applyMatrix4(this.renderingCamera.matrixWorld)
- this.startGizmo.updateLine([
+ this.startGizmo?.updateLine([
startLine0,
Measurement.vec3Buff1.set(endNDC.x, endNDC.y, endNDC.z)
])
- this.endGizmo.enable(false, false, false, false)
+ this.endGizmo?.enable(false, false, false, false)
}
if (this._state === MeasurementState.DANGLING_END) {
@@ -148,11 +157,11 @@ export class PerpendicularMeasurement extends Measurement {
.copy(this.startNormal)
.multiplyScalar(this.startLineLength)
)
- this.startGizmo.updateLine([startLine0, startLine1])
+ this.startGizmo?.updateLine([startLine0, startLine1])
const endLine0 = Measurement.vec3Buff3.copy(this.endPoint)
- this.endGizmo.updateLine([
+ this.endGizmo?.updateLine([
endLine0,
endLine3,
endLine3,
@@ -160,7 +169,7 @@ export class PerpendicularMeasurement extends Measurement {
this.midPoint,
endLine0
])
- this.endGizmo.updatePoint(this.midPoint)
+ this.endGizmo?.updatePoint(this.midPoint)
const textPos = Measurement.vec3Buff0
.copy(this.startPoint)
@@ -171,29 +180,29 @@ export class PerpendicularMeasurement extends Measurement {
)
this.value = this.midPoint.distanceTo(this.startPoint)
- this.startGizmo.updateText(
+ this.startGizmo?.updateText(
`${(this.value * getConversionFactor('m', this.units)).toFixed(
this.precision
)} ${this.units}`,
textPos
)
- this.endGizmo.enable(true, true, true, true)
+ this.endGizmo?.enable(true, true, true, true)
}
if (this._state === MeasurementState.COMPLETE) {
- this.startGizmo.updateText(
+ this.startGizmo?.updateText(
`${(this.value * getConversionFactor('m', this.units)).toFixed(
this.precision
)} ${this.units}`
)
- this.startGizmo.enable(false, true, true, true)
- this.endGizmo.enable(false, false, true, false)
+ this.startGizmo?.enable(false, true, true, true)
+ this.endGizmo?.enable(false, false, true, false)
}
}
- public raycast(raycaster, intersects) {
- const results = []
- this.startGizmo.raycast(raycaster, results)
- this.endGizmo.raycast(raycaster, results)
+ public raycast(raycaster: Raycaster, intersects: Array) {
+ const results: Array = []
+ this.startGizmo?.raycast(raycaster, results)
+ this.endGizmo?.raycast(raycaster, results)
if (results.length) {
intersects.push({
distance: results[0].distance,
@@ -207,12 +216,12 @@ export class PerpendicularMeasurement extends Measurement {
}
public highlight(value: boolean) {
- this.startGizmo.highlight = value
- this.endGizmo.highlight = value
+ if (this.startGizmo) this.startGizmo.highlight = value
+ if (this.endGizmo) this.endGizmo.highlight = value
}
public updateClippingPlanes(planes: Plane[]) {
- this.startGizmo.updateClippingPlanes(planes)
- this.endGizmo.updateClippingPlanes(planes)
+ if (this.startGizmo) this.startGizmo.updateClippingPlanes(planes)
+ if (this.endGizmo) this.endGizmo.updateClippingPlanes(planes)
}
}
diff --git a/packages/viewer/src/modules/extensions/measurements/PointToPointMeasurement.ts b/packages/viewer/src/modules/extensions/measurements/PointToPointMeasurement.ts
index 2cdb4a00e6..e0bf6fe63e 100644
--- a/packages/viewer/src/modules/extensions/measurements/PointToPointMeasurement.ts
+++ b/packages/viewer/src/modules/extensions/measurements/PointToPointMeasurement.ts
@@ -1,16 +1,16 @@
-import { Box3, Camera, Plane, Vector2 } from 'three'
+import { Box3, Camera, Plane, Raycaster, Vector2, type Intersection } from 'three'
import { MeasurementPointGizmo } from './MeasurementPointGizmo'
import { getConversionFactor } from '../../converter/Units'
import { Measurement, MeasurementState } from './Measurement'
import { ObjectLayers } from '../../../IViewer'
export class PointToPointMeasurement extends Measurement {
- private startGizmo: MeasurementPointGizmo = null
- private endGizmo: MeasurementPointGizmo = null
+ private startGizmo: MeasurementPointGizmo | null = null
+ private endGizmo: MeasurementPointGizmo | null = null
public set isVisible(value: boolean) {
- this.startGizmo.enable(value, value, value, value)
- this.endGizmo.enable(value, value, value, value)
+ this.startGizmo?.enable(value, value, value, value)
+ this.endGizmo?.enable(value, value, value, value)
}
public constructor() {
@@ -26,14 +26,14 @@ export class PointToPointMeasurement extends Measurement {
public frameUpdate(camera: Camera, size: Vector2, bounds: Box3) {
super.frameUpdate(camera, size, bounds)
- this.startGizmo.frameUpdate(camera, bounds)
- this.endGizmo.frameUpdate(camera, bounds)
+ this.startGizmo?.frameUpdate(camera, bounds)
+ this.endGizmo?.frameUpdate(camera, bounds)
}
public update() {
- this.startGizmo.updateDisc(this.startPoint, this.startNormal)
- this.startGizmo.updatePoint(this.startPoint)
- this.endGizmo.updateDisc(this.endPoint, this.endNormal)
+ this.startGizmo?.updateDisc(this.startPoint, this.startNormal)
+ this.startGizmo?.updatePoint(this.startPoint)
+ this.endGizmo?.updateDisc(this.endPoint, this.endNormal)
if (this._state === MeasurementState.DANGLING_START) {
const startLine0 = Measurement.vec3Buff0.copy(this.startPoint)
@@ -44,8 +44,8 @@ export class PointToPointMeasurement extends Measurement {
.copy(this.startNormal)
.multiplyScalar(this.startLineLength)
)
- this.startGizmo.updateLine([startLine0, startLine1])
- this.endGizmo.enable(false, false, false, false)
+ this.startGizmo?.updateLine([startLine0, startLine1])
+ this.endGizmo?.enable(false, false, false, false)
}
if (this._state === MeasurementState.DANGLING_END) {
this.startLineLength = this.startPoint.distanceTo(this.endPoint)
@@ -69,20 +69,20 @@ export class PointToPointMeasurement extends Measurement {
.multiplyScalar(this.startLineLength * 0.5)
)
- this.startGizmo.updateLine([this.startPoint, lineEndPoint])
- this.endGizmo.updatePoint(lineEndPoint)
- this.startGizmo.updateText(
+ this.startGizmo?.updateLine([this.startPoint, lineEndPoint])
+ this.endGizmo?.updatePoint(lineEndPoint)
+ this.startGizmo?.updateText(
`${(this.value * getConversionFactor('m', this.units)).toFixed(
this.precision
)} ${this.units}`,
textPos
)
- this.endGizmo.enable(true, true, true, true)
+ this.endGizmo?.enable(true, true, true, true)
}
if (this._state === MeasurementState.COMPLETE) {
- this.startGizmo.enable(false, true, true, true)
- this.endGizmo.enable(false, false, true, false)
- this.startGizmo.updateText(
+ this.startGizmo?.enable(false, true, true, true)
+ this.endGizmo?.enable(false, false, true, false)
+ this.startGizmo?.updateText(
`${(this.value * getConversionFactor('m', this.units)).toFixed(
this.precision
)} ${this.units}`
@@ -90,10 +90,10 @@ export class PointToPointMeasurement extends Measurement {
}
}
- public raycast(raycaster, intersects) {
- const results = []
- this.startGizmo.raycast(raycaster, results)
- this.endGizmo.raycast(raycaster, results)
+ public raycast(raycaster: Raycaster, intersects: Array) {
+ const results: Array = []
+ this.startGizmo?.raycast(raycaster, results)
+ this.endGizmo?.raycast(raycaster, results)
if (results.length) {
intersects.push({
distance: results[0].distance,
@@ -107,12 +107,12 @@ export class PointToPointMeasurement extends Measurement {
}
public highlight(value: boolean) {
- this.startGizmo.highlight = value
- this.endGizmo.highlight = value
+ if (this.startGizmo) this.startGizmo.highlight = value
+ if (this.endGizmo) this.endGizmo.highlight = value
}
public updateClippingPlanes(planes: Plane[]) {
- this.startGizmo.updateClippingPlanes(planes)
- this.endGizmo.updateClippingPlanes(planes)
+ if (this.startGizmo) this.startGizmo.updateClippingPlanes(planes)
+ if (this.endGizmo) this.endGizmo.updateClippingPlanes(planes)
}
}
diff --git a/packages/viewer/src/modules/filtering/PropertyManager.ts b/packages/viewer/src/modules/filtering/PropertyManager.ts
index 46dfbd5148..d696ca6c15 100644
--- a/packages/viewer/src/modules/filtering/PropertyManager.ts
+++ b/packages/viewer/src/modules/filtering/PropertyManager.ts
@@ -1,5 +1,5 @@
import flatten from '../../helpers/flatten'
-import { TreeNode, WorldTree } from '../tree/WorldTree'
+import { type TreeNode, WorldTree } from '../tree/WorldTree'
export class PropertyManager {
private propCache = {} as Record
@@ -12,7 +12,7 @@ export class PropertyManager {
*/
public async getProperties(
tree: WorldTree,
- resourceUrl: string = null,
+ resourceUrl: string | null = null,
bypassCache = false
): Promise {
let rootNode: TreeNode = tree.root
@@ -21,14 +21,16 @@ export class PropertyManager {
return this.propCache[resourceUrl ? resourceUrl : rootNode.model.id]
if (resourceUrl) {
- const actualRoot = rootNode.children.find((n) => n.model.id === resourceUrl)
+ const actualRoot = rootNode.children.find(
+ (n: { model: { id: string } }) => n.model.id === resourceUrl
+ )
if (actualRoot) rootNode = actualRoot
else {
throw new Error(`Could not find root node for ${resourceUrl} - is it loaded?`)
}
}
- const propValues = {}
+ const propValues: { [key: string]: unknown } = {}
await tree.walkAsync((node: TreeNode) => {
if (!node.model.atomic) return true
@@ -36,7 +38,7 @@ export class PropertyManager {
for (const key in obj) {
if (Array.isArray(obj[key])) continue
if (!propValues[key]) propValues[key] = []
- propValues[key].push({ value: obj[key], id: obj.id })
+ ;(propValues[key] as Array).push({ value: obj[key], id: obj.id })
}
return true
}, rootNode)
@@ -47,20 +49,33 @@ export class PropertyManager {
const propValuesArr = propValues[propKey]
const propInfo = {} as PropertyInfo
propInfo.key = propKey
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
propInfo.type = typeof propValuesArr[0].value === 'string' ? 'string' : 'number'
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
propInfo.objectCount = propValuesArr.length
// For string based props, keep track of which ids belong to which group
if (propInfo.type === 'string') {
const stringPropInfo = propInfo as StringPropertyInfo
const valueGroups = {}
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
for (const { value, id } of propValuesArr) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
if (!valueGroups[value]) valueGroups[value] = []
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
valueGroups[value].push(id)
}
stringPropInfo.valueGroups = []
- for (const key in valueGroups)
+ for (const key in valueGroups) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
stringPropInfo.valueGroups.push({ value: key, ids: valueGroups[key] })
+ }
stringPropInfo.valueGroups = stringPropInfo.valueGroups.sort((a, b) =>
a.value.localeCompare(b.value)
@@ -71,10 +86,14 @@ export class PropertyManager {
const numProp = propInfo as NumericPropertyInfo
numProp.min = Number.MAX_VALUE
numProp.max = Number.MIN_VALUE
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
for (const { value } of propValuesArr) {
if (value < numProp.min) numProp.min = value
if (value > numProp.max) numProp.max = value
}
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
numProp.valueGroups = propValuesArr.sort((a, b) => a.value - b.value)
// const sorted = propValuesArr.sort((a, b) => a.value - b.value)
// propInfo.sortedValues = sorted.map(s => s.value)
diff --git a/packages/viewer/src/modules/input/Input.ts b/packages/viewer/src/modules/input/Input.ts
index 17bf0e5cd1..7b731bcbd6 100644
--- a/packages/viewer/src/modules/input/Input.ts
+++ b/packages/viewer/src/modules/input/Input.ts
@@ -1,38 +1,39 @@
import { Vector2 } from 'three'
import EventEmitter from '../EventEmitter'
-export interface InputOptions {
- hover: boolean
-}
-
-export const InputOptionsDefault = {
- hover: false
+export enum InputEvent {
+ PointerDown = 'pointer-down',
+ PointerUp = 'pointer-up',
+ PointerMove = 'pointer-move',
+ Click = 'click',
+ DoubleClick = 'double-click',
+ KeyUp = 'key-up'
}
-export enum InputEvent {
- PointerDown,
- PointerUp,
- PointerMove,
- Click,
- DoubleClick,
- KeyUp
+export interface InputEventPayload {
+ [InputEvent.PointerDown]: Vector2 & { event: PointerEvent }
+ [InputEvent.PointerUp]: Vector2 & { event: PointerEvent }
+ [InputEvent.PointerMove]: Vector2 & { event: PointerEvent }
+ [InputEvent.Click]: Vector2 & { event: PointerEvent; multiSelect: boolean }
+ [InputEvent.DoubleClick]: Vector2 & { event: PointerEvent; multiSelect: boolean }
+ [InputEvent.KeyUp]: KeyboardEvent
}
+//TO DO: Define proper interface for InputEvent data
export default class Input extends EventEmitter {
private static readonly MAX_DOUBLE_CLICK_TIMING = 500
- private tapTimeout
+ private tapTimeout: number = 0
private lastTap = 0
private lastClick = 0
- private touchLocation: Touch
+ private touchLocation: Touch | undefined
private container
- constructor(container: HTMLElement, _options: InputOptions) {
+ constructor(container: HTMLElement) {
super()
- _options
this.container = container
// Handle mouseclicks
- let mdTime
+ let mdTime: number
this.container.addEventListener('pointerdown', (e) => {
e.preventDefault()
const loc = this._getNormalisedClickPosition(e)
@@ -72,10 +73,14 @@ export default class Input extends EventEmitter {
const tapLength = currentTime - this.lastTap
clearTimeout(this.tapTimeout)
if (tapLength < 500 && tapLength > 0) {
- const loc = this._getNormalisedClickPosition(this.touchLocation)
- this.emit(InputEvent.DoubleClick, loc)
+ if (this.touchLocation) {
+ const loc = this._getNormalisedClickPosition(this.touchLocation)
+ this.emit(InputEvent.DoubleClick, loc)
+ }
} else {
- this.tapTimeout = setTimeout(function () {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ this.tapTimeout = setTimeout(() => {
clearTimeout(this.tapTimeout)
}, 500)
}
@@ -85,18 +90,17 @@ export default class Input extends EventEmitter {
this.container.addEventListener('dblclick', (e) => {
const data = this._getNormalisedClickPosition(e)
;(data as unknown as Record).event = e
+ if (e.shiftKey) (data as unknown as Record).multiSelect = true
this.emit(InputEvent.DoubleClick, data)
})
this.container.addEventListener('pointermove', (e) => {
const data = this._getNormalisedClickPosition(e)
;(data as unknown as Record).event = e
- this.emit('pointer-move', data)
this.emit(InputEvent.PointerMove, data)
})
document.addEventListener('keyup', (e) => {
- this.emit('key-up', e)
this.emit(InputEvent.KeyUp, e)
})
@@ -112,9 +116,16 @@ export default class Input extends EventEmitter {
// })
}
- _getNormalisedClickPosition(e) {
+ public on(
+ eventType: T,
+ listener: (arg: InputEventPayload[T]) => void
+ ): void {
+ super.on(eventType, listener)
+ }
+
+ _getNormalisedClickPosition(e: MouseEvent | Touch) {
// Reference: https://threejsfundamentals.org/threejs/lessons/threejs-picking.html
- const canvas = this.container
+ const canvas = this.container as HTMLCanvasElement
const rect = this.container.getBoundingClientRect()
const pos = {
diff --git a/packages/viewer/src/modules/loaders/GeometryConverter.ts b/packages/viewer/src/modules/loaders/GeometryConverter.ts
index b0f938cac5..e8b77de02a 100644
--- a/packages/viewer/src/modules/loaders/GeometryConverter.ts
+++ b/packages/viewer/src/modules/loaders/GeometryConverter.ts
@@ -1,5 +1,5 @@
-import { NodeData } from '../..'
-import { GeometryData } from '../converter/Geometry'
+import { type GeometryData } from '../converter/Geometry'
+import type { NodeData } from '../tree/WorldTree'
export enum SpeckleType {
View3D = 'View3D',
@@ -40,6 +40,6 @@ export const SpeckleTypeAllRenderables: SpeckleType[] = [
export abstract class GeometryConverter {
public abstract getSpeckleType(node: NodeData): SpeckleType
- public abstract convertNodeToGeometryData(node: NodeData): GeometryData
+ public abstract convertNodeToGeometryData(node: NodeData): GeometryData | null
public abstract disposeNodeGeometryData(node: NodeData): void
}
diff --git a/packages/viewer/src/modules/loaders/Loader.ts b/packages/viewer/src/modules/loaders/Loader.ts
index 469fd70443..10f5a429c3 100644
--- a/packages/viewer/src/modules/loaders/Loader.ts
+++ b/packages/viewer/src/modules/loaders/Loader.ts
@@ -1,26 +1,43 @@
import EventEmitter from '../EventEmitter'
export enum LoaderEvent {
- LoadComplete = 'load-complete',
LoadProgress = 'load-progress',
LoadCancelled = 'load-cancelled',
LoadWarning = 'load-warning'
}
+export interface LoaderEventPayload {
+ [LoaderEvent.LoadProgress]: { progress: number; id: string }
+ [LoaderEvent.LoadCancelled]: string
+ [LoaderEvent.LoadWarning]: { message: string }
+}
+
export abstract class Loader extends EventEmitter {
protected _resource: string
- protected _resourceData: string | ArrayBuffer
+ protected _resourceData: string | ArrayBuffer | undefined
public abstract get resource(): string
public abstract get finished(): boolean
- protected constructor(resource: string, resourceData: string | ArrayBuffer) {
+ protected constructor(
+ resource: string,
+ resourceData?: string | ArrayBuffer | undefined
+ ) {
super()
this._resource = resource
this._resourceData = resourceData
}
+ public on(
+ eventType: T,
+ listener: (arg: LoaderEventPayload[T]) => void
+ ): void {
+ super.on(eventType, listener)
+ }
+
public abstract load(): Promise
- public abstract cancel()
- public abstract dispose()
+ public abstract cancel(): void
+ public dispose(): void {
+ super.dispose()
+ }
}
diff --git a/packages/viewer/src/modules/loaders/OBJ/ObjConverter.ts b/packages/viewer/src/modules/loaders/OBJ/ObjConverter.ts
index 2682027f53..fbc0738c15 100644
--- a/packages/viewer/src/modules/loaders/OBJ/ObjConverter.ts
+++ b/packages/viewer/src/modules/loaders/OBJ/ObjConverter.ts
@@ -1,17 +1,18 @@
-import { Group, Mesh, Object3D } from 'three'
-import { TreeNode, WorldTree } from '../../tree/WorldTree'
-import {
- ConverterNodeDelegate,
- ConverterResultDelegate
-} from '../Speckle/SpeckleConverter'
+import { Mesh, Object3D } from 'three'
+import { type TreeNode, WorldTree } from '../../tree/WorldTree'
+import type { ConverterResultDelegate } from '../Speckle/SpeckleConverter'
import Logger from 'js-logger'
+export type ObjConverterNodeDelegate =
+ | ((object: Object3D, node: TreeNode) => Promise)
+ | null
+
export class ObjConverter {
- private lastAsyncPause: number
+ private lastAsyncPause: number = 0
private tree: WorldTree
private readonly NodeConverterMapping: {
- [name: string]: ConverterNodeDelegate
+ [name: string]: ObjConverterNodeDelegate
} = {
Group: this.groupToNode.bind(this),
Mesh: this.MeshToNode.bind(this)
@@ -33,7 +34,7 @@ export class ObjConverter {
objectURL: string,
object: Object3D,
callback: ConverterResultDelegate,
- node: TreeNode = null
+ node: TreeNode | null = null
) {
await this.asyncPause()
@@ -68,14 +69,15 @@ export class ObjConverter {
}
}
- private directNodeConverterExists(obj) {
+ private directNodeConverterExists(obj: Object3D) {
return obj.type in this.NodeConverterMapping
}
- private async convertToNode(obj, node) {
+ private async convertToNode(obj: Object3D, node: TreeNode) {
try {
if (this.directNodeConverterExists(obj)) {
- return await this.NodeConverterMapping[obj.type](obj, node)
+ const delegate = this.NodeConverterMapping[obj.type]
+ if (delegate) return await delegate(obj, node)
}
return null
} catch (e) {
@@ -84,7 +86,8 @@ export class ObjConverter {
}
}
- private async MeshToNode(obj: Mesh, node) {
+ private async MeshToNode(_obj: Object3D, node: TreeNode) {
+ const obj = _obj as Mesh
if (!obj) return
if (
!obj.geometry.attributes.position ||
@@ -99,10 +102,12 @@ export class ObjConverter {
node.model.raw.vertices = obj.geometry.attributes.position.array
node.model.raw.faces = obj.geometry.index?.array
node.model.raw.colors = obj.geometry.attributes.color?.array
+ return Promise.resolve()
}
- private groupToNode(obj: Group, node) {
+ private groupToNode(obj: Object3D, node: TreeNode) {
obj
node
+ return Promise.resolve()
}
}
diff --git a/packages/viewer/src/modules/loaders/OBJ/ObjGeometryConverter.ts b/packages/viewer/src/modules/loaders/OBJ/ObjGeometryConverter.ts
index d4355bf325..d7a77a17bc 100644
--- a/packages/viewer/src/modules/loaders/OBJ/ObjGeometryConverter.ts
+++ b/packages/viewer/src/modules/loaders/OBJ/ObjGeometryConverter.ts
@@ -1,6 +1,6 @@
import { Matrix4 } from 'three'
-import { NodeData } from '../../..'
-import { GeometryData } from '../../converter/Geometry'
+import { type NodeData } from '../../..'
+import { type GeometryData } from '../../converter/Geometry'
import { GeometryConverter, SpeckleType } from '../GeometryConverter'
import { mergeVertices } from 'three/examples/jsm/utils/BufferGeometryUtils'
@@ -11,16 +11,20 @@ export class ObjGeometryConverter extends GeometryConverter {
return SpeckleType.BlockInstance
case 'Mesh':
return SpeckleType.Mesh
+ default:
+ return SpeckleType.Unknown
}
}
- public convertNodeToGeometryData(node: NodeData): GeometryData {
+ public convertNodeToGeometryData(node: NodeData): GeometryData | null {
const type = this.getSpeckleType(node)
switch (type) {
case SpeckleType.BlockInstance:
return this.BlockInstanceToGeometryData(node)
case SpeckleType.Mesh:
return this.MeshToGeometryData(node)
+ default:
+ return null
}
}
@@ -53,8 +57,8 @@ export class ObjGeometryConverter extends GeometryConverter {
/**
* MESH
*/
- private MeshToGeometryData(node: NodeData): GeometryData {
- if (!node.raw) return
+ private MeshToGeometryData(node: NodeData): GeometryData | null {
+ if (!node.raw) return null
const conversionFactor = 1
if (!node.raw.geometry.index || node.raw.geometry.index.array.length === 0) {
diff --git a/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts b/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts
index e67c5207e7..9836c4b1b7 100644
--- a/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts
+++ b/packages/viewer/src/modules/loaders/OBJ/ObjLoader.ts
@@ -10,7 +10,7 @@ export class ObjLoader extends Loader {
private baseLoader: OBJLoader
private converter: ObjConverter
private tree: WorldTree
- private isFinished: boolean
+ private isFinished: boolean = false
public get resource(): string {
return this._resource
@@ -66,12 +66,16 @@ export class ObjLoader extends Loader {
pload.then(async () => {
const t0 = performance.now()
- const res = await this.tree
- .getRenderTree(this._resource)
- .buildRenderTree(new ObjGeometryConverter())
- Logger.log('Tree build time -> ', performance.now() - t0)
- this.isFinished = true
- resolve(res)
+ const renderTree = this.tree.getRenderTree(this._resource)
+ if (renderTree) {
+ const res = await renderTree.buildRenderTree(new ObjGeometryConverter())
+ Logger.log('Tree build time -> ', performance.now() - t0)
+ this.isFinished = true
+ resolve(res)
+ } else {
+ Logger.error(`Could not get render tree for ${this._resource}`)
+ reject()
+ }
})
pload.catch(() => {
Logger.error(`Could not load ${this._resource}`)
@@ -82,9 +86,9 @@ export class ObjLoader extends Loader {
public cancel() {
this.isFinished = false
- throw new Error('Method not implemented.')
}
+
public dispose() {
- this.baseLoader = null
+ super.dispose()
}
}
diff --git a/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts b/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts
index 250ceb8981..72dc43ae36 100644
--- a/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts
+++ b/packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts
@@ -1,18 +1,22 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { MathUtils } from 'three'
-import { TreeNode, WorldTree } from '../../tree/WorldTree'
+import { type TreeNode, WorldTree } from '../../tree/WorldTree'
import Logger from 'js-logger'
import { NodeMap } from '../../tree/NodeMap'
+import type { SpeckleObject } from '../../..'
+import type ObjectLoader from '@speckle/objectloader'
export type ConverterResultDelegate = () => Promise
-export type ConverterNodeDelegate = (object, node) => Promise
+export type SpeckleConverterNodeDelegate =
+ | ((object: SpeckleObject, node: TreeNode) => Promise)
+ | null
/**
* Utility class providing some top level conversion methods.
* Warning: HIC SVNT DRACONES.
*/
export default class SpeckleConverter {
- private objectLoader
+ private objectLoader: ObjectLoader
private activePromises: number
private maxChildrenPromises: number
private spoofIDs = false
@@ -21,7 +25,7 @@ export default class SpeckleConverter {
private instanceCounter = 0
private readonly NodeConverterMapping: {
- [name: string]: ConverterNodeDelegate
+ [name: string]: SpeckleConverterNodeDelegate
} = {
View3D: this.View3DToNode.bind(this),
BlockInstance: this.BlockInstanceToNode.bind(this),
@@ -45,7 +49,7 @@ export default class SpeckleConverter {
private readonly IgnoreNodes = ['Parameter']
- constructor(objectLoader: unknown, tree: WorldTree) {
+ constructor(objectLoader: ObjectLoader, tree: WorldTree) {
if (!objectLoader) {
Logger.warn(
'Converter initialized without a corresponding object loader. Any objects that include references will throw errors.'
@@ -67,9 +71,9 @@ export default class SpeckleConverter {
*/
public async traverse(
objectURL: string,
- obj,
+ obj: SpeckleObject,
callback: ConverterResultDelegate,
- node: TreeNode = null
+ node: TreeNode | null = null
) {
// Exit on primitives (string, ints, bools, bigints, etc.)
if (obj === null || typeof obj !== 'object') return
@@ -126,7 +130,7 @@ export default class SpeckleConverter {
// If we can convert it, we should invoke the respective conversion routine.
if (this.directNodeConverterExists(obj)) {
try {
- await this.convertToNode(obj.data || obj, childNode)
+ await this.convertToNode(obj, childNode)
await callback()
return
} catch (e) {
@@ -139,7 +143,7 @@ export default class SpeckleConverter {
}
}
- const target = obj
+ const target: SpeckleObject = obj
// Check if the object has a display value of sorts
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -167,7 +171,9 @@ export default class SpeckleConverter {
await callback()
} catch (e) {
Logger.warn(
- `(Traversing) Failed to convert obj with id: ${obj.id} — ${e.message}`
+ `(Traversing) Failed to convert obj with id: ${obj.id} — ${
+ (e as never)['message']
+ }`
)
}
} else {
@@ -192,7 +198,7 @@ export default class SpeckleConverter {
const elements = this.getElementsValue(obj)
if (elements) {
childrenConversionPromisses.push(
- this.traverse(objectURL, elements, callback, childNode)
+ this.traverse(objectURL, elements as SpeckleObject, callback, childNode)
)
this.activePromises += childrenConversionPromisses.length
await Promise.all(childrenConversionPromisses)
@@ -214,9 +220,19 @@ export default class SpeckleConverter {
if (typeof target[prop] !== 'object' || target[prop] === null) continue
if (this.activePromises >= this.maxChildrenPromises) {
- await this.traverse(objectURL, target[prop], callback, childNode)
+ await this.traverse(
+ objectURL,
+ target[prop] as SpeckleObject,
+ callback,
+ childNode
+ )
} else {
- const childPromise = this.traverse(objectURL, target[prop], callback, childNode)
+ const childPromise = this.traverse(
+ objectURL,
+ target[prop] as SpeckleObject,
+ callback,
+ childNode
+ )
childrenConversionPromisses.push(childPromise)
}
}
@@ -225,7 +241,7 @@ export default class SpeckleConverter {
this.activePromises -= childrenConversionPromisses.length
}
- private getNodeId(obj) {
+ private getNodeId(obj: SpeckleObject): string {
if (this.spoofIDs) return MathUtils.generateUUID()
return obj.id
}
@@ -235,19 +251,22 @@ export default class SpeckleConverter {
* @param {[type]} arr [description]
* @return {[type]} [description]
*/
- private async dechunk(arr) {
+ private async dechunk(arr: Array<{ referencedId: string }>) {
if (!arr || arr.length === 0) return arr
// Handles pre-chunking objects, or arrs that have not been chunked
if (!arr[0].referencedId) return arr
- const chunked = []
+ const chunked: unknown[] = []
for (const ref of arr) {
- const real = await this.objectLoader.getObject(ref.referencedId)
+ const real: Record = await this.objectLoader.getObject(
+ ref.referencedId
+ )
chunked.push(real.data)
// await this.asyncPause()
}
- const dechunked = [].concat(...chunked)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const dechunked = [].concat(...(chunked as any))
return dechunked
}
@@ -257,9 +276,11 @@ export default class SpeckleConverter {
* @param {[type]} obj [description]
* @return {[type]} [description]
*/
- private async resolveReference(obj) {
+ private async resolveReference(obj: SpeckleObject): Promise {
if (obj.referencedId) {
- const resolvedObj = await this.objectLoader.getObject(obj.referencedId)
+ const resolvedObj = (await this.objectLoader.getObject(
+ obj.referencedId
+ )) as SpeckleObject
// this.asyncPause()
return resolvedObj
} else return obj
@@ -270,10 +291,8 @@ export default class SpeckleConverter {
* @param {[type]} obj [description]
* @return {[type]} [description]
*/
- private getSpeckleType(obj): string {
- let rawType = 'Base'
- if (obj.data) rawType = obj.data.speckle_type ? obj.data.speckle_type : 'Base'
- else rawType = obj.speckle_type ? obj.speckle_type : 'Base'
+ private getSpeckleType(obj: SpeckleObject): string {
+ const rawType = obj.speckle_type ? obj.speckle_type : 'Base'
const lookup = this.typeLookupTable[rawType]
if (lookup) return lookup
@@ -291,11 +310,9 @@ export default class SpeckleConverter {
return typeRet
}
- private getSpeckleTypeChain(obj): string[] {
+ private getSpeckleTypeChain(obj: SpeckleObject): string[] {
let type = ['Base']
- if (obj.data)
- type = obj.data.speckle_type ? obj.data.speckle_type.split(':').reverse() : type
- else type = obj.speckle_type ? obj.speckle_type.split(':').reverse() : type
+ type = obj.speckle_type ? obj.speckle_type.split(':').reverse() : type
type = type.map((value: string) => {
return value.split('.').reverse()[0]
})
@@ -303,15 +320,16 @@ export default class SpeckleConverter {
return type
}
- private directNodeConverterExists(obj) {
+ private directNodeConverterExists(obj: SpeckleObject) {
return this.getSpeckleType(obj) in this.NodeConverterMapping
}
- private async convertToNode(obj, node) {
+ private async convertToNode(obj: SpeckleObject, node: TreeNode) {
if (obj.referencedId) obj = await this.resolveReference(obj)
try {
if (this.directNodeConverterExists(obj)) {
- return await this.NodeConverterMapping[this.getSpeckleType(obj)](obj, node)
+ const delegate = this.NodeConverterMapping[this.getSpeckleType(obj)]
+ if (delegate) return await delegate(obj, node)
}
return null
} catch (e) {
@@ -320,7 +338,7 @@ export default class SpeckleConverter {
}
}
- private getDisplayValue(obj) {
+ private getDisplayValue(obj: SpeckleObject) {
const displayValue =
obj['displayValue'] ||
obj['@displayValue'] ||
@@ -340,11 +358,11 @@ export default class SpeckleConverter {
return null
}
- private getElementsValue(obj) {
+ private getElementsValue(obj: SpeckleObject) {
return obj['elements'] || obj['@elements']
}
- private getBlockDefinition(obj) {
+ private getBlockDefinition(obj: SpeckleObject) {
return (
obj['@blockDefinition'] ||
obj['blockDefinition'] ||
@@ -353,12 +371,12 @@ export default class SpeckleConverter {
)
}
- private getBlockDefinitionGeometry(obj) {
+ private getBlockDefinitionGeometry(obj: SpeckleObject) {
return obj['@geometry'] || obj['geometry']
}
/** We're wasting a few milis here, but it is what it is */
- private getCompoundId(baseId, counter) {
+ private getCompoundId(baseId: string, counter: number) {
const index = baseId.indexOf(NodeMap.COMPOUND_ID_CHAR)
if (index === -1) {
return baseId + NodeMap.COMPOUND_ID_CHAR + counter
@@ -375,8 +393,12 @@ export default class SpeckleConverter {
*
NODES
*/
- private async View3DToNode(obj, node) {
+ private async View3DToNode(obj: SpeckleObject, _node: TreeNode) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
obj.origin.units = obj.units
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
obj.target.units = obj.units
}
@@ -384,15 +406,19 @@ export default class SpeckleConverter {
* It's only looking for 'elements' and 'displayValues'
* I think it can be used for RevitInstances as well to replace it's current lookup, but I'm afraid to do it
*/
- private async displayableLookup(obj, node, instanced) {
+ private async displayableLookup(
+ obj: SpeckleObject,
+ node: TreeNode,
+ instanced: boolean
+ ) {
if (this.directNodeConverterExists(obj)) {
await this.convertToNode(obj, node)
} else {
const displayValues = this.getDisplayValue(obj)
const elements = this.getElementsValue(obj)
const entries = [
- ...(displayValues ? displayValues : []),
- ...(elements ? elements : [])
+ ...(displayValues ? (displayValues as SpeckleObject[]) : []),
+ ...(elements ? (elements as SpeckleObject[]) : [])
]
for (const entry of entries) {
const value = await this.resolveReference(entry)
@@ -411,16 +437,16 @@ export default class SpeckleConverter {
}
private async parseInstanceDefinitionGeometry(
- instanceObj,
- defGeometry,
- instanceNode
+ instanceObj: SpeckleObject,
+ defGeometry: SpeckleObject,
+ instanceNode: TreeNode
) {
const transformNodeId = MathUtils.generateUUID()
let transformData = null
/** Legacy form of Transform */
if (Array.isArray(instanceObj.transform)) {
transformData = this.getEmptyTransformData(transformNodeId)
- transformData.units = instanceObj.units
+ transformData.units = instanceObj.units as string
transformData.matrix = instanceObj.transform
} else {
transformData = instanceObj.transform
@@ -447,7 +473,11 @@ export default class SpeckleConverter {
await this.displayableLookup(defGeometry, childNode, true)
}
- private async parseInstanceElement(instanceObj, elementObj, instanceNode) {
+ private async parseInstanceElement(
+ _instanceObj: SpeckleObject,
+ elementObj: SpeckleObject,
+ instanceNode: TreeNode
+ ) {
const childNode: TreeNode = this.tree.parse({
id: this.getNodeId(elementObj),
raw: elementObj,
@@ -458,43 +488,49 @@ export default class SpeckleConverter {
await this.displayableLookup(elementObj, childNode, false)
}
- private async BlockInstanceToNode(obj, node) {
- const definition = await this.resolveReference(this.getBlockDefinition(obj))
+ private async BlockInstanceToNode(obj: SpeckleObject, node: TreeNode) {
+ const definition: SpeckleObject = await this.resolveReference(
+ this.getBlockDefinition(obj) as SpeckleObject
+ )
node.model.raw.definition = definition
- for (const def of this.getBlockDefinitionGeometry(definition)) {
+ for (const def of this.getBlockDefinitionGeometry(definition) as SpeckleObject[]) {
const ref = await this.resolveReference(def)
await this.parseInstanceDefinitionGeometry(obj, ref, node)
}
const elements = this.getElementsValue(obj)
if (elements) {
- for (const element of elements) {
+ for (const element of elements as SpeckleObject[]) {
const elementObj = await this.resolveReference(element)
this.parseInstanceElement(obj, elementObj, node)
}
}
}
- private async RevitInstanceToNode(obj, node) {
- const definition = await this.resolveReference(obj.definition)
+ private async RevitInstanceToNode(obj: SpeckleObject, node: TreeNode) {
+ const definition = await this.resolveReference(obj.definition as SpeckleObject)
node.model.raw.definition = definition
await this.parseInstanceDefinitionGeometry(obj, definition, node)
const elements = this.getElementsValue(obj)
if (elements) {
- for (const element of elements) {
+ for (const element of elements as SpeckleObject[]) {
const elementObj = await this.resolveReference(element)
this.parseInstanceElement(obj, elementObj, node)
}
}
}
- private async PointcloudToNode(obj, node) {
+ private async PointcloudToNode(obj: SpeckleObject, node: TreeNode) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
node.model.raw.points = await this.dechunk(obj.points)
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
node.model.raw.colors = await this.dechunk(obj.colors)
}
- private async BrepToNode(obj, node) {
+ private async BrepToNode(obj: SpeckleObject, node: TreeNode) {
try {
if (!obj) return
@@ -503,7 +539,7 @@ export default class SpeckleConverter {
if (Array.isArray(displayValue)) displayValue = displayValue[0] //Just take the first display value for now (not ideal)
if (!displayValue) return
- const ref = await this.resolveReference(displayValue)
+ const ref = await this.resolveReference(displayValue as SpeckleObject)
const nestedNode: TreeNode = this.tree.parse({
id: node.model.instanced
? this.getCompoundId(ref.id, this.instanceCounter++)
@@ -531,32 +567,37 @@ export default class SpeckleConverter {
}
}
- private async MeshToNode(obj, node) {
+ private async MeshToNode(obj: SpeckleObject, node: TreeNode) {
if (!obj) return
- if (!obj.vertices || obj.vertices.length === 0) {
+ if (!obj.vertices || (obj.vertices as Array).length === 0) {
Logger.warn(
`Object id ${obj.id} of type ${obj.speckle_type} has no vertex position data and will be ignored`
)
return
}
- if (!obj.faces || obj.faces.length === 0) {
+ if (!obj.faces || (obj.faces as Array).length === 0) {
Logger.warn(
`Object id ${obj.id} of type ${obj.speckle_type} has no face data and will be ignored`
)
return
}
-
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
node.model.raw.vertices = await this.dechunk(obj.vertices)
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
node.model.raw.faces = await this.dechunk(obj.faces)
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
node.model.raw.colors = await this.dechunk(obj.colors)
}
- private async TextToNode(obj, node) {
+ private async TextToNode(_obj: SpeckleObject, _node: TreeNode) {
return
}
- private async DimensionToNode(obj, node) {
- const displayValues = [...this.getDisplayValue(obj)]
+ private async DimensionToNode(obj: SpeckleObject, node: TreeNode) {
+ const displayValues = [...(this.getDisplayValue(obj) as SpeckleObject[])]
for (const displayValue of displayValues) {
const childNode: TreeNode = this.tree.parse({
id: this.getNodeId(displayValue),
@@ -598,28 +639,36 @@ export default class SpeckleConverter {
await this.convertToNode(textObj, textNode)
}
- private async PointToNode(obj, node) {
+ private async PointToNode(_obj: SpeckleObject, _node: TreeNode) {
return
}
- private async LineToNode(obj, node) {
+ private async LineToNode(_obj: SpeckleObject, _node: TreeNode) {
return
}
- private async PolylineToNode(obj, node) {
- node.model.raw.value = await this.dechunk(obj.value)
+ private async PolylineToNode(obj: SpeckleObject, node: TreeNode) {
+ node.model.raw.value = await this.dechunk(
+ obj.value as Array<{ referencedId: string }>
+ )
}
- private async BoxToNode(obj, node) {
+ private async BoxToNode(_obj: SpeckleObject, _node: TreeNode) {
return
}
- private async PolycurveToNode(obj, node) {
+ private async PolycurveToNode(obj: SpeckleObject, node: TreeNode) {
node.model.nestedNodes = []
- for (let i = 0; i < obj.segments.length; i++) {
- let element = obj.segments[i]
+ for (
+ let i = 0;
+ i < (obj as unknown as { segments: SpeckleObject[] }).segments.length;
+ i++
+ ) {
+ let element = (obj as unknown as { segments: SpeckleObject[] }).segments[
+ i
+ ] as SpeckleObject
/** Not a big fan of this... */
if (!this.directNodeConverterExists(element)) {
- element = this.getDisplayValue(element)
+ element = this.getDisplayValue(element) as SpeckleObject
if (element.referencedId) {
element = await this.resolveReference(element)
}
@@ -636,8 +685,8 @@ export default class SpeckleConverter {
}
}
- private async CurveToNode(obj, node) {
- let displayValue = this.getDisplayValue(obj)
+ private async CurveToNode(obj: SpeckleObject, node: TreeNode) {
+ let displayValue: SpeckleObject = this.getDisplayValue(obj) as SpeckleObject
if (!displayValue) {
Logger.warn(
`Object ${obj.id} of type ${obj.speckle_type} has no display value and will be ignored`
@@ -645,7 +694,7 @@ export default class SpeckleConverter {
return
}
node.model.nestedNodes = []
- displayValue = await this.resolveReference(obj.displayValue)
+ displayValue = await this.resolveReference(obj.displayValue as SpeckleObject)
displayValue.units = displayValue.units || obj.units
const nestedNode: TreeNode = this.tree.parse({
id: this.getNodeId(displayValue),
@@ -658,15 +707,15 @@ export default class SpeckleConverter {
node.model.nestedNodes.push(nestedNode)
}
- private async CircleToNode(obj, node) {
+ private async CircleToNode(_obj: SpeckleObject, _node: TreeNode) {
return
}
- private async ArcToNode(obj, node) {
+ private async ArcToNode(_obj: SpeckleObject, _node: TreeNode) {
return
}
- private async EllipseToNode(obj, node) {
+ private async EllipseToNode(_obj: SpeckleObject, _node: TreeNode) {
return
}
}
diff --git a/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts b/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts
index af532ef525..82efd25ba0 100644
--- a/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts
+++ b/packages/viewer/src/modules/loaders/Speckle/SpeckleGeometryConverter.ts
@@ -1,7 +1,7 @@
-import { Geometry, GeometryData } from '../../converter/Geometry'
+import { Geometry, type GeometryData } from '../../converter/Geometry'
import MeshTriangulationHelper from '../../converter/MeshTriangulationHelper'
import { getConversionFactor } from '../../converter/Units'
-import { NodeData } from '../../tree/WorldTree'
+import { type NodeData } from '../../tree/WorldTree'
import { Box3, EllipseCurve, Matrix4, Vector2, Vector3 } from 'three'
import Logger from 'js-logger'
import { GeometryConverter, SpeckleType } from '../GeometryConverter'
@@ -10,25 +10,22 @@ export class SpeckleGeometryConverter extends GeometryConverter {
public typeLookupTable: { [type: string]: SpeckleType } = {}
public getSpeckleType(node: NodeData): SpeckleType {
- let rawType = 'Base'
- if (node.raw.data)
- rawType = node.raw.data.speckle_type ? node.raw.data.speckle_type : 'Base'
- else rawType = node.raw.speckle_type ? node.raw.speckle_type : 'Base'
+ const rawType = node.raw.speckle_type ? node.raw.speckle_type : 'Base'
const lookup = this.typeLookupTable[rawType]
if (lookup) {
return lookup
}
- let typeRet = SpeckleType.Unknown
- let typeChain = []
+ let typeRet: SpeckleType = SpeckleType.Unknown
+ let typeChain: string[] = []
typeChain = rawType.split(':').reverse()
typeChain = typeChain.map((value: string) => {
return value.split('.').reverse()[0]
})
for (const type of typeChain) {
if (type in SpeckleType) {
- typeRet = type
+ typeRet = type as SpeckleType
break
}
}
@@ -36,7 +33,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
return typeRet
}
- public convertNodeToGeometryData(node: NodeData): GeometryData {
+ public convertNodeToGeometryData(node: NodeData): GeometryData | null {
const type = this.getSpeckleType(node)
switch (type) {
case SpeckleType.BlockInstance:
@@ -168,13 +165,13 @@ export class SpeckleGeometryConverter extends GeometryConverter {
}
/** BLOCK INSTANCE */
- private BlockInstanceToGeometryData(node: NodeData): GeometryData {
+ private BlockInstanceToGeometryData(node: NodeData): GeometryData | null {
node
return null
}
/** REVIT INSTANCE */
- private RevitInstanceToGeometryData(node: NodeData): GeometryData {
+ private RevitInstanceToGeometryData(node: NodeData): GeometryData | null {
node
return null
}
@@ -182,7 +179,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* POINT CLOUD
*/
- private PointcloudToGeometryData(node: NodeData) {
+ private PointcloudToGeometryData(node: NodeData): GeometryData | null {
const conversionFactor = getConversionFactor(node.raw.units)
const vertices = node.instanced ? node.raw.points.slice() : node.raw.points
@@ -216,7 +213,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* BREP
*/
- private BrepToGeometryData(node) {
+ private BrepToGeometryData(node: NodeData): GeometryData | null {
/** Breps don't (currently) have inherent geometryic description in the viewer. They are replaced
* by their mesh display values
*/
@@ -227,14 +224,14 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* MESH
*/
- private MeshToGeometryData(node: NodeData): GeometryData {
- if (!node.raw) return
+ private MeshToGeometryData(node: NodeData): GeometryData | null {
+ if (!node.raw) return null
const conversionFactor = getConversionFactor(node.raw.units)
const indices = []
- if (!node.raw.vertices) return
- if (!node.raw.faces) return
+ if (!node.raw.vertices) return null
+ if (!node.raw.faces) return null
const vertices = node.raw.vertices
const faces = node.raw.faces
@@ -293,7 +290,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* TEXT
*/
- private TextToGeometryData(node: NodeData): GeometryData {
+ private TextToGeometryData(node: NodeData): GeometryData | null {
const conversionFactor = getConversionFactor(node.raw.units)
const plane = node.raw.plane
const position = new Vector3(plane.origin.x, plane.origin.y, plane.origin.z)
@@ -316,11 +313,17 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* POINT
*/
- private PointToGeometryData(node: NodeData): GeometryData {
+ private PointToGeometryData(node: NodeData): GeometryData | null {
const conversionFactor = getConversionFactor(node.raw.units)
return {
attributes: {
- POSITION: this.PointToFloatArray(node.raw)
+ POSITION: this.PointToFloatArray(
+ node.raw as { value: Array; units: string } & {
+ x: number
+ y: number
+ z: number
+ }
+ )
},
bakeTransform: new Matrix4().makeScale(
conversionFactor,
@@ -334,7 +337,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* LINE
*/
- private LineToGeometryData(node: NodeData): GeometryData {
+ private LineToGeometryData(node: NodeData): GeometryData | null {
const conversionFactor = getConversionFactor(node.raw.units)
return {
attributes: {
@@ -354,7 +357,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* POLYLINE
*/
- private PolylineToGeometryData(node: NodeData): GeometryData {
+ private PolylineToGeometryData(node: NodeData): GeometryData | null {
const conversionFactor = getConversionFactor(node.raw.units)
if (node.raw.closed)
@@ -375,7 +378,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* BOX
*/
- private BoxToGeometryData(node: NodeData) {
+ private BoxToGeometryData(node: NodeData): GeometryData | null {
/**
* Right, so we're cheating here a bit. We're using three's box geometry
* to get the vertices and indices. Normally we could(should) do that by hand
@@ -436,25 +439,30 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* POLYCURVE
*/
- private PolycurveToGeometryData(node: NodeData): GeometryData {
+ private PolycurveToGeometryData(node: NodeData): GeometryData | null {
+ if (!node.nestedNodes || node.nestedNodes.length === 0) {
+ return null
+ }
const buffers = []
+
for (let i = 0; i < node.nestedNodes.length; i++) {
const element = node.nestedNodes[i].model
const conv = this.convertNodeToGeometryData(element)
buffers.push(conv)
}
- return Geometry.mergeGeometryData(buffers)
+ return Geometry.mergeGeometryData(buffers as GeometryData[])
}
/**
* CURVE
*/
- private CurveToGeometryData(node) {
- if (node.nestedNodes.length === 0) {
+ private CurveToGeometryData(node: NodeData): GeometryData | null {
+ if (!node.nestedNodes || node.nestedNodes.length === 0) {
return null
}
const polylineGeometry = this.PolylineToGeometryData(node.nestedNodes[0].model)
+ if (!polylineGeometry || !polylineGeometry.attributes) return null
return {
attributes: {
POSITION: polylineGeometry.attributes.POSITION
@@ -467,7 +475,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* CIRCLE
*/
- private CircleToGeometryData(node: NodeData) {
+ private CircleToGeometryData(node: NodeData): GeometryData | null {
const conversionFactor = getConversionFactor(node.raw.units)
const curveSegmentLength = 0.1 * conversionFactor
const points = this.getCircularCurvePoints(
@@ -489,7 +497,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* ARC
*/
- private ArcToGeometryData(node: NodeData) {
+ private ArcToGeometryData(node: NodeData): GeometryData | null {
const origin = new Vector3(
node.raw.plane.origin.x,
node.raw.plane.origin.y,
@@ -591,7 +599,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
/**
* ELLIPSE
*/
- private EllipseToGeometryData(node: NodeData) {
+ private EllipseToGeometryData(node: NodeData): GeometryData | null {
const conversionFactor = getConversionFactor(node.raw.units)
const center = new Vector3(
@@ -639,8 +647,24 @@ export class SpeckleGeometryConverter extends GeometryConverter {
*/
private getCircularCurvePoints(
- plane,
- radius,
+ plane: {
+ xdir: { value: Array; units: string } & {
+ x: number
+ y: number
+ z: number
+ }
+ ydir: { value: Array; units: string } & {
+ x: number
+ y: number
+ z: number
+ }
+ origin: { value: Array; units: string } & {
+ x: number
+ y: number
+ z: number
+ }
+ },
+ radius: number,
startAngle = 0,
endAngle = 2 * Math.PI,
res = 0.1
@@ -673,7 +697,10 @@ export class SpeckleGeometryConverter extends GeometryConverter {
return points
}
- private PointToVector3(obj, scale = true) {
+ private PointToVector3(
+ obj: { value: Array; units: string } & { x: number; y: number; z: number },
+ scale = true
+ ) {
const conversionFactor = scale ? getConversionFactor(obj.units) : 1
let v = null
if (obj.value) {
@@ -694,7 +721,9 @@ export class SpeckleGeometryConverter extends GeometryConverter {
return v
}
- private PointToFloatArray(obj) {
+ private PointToFloatArray(
+ obj: { value: Array; units: string } & { x: number; y: number; z: number }
+ ) {
if (obj.value) {
return [obj.value[0], obj.value[1], obj.value[2]]
} else {
@@ -704,7 +733,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
private FlattenVector3Array(input: Vector3[] | Vector2[]): number[] {
const output = new Array(input.length * 3)
- const vBuff = []
+ const vBuff: Array = []
for (let k = 0, l = 0; k < input.length; k++, l += 3) {
input[k].toArray(vBuff)
output[l] = vBuff[0]
@@ -733,7 +762,7 @@ export class SpeckleGeometryConverter extends GeometryConverter {
return colors
}
- private srgbToLinear(x) {
+ private srgbToLinear(x: number) {
if (x <= 0) return 0
else if (x >= 1) return 1
else if (x < 0.04045) return x / 12.92
diff --git a/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts b/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts
index d6a90ca496..85c9c7c76c 100644
--- a/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts
+++ b/packages/viewer/src/modules/loaders/Speckle/SpeckleLoader.ts
@@ -3,14 +3,13 @@ import SpeckleConverter from './SpeckleConverter'
import { Loader, LoaderEvent } from '../Loader'
import ObjectLoader from '@speckle/objectloader'
import { SpeckleGeometryConverter } from './SpeckleGeometryConverter'
-import { WorldTree } from '../../..'
+import { WorldTree, type SpeckleObject } from '../../..'
import { AsyncPause } from '../../World'
export class SpeckleLoader extends Loader {
private loader: ObjectLoader
private converter: SpeckleConverter
private tree: WorldTree
- private priority: number = 1
private isCancelled = false
private isFinished = false
@@ -25,17 +24,15 @@ export class SpeckleLoader extends Loader {
constructor(
targetTree: WorldTree,
resource: string,
- authToken: string,
+ authToken?: string,
enableCaching?: boolean,
- resourceData?: string | ArrayBuffer,
- priority: number = 1
+ resourceData?: string | ArrayBuffer
) {
super(resource, resourceData)
this.tree = targetTree
- this.priority = priority
- let token = null
+ let token = undefined
try {
- token = authToken || localStorage.getItem('AuthToken')
+ token = authToken || (localStorage.getItem('AuthToken') as string | undefined)
} catch (error) {
// Accessing localStorage may throw when executing on sandboxed document, ignore.
}
@@ -90,15 +87,19 @@ export class SpeckleLoader extends Loader {
return Promise.resolve(false)
}
if (first) {
- firstObjectPromise = this.converter.traverse(this._resource, obj, async () => {
- viewerLoads++
- pause.tick(100)
- if (pause.needsWait) {
- await pause.wait(16)
+ firstObjectPromise = this.converter.traverse(
+ this._resource,
+ obj as SpeckleObject,
+ async () => {
+ viewerLoads++
+ pause.tick(100)
+ if (pause.needsWait) {
+ await pause.wait(16)
+ }
}
- })
+ )
first = false
- total = obj.totalChildrenCount
+ total = obj.totalChildrenCount as number
}
current++
this.emit(LoaderEvent.LoadProgress, {
@@ -147,6 +148,7 @@ export class SpeckleLoader extends Loader {
}
dispose() {
+ super.dispose()
this.loader.dispose()
}
}
diff --git a/packages/viewer/src/modules/materials/Materials.ts b/packages/viewer/src/modules/materials/Materials.ts
index a6a216975f..a3f6ded614 100644
--- a/packages/viewer/src/modules/materials/Materials.ts
+++ b/packages/viewer/src/modules/materials/Materials.ts
@@ -1,6 +1,6 @@
import { Color, DoubleSide, FrontSide, Material, Texture, Vector2 } from 'three'
import { GeometryType } from '../batching/Batch'
-import { TreeNode } from '../tree/WorldTree'
+import { type TreeNode } from '../tree/WorldTree'
import { NodeRenderView } from '../tree/NodeRenderView'
import SpeckleLineMaterial from './SpeckleLineMaterial'
import SpeckleStandardMaterial from './SpeckleStandardMaterial'
@@ -13,7 +13,7 @@ import SpeckleGhostMaterial from './SpeckleGhostMaterial'
import SpeckleTextMaterial from './SpeckleTextMaterial'
import { SpeckleMaterial } from './SpeckleMaterial'
import SpecklePointColouredMaterial from './SpecklePointColouredMaterial'
-import { Asset, AssetType, MaterialOptions } from '../../IViewer'
+import { type Asset, AssetType, type MaterialOptions } from '../../IViewer'
const defaultGradient: Asset = {
id: 'defaultGradient',
@@ -44,6 +44,7 @@ export enum FilterMaterialType {
HIDDEN
}
+/** TO DO: This still sucks */
export interface FilterMaterial {
filterType: FilterMaterialType
rampIndex?: number
@@ -62,26 +63,26 @@ export default class Materials {
public static readonly UNIFORM_VECTORS_USED = 33
public static readonly DEFAULT_ARTIFICIAL_ROUGHNESS = 0.6 /** The inverse of "shininess" */
private readonly materialMap: { [hash: number]: Material } = {}
- private meshGhostMaterial: Material = null
- private meshGradientMaterial: Material = null
- private meshTransparentGradientMaterial: Material = null
- private meshColoredMaterial: Material = null
- private meshTransparentColoredMaterial: Material = null
- private meshHiddenMaterial: Material = null
+ private meshGhostMaterial: Material
+ private meshGradientMaterial: Material
+ private meshTransparentGradientMaterial: Material
+ private meshColoredMaterial: Material
+ private meshTransparentColoredMaterial: Material
+ private meshHiddenMaterial: Material
- private lineGhostMaterial: Material = null
- private lineColoredMaterial: Material = null
- private lineHiddenMaterial: Material = null
+ private lineGhostMaterial: Material
+ private lineColoredMaterial: Material
+ private lineHiddenMaterial: Material
- private pointGhostMaterial: Material = null
- private pointCloudColouredMaterial: Material = null
- private pointCloudGradientMaterial: Material = null
+ private pointGhostMaterial: Material
+ private pointCloudColouredMaterial: Material
+ private pointCloudGradientMaterial: Material
- private textGhostMaterial: Material = null
- private textColoredMaterial: Material = null
- private textHiddenMaterial: Material = null
+ private textGhostMaterial: Material
+ private textColoredMaterial: Material
+ private textHiddenMaterial: Material
- private defaultGradientTextureData: ImageData = null
+ private defaultGradientTextureData!: ImageData
private static readonly NullRenderMaterialHash = this.hashCode(
GeometryType.MESH.toString()
@@ -112,11 +113,11 @@ export default class Materials {
)
public static renderMaterialFromNode(
- materialNode: TreeNode,
- geometryNode: TreeNode
- ): RenderMaterial {
+ materialNode: TreeNode | null,
+ geometryNode: TreeNode | null
+ ): RenderMaterial | null {
if (!materialNode) return null
- let renderMaterial: RenderMaterial = null
+ let renderMaterial: RenderMaterial | null = null
if (materialNode.model.raw.renderMaterial) {
renderMaterial = {
id: materialNode.model.raw.renderMaterial.id,
@@ -128,15 +129,17 @@ export default class Materials {
roughness: materialNode.model.raw.renderMaterial.roughness,
metalness: materialNode.model.raw.renderMaterial.metalness,
vertexColors:
- geometryNode.model.raw.colors && geometryNode.model.raw.colors.length > 0
+ geometryNode &&
+ geometryNode.model.raw.colors &&
+ geometryNode.model.raw.colors.length > 0
}
}
return renderMaterial
}
- public static displayStyleFromNode(node: TreeNode): DisplayStyle {
+ public static displayStyleFromNode(node: TreeNode | null): DisplayStyle | null {
if (!node) return null
- let displayStyle: DisplayStyle = null
+ let displayStyle: DisplayStyle | null = null
if (node.model.raw.displayStyle) {
/** If there are no units specified, we ignore the line width value */
let lineWeight = node.model.raw.displayStyle.lineweight || 0
@@ -189,21 +192,32 @@ export default class Materials {
return plm
}
- private static hashCode(s: string) {
- let h
+ private static hashCode(s: string): number {
+ let h = 0
for (let i = 0; i < s.length; i++) h = (Math.imul(31, h) + s.charCodeAt(i)) | 0
return h
}
- public static isMaterialInstance(material): material is Material {
+ public static isMaterialInstance(
+ material: Material | FilterMaterial | RenderMaterial | DisplayStyle
+ ): material is Material {
return material instanceof Material
}
- public static isFilterMaterial(material): material is FilterMaterial {
+ public static isFilterMaterial(
+ material: Material | FilterMaterial | RenderMaterial | DisplayStyle
+ ): material is FilterMaterial {
return 'filterType' in material
}
- public static isRendeMaterial(materialData): materialData is RenderMaterial {
+ public static isRendeMaterial(
+ materialData:
+ | Material
+ | FilterMaterial
+ | RenderMaterial
+ | DisplayStyle
+ | MaterialOptions
+ ): materialData is RenderMaterial {
return (
'color' in materialData &&
'opacity' in materialData &&
@@ -213,14 +227,21 @@ export default class Materials {
)
}
- public static isDisplayStyle(materialData): materialData is DisplayStyle {
+ public static isDisplayStyle(
+ materialData:
+ | Material
+ | FilterMaterial
+ | RenderMaterial
+ | DisplayStyle
+ | MaterialOptions
+ ): materialData is DisplayStyle {
return 'color' in materialData && 'lineWeight' in materialData
}
public static getMaterialHash(
renderView: NodeRenderView,
- materialData?: RenderMaterial | DisplayStyle | MaterialOptions
- ) {
+ materialData?: RenderMaterial | DisplayStyle | MaterialOptions | null
+ ): number {
if (!materialData) {
materialData =
renderView.renderData.renderMaterial || renderView.renderData.displayStyle
@@ -733,7 +754,7 @@ export default class Materials {
public getMaterial(
hash: number,
- material: RenderMaterial | DisplayStyle,
+ material: RenderMaterial | DisplayStyle | null,
type: GeometryType
): Material {
let mat
@@ -742,7 +763,7 @@ export default class Materials {
mat = this.getMeshMaterial(hash, material as RenderMaterial)
break
case GeometryType.LINE:
- mat = this.getLineMaterial(hash, material)
+ mat = this.getLineMaterial(hash, material as DisplayStyle)
break
case GeometryType.POINT:
mat = this.getPointMaterial(hash, material as RenderMaterial)
@@ -751,7 +772,7 @@ export default class Materials {
mat = this.getPointCloudMaterial(hash, material as RenderMaterial)
break
case GeometryType.TEXT:
- mat = this.getTextMaterial(hash, material)
+ mat = this.getTextMaterial(hash, material as DisplayStyle)
break
}
// }
@@ -801,7 +822,7 @@ export default class Materials {
public getGhostMaterial(
renderView: NodeRenderView,
filterMaterial?: FilterMaterial
- ): Material {
+ ): Material | null {
filterMaterial
switch (renderView.geometryType) {
case GeometryType.MESH:
@@ -820,7 +841,7 @@ export default class Materials {
public getGradientMaterial(
renderView: NodeRenderView,
filterMaterial?: FilterMaterial
- ): Material {
+ ): Material | null {
switch (renderView.geometryType) {
case GeometryType.MESH: {
const material = renderView.transparent
@@ -858,7 +879,7 @@ export default class Materials {
public getColoredMaterial(
renderView: NodeRenderView,
filterMaterial?: FilterMaterial
- ): Material {
+ ): Material | null {
switch (renderView.geometryType) {
case GeometryType.MESH: {
const material = renderView.transparent
@@ -896,7 +917,7 @@ export default class Materials {
public getHiddenMaterial(
renderView: NodeRenderView,
filterMaterial?: FilterMaterial
- ): Material {
+ ): Material | null {
filterMaterial
switch (renderView.geometryType) {
case GeometryType.MESH:
@@ -915,8 +936,8 @@ export default class Materials {
public getFilterMaterial(
renderView: NodeRenderView,
filterMaterial: FilterMaterial
- ): Material {
- let retMaterial: Material
+ ): Material | null {
+ let retMaterial: Material | null = null
switch (filterMaterial.filterType) {
case FilterMaterialType.GHOST:
retMaterial = this.getGhostMaterial(renderView, filterMaterial)
@@ -934,7 +955,7 @@ export default class Materials {
/** There's a bug in three.js where it checks for the length of the planes without checking if they exist first
* It's been allegedly fixed in a later version but until we update we'll just assing an empty array
*/
- retMaterial.clippingPlanes = []
+ if (retMaterial) retMaterial.clippingPlanes = []
return retMaterial
}
@@ -948,7 +969,7 @@ export default class Materials {
public getFilterMaterialOptions(
filterMaterial: FilterMaterial
- ): FilterMaterialOptions {
+ ): FilterMaterialOptions | null {
switch (filterMaterial.filterType) {
case FilterMaterialType.COLORED:
return {
@@ -973,7 +994,8 @@ export default class Materials {
rampIndexColor:
filterMaterial.rampIndexColor !== undefined
? filterMaterial.rampIndexColor
- : new Color()
+ : filterMaterial.rampIndex
+ ? new Color()
.setRGB(
this.defaultGradientTextureData.data[
Math.floor(
@@ -998,7 +1020,8 @@ export default class Materials {
2
] / 255
)
- .convertSRGBToLinear(),
+ .convertSRGBToLinear()
+ : undefined,
rampTexture: filterMaterial.rampTexture
? filterMaterial.rampTexture
: this.meshGradientMaterial.userData.gradientRamp.value,
@@ -1006,6 +1029,8 @@ export default class Materials {
? filterMaterial.rampTexture.image.width
: this.meshGradientMaterial.userData.gradientRamp.value.image.width
}
+ default:
+ return null
}
}
diff --git a/packages/viewer/src/modules/materials/SpeckleBasicMaterial.ts b/packages/viewer/src/modules/materials/SpeckleBasicMaterial.ts
index 725618ace0..22576e17aa 100644
--- a/packages/viewer/src/modules/materials/SpeckleBasicMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpeckleBasicMaterial.ts
@@ -1,18 +1,28 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable camelcase */
import { speckleBasicVert } from './shaders/speckle-basic-vert'
import { speckleBasicFrag } from './shaders/speckle-basic-frag'
-import { ShaderLib, Vector3, Material, IUniform, Vector2 } from 'three'
+import {
+ ShaderLib,
+ Vector3,
+ Material,
+ type IUniform,
+ Vector2,
+ type MeshBasicMaterialParameters,
+ Scene,
+ Camera,
+ BufferGeometry,
+ Object3D
+} from 'three'
import { Matrix4 } from 'three'
-import { Geometry } from '../converter/Geometry'
-import { ExtendedMeshBasicMaterial, Uniforms } from './SpeckleMaterial'
+import { ExtendedMeshBasicMaterial, type Uniforms } from './SpeckleMaterial'
+import type { SpeckleWebGLRenderer } from '../objects/SpeckleWebGLRenderer'
class SpeckleBasicMaterial extends ExtendedMeshBasicMaterial {
protected static readonly matBuff: Matrix4 = new Matrix4()
protected static readonly vecBuff: Vector2 = new Vector2()
- private _billboardPixelHeight: number
+ private _billboardPixelHeight!: number
protected get vertexProgram(): string {
return speckleBasicVert
@@ -43,7 +53,7 @@ class SpeckleBasicMaterial extends ExtendedMeshBasicMaterial {
this._billboardPixelHeight = value
}
- constructor(parameters, defines = []) {
+ constructor(parameters: MeshBasicMaterialParameters, defines: string[] = []) {
super(parameters)
this.init(defines)
}
@@ -53,7 +63,7 @@ class SpeckleBasicMaterial extends ExtendedMeshBasicMaterial {
return this.constructor.name
}
- public copy(source) {
+ public copy(source: Material) {
super.copy(source)
this.copyFrom(source)
return this
@@ -69,8 +79,14 @@ class SpeckleBasicMaterial extends ExtendedMeshBasicMaterial {
}
/** Called by three.js render loop */
- public onBeforeRender(_this, scene, camera, geometry, object, group) {
- if (this.defines['BILLBOARD_FIXED']) {
+ public onBeforeRender(
+ _this: SpeckleWebGLRenderer,
+ _scene: Scene,
+ camera: Camera,
+ _geometry: BufferGeometry,
+ object: Object3D
+ ) {
+ if (this.defines && this.defines['BILLBOARD_FIXED']) {
const resolution = _this.getDrawingBufferSize(SpeckleBasicMaterial.vecBuff)
SpeckleBasicMaterial.vecBuff.set(
(this._billboardPixelHeight / resolution.x) * 2,
@@ -81,7 +97,7 @@ class SpeckleBasicMaterial extends ExtendedMeshBasicMaterial {
this.userData.invProjection.value.copy(SpeckleBasicMaterial.matBuff)
}
- if (this.defines['USE_RTE']) {
+ if (this.defines && this.defines['USE_RTE']) {
object.modelViewMatrix.copy(_this.RTEBuffers.rteViewModelMatrix)
this.userData.uViewer_low.value.copy(_this.RTEBuffers.viewerLow)
this.userData.uViewer_high.value.copy(_this.RTEBuffers.viewerHigh)
diff --git a/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts b/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts
index be23afb64b..50cbe81982 100644
--- a/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts
@@ -1,17 +1,21 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable camelcase */
import { speckleDepthVert } from './shaders/speckle-depth-vert'
import { speckleDepthFrag } from './shaders/speckle-depth-frag'
-import { ShaderLib, Vector3, IUniform } from 'three'
+import {
+ BufferGeometry,
+ Camera,
+ Object3D,
+ Scene,
+ ShaderLib,
+ Vector3,
+ type IUniform,
+ type MeshDepthMaterialParameters
+} from 'three'
import { Matrix4, Material } from 'three'
-import { ExtendedMeshDepthMaterial, Uniforms } from './SpeckleMaterial'
+import { ExtendedMeshDepthMaterial, type Uniforms } from './SpeckleMaterial'
+import type { SpeckleWebGLRenderer } from '../objects/SpeckleWebGLRenderer'
class SpeckleDepthMaterial extends ExtendedMeshDepthMaterial {
- private static readonly matBuff: Matrix4 = new Matrix4()
- private static readonly vecBuff0: Vector3 = new Vector3()
- private static readonly vecBuff1: Vector3 = new Vector3()
- private static readonly vecBuff2: Vector3 = new Vector3()
-
protected get vertexProgram(): string {
return speckleDepthVert
}
@@ -37,7 +41,7 @@ class SpeckleDepthMaterial extends ExtendedMeshDepthMaterial {
}
}
- constructor(parameters, defines = []) {
+ constructor(parameters: MeshDepthMaterialParameters, defines: string[] = []) {
super(parameters)
this.init(defines)
}
@@ -47,7 +51,7 @@ class SpeckleDepthMaterial extends ExtendedMeshDepthMaterial {
return this.constructor.name
}
- public copy(source) {
+ public copy(source: Material) {
super.copy(source)
this.copyFrom(source)
return this
@@ -62,8 +66,14 @@ class SpeckleDepthMaterial extends ExtendedMeshDepthMaterial {
/** Another note here, this will NOT get called by three when rendering shadowmaps. We update the uniforms manually
* inside SpeckleRenderer for shadowmaps
*/
- onBeforeRender(_this, scene, camera, geometry, object, group) {
- if (this.defines['USE_RTE']) {
+ onBeforeRender(
+ _this: SpeckleWebGLRenderer,
+ _scene: Scene,
+ _camera: Camera,
+ _geometry: BufferGeometry,
+ object: Object3D
+ ) {
+ if (this.defines && this.defines['USE_RTE']) {
object.modelViewMatrix.copy(_this.RTEBuffers.rteViewModelMatrix)
this.userData.uViewer_low.value.copy(_this.RTEBuffers.viewerLow)
this.userData.uViewer_high.value.copy(_this.RTEBuffers.viewerHigh)
diff --git a/packages/viewer/src/modules/materials/SpeckleDisplaceMaterial.ts b/packages/viewer/src/modules/materials/SpeckleDisplaceMaterial.ts
index 933baf12ae..e8da58247d 100644
--- a/packages/viewer/src/modules/materials/SpeckleDisplaceMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpeckleDisplaceMaterial.ts
@@ -1,8 +1,8 @@
import { speckleDisplaceVert } from './shaders/speckle-displace.vert'
import { speckleDisplaceFrag } from './shaders/speckle-displace-frag'
-import { Material, Vector2 } from 'three'
+import { Material, Vector2, type MeshBasicMaterialParameters } from 'three'
import SpeckleBasicMaterial from './SpeckleBasicMaterial'
-import { Uniforms } from './SpeckleMaterial'
+import { type Uniforms } from './SpeckleMaterial'
class SpeckleDisplaceMaterial extends SpeckleBasicMaterial {
protected get vertexProgram(): string {
@@ -17,7 +17,7 @@ class SpeckleDisplaceMaterial extends SpeckleBasicMaterial {
return { ...super.uniformsDef, size: new Vector2(), displacement: 0 }
}
- constructor(parameters, defines) {
+ constructor(parameters: MeshBasicMaterialParameters, defines: string[] = []) {
super(parameters, defines)
}
diff --git a/packages/viewer/src/modules/materials/SpeckleGhostMaterial.ts b/packages/viewer/src/modules/materials/SpeckleGhostMaterial.ts
index 1ea55edf04..f7adb27a3e 100644
--- a/packages/viewer/src/modules/materials/SpeckleGhostMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpeckleGhostMaterial.ts
@@ -1,6 +1,7 @@
import { speckleGhostVert } from './shaders/speckle-ghost-vert'
import { speckleGhostFrag } from './shaders/speckle-ghost-frag'
import SpeckleBasicMaterial from './SpeckleBasicMaterial'
+import type { MeshBasicMaterialParameters } from 'three'
class SpeckleGhostMaterial extends SpeckleBasicMaterial {
protected get vertexProgram(): string {
@@ -11,7 +12,10 @@ class SpeckleGhostMaterial extends SpeckleBasicMaterial {
return speckleGhostFrag
}
- constructor(parameters, defines = ['USE_RTE']) {
+ constructor(
+ parameters: MeshBasicMaterialParameters,
+ defines: string[] = ['USE_RTE']
+ ) {
super(parameters, defines)
}
}
diff --git a/packages/viewer/src/modules/materials/SpeckleLineMaterial.ts b/packages/viewer/src/modules/materials/SpeckleLineMaterial.ts
index 05a67c9adc..ed2d5259a3 100644
--- a/packages/viewer/src/modules/materials/SpeckleLineMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpeckleLineMaterial.ts
@@ -1,10 +1,22 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable camelcase */
import { speckleLineVert } from './shaders/speckle-line-vert'
import { speckleLineFrag } from './shaders/speckle-line-frag'
-import { ShaderLib, Vector3, IUniform, Material } from 'three'
-import { ExtendedLineMaterial, Uniforms } from './SpeckleMaterial'
-import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
+import {
+ ShaderLib,
+ Vector3,
+ type IUniform,
+ Material,
+ Scene,
+ Camera,
+ BufferGeometry,
+ Object3D
+} from 'three'
+import { ExtendedLineMaterial, type Uniforms } from './SpeckleMaterial'
+import {
+ LineMaterial,
+ type LineMaterialParameters
+} from 'three/examples/jsm/lines/LineMaterial.js'
+import type { SpeckleWebGLRenderer } from '../objects/SpeckleWebGLRenderer'
class SpeckleLineMaterial extends ExtendedLineMaterial {
protected get vertexProgram(): string {
@@ -32,7 +44,7 @@ class SpeckleLineMaterial extends ExtendedLineMaterial {
this.needsUpdate = true
}
- constructor(parameters, defines = ['USE_RTE']) {
+ constructor(parameters: LineMaterialParameters, defines = ['USE_RTE']) {
super(parameters)
this.init(defines)
}
@@ -42,7 +54,7 @@ class SpeckleLineMaterial extends ExtendedLineMaterial {
return this.constructor.name
}
- public copy(source) {
+ public copy(source: Material) {
super.copy(source)
this.copyFrom(source)
return this
@@ -56,7 +68,13 @@ class SpeckleLineMaterial extends ExtendedLineMaterial {
to.userData.pixelThreshold.value = from.userData.pixelThreshold.value
}
- onBeforeRender(_this, scene, camera, geometry, object, group) {
+ onBeforeRender(
+ _this: SpeckleWebGLRenderer,
+ _scene: Scene,
+ _camera: Camera,
+ _geometry: BufferGeometry,
+ object: Object3D
+ ) {
object.modelViewMatrix.copy(_this.RTEBuffers.rteViewModelMatrix)
this.userData.uViewer_low.value.copy(_this.RTEBuffers.viewerLow)
this.userData.uViewer_high.value.copy(_this.RTEBuffers.viewerHigh)
diff --git a/packages/viewer/src/modules/materials/SpeckleMaterial.ts b/packages/viewer/src/modules/materials/SpeckleMaterial.ts
index 1fa742294b..3ffb70356a 100644
--- a/packages/viewer/src/modules/materials/SpeckleMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpeckleMaterial.ts
@@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
-/* eslint-disable camelcase */
import {
AlwaysStencilFunc,
- IUniform,
+ type IUniform,
Material,
MeshBasicMaterial,
MeshDepthMaterial,
@@ -10,24 +9,43 @@ import {
MeshStandardMaterial,
PointsMaterial,
ReplaceStencilOp,
- UniformsUtils
+ UniformsUtils,
+ type Shader
} from 'three'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
import { StencilOutlineType } from '../../IViewer'
-import { MaterialOptions } from './MaterialOptions'
+import { type MaterialOptions } from './MaterialOptions'
class SpeckleUserData {
+ [k: string]: unknown
toJSON() {
return {}
}
}
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Uniforms = Record
export class SpeckleMaterial {
- protected _internalUniforms
- protected _stencilOutline
- public needsCopy: boolean
+ protected _internalUniforms!: Shader
+ protected _stencilOutline: StencilOutlineType = StencilOutlineType.NONE
+ public needsCopy: boolean = false
+
+ protected get speckleUserData(): SpeckleUserData {
+ return (this as unknown as Material).userData
+ }
+
+ protected set speckleUserData(value: SpeckleUserData) {
+ ;(this as unknown as Material).userData = value
+ }
+
+ protected get speckleDefines(): Record | undefined {
+ return (this as unknown as Material).defines
+ }
+
+ protected set speckleDefines(value: Record | undefined) {
+ ;(this as unknown as Material).defines = value
+ }
protected get vertexProgram(): string {
return ''
@@ -50,55 +68,62 @@ export class SpeckleMaterial {
}
protected set stencilOutline(value: StencilOutlineType) {
- this['stencilWrite'] = value === StencilOutlineType.NONE ? false : true
- if (this['stencilWrite']) {
- this['stencilWriteMask'] = 0xff
- this['stencilRef'] = 0x00
- this['stencilFunc'] = AlwaysStencilFunc
- this['stencilZFail'] = ReplaceStencilOp
- this['stencilZPass'] = ReplaceStencilOp
- this['stencilFail'] = ReplaceStencilOp
+ this._stencilOutline = value
+ const thisAsMaterial: Material = this as unknown as Material
+ thisAsMaterial.stencilWrite = value === StencilOutlineType.NONE ? false : true
+ if (thisAsMaterial.stencilWrite) {
+ thisAsMaterial.stencilWriteMask = 0xff
+ thisAsMaterial.stencilRef = 0x00
+ thisAsMaterial.stencilFunc = AlwaysStencilFunc
+ thisAsMaterial.stencilZFail = ReplaceStencilOp
+ thisAsMaterial.stencilZPass = ReplaceStencilOp
+ thisAsMaterial.stencilFail = ReplaceStencilOp
if (value === StencilOutlineType.OUTLINE_ONLY) {
- this['colorWrite'] = false
- this['depthWrite'] = false
- this['stencilWrite'] = true
+ thisAsMaterial.colorWrite = false
+ thisAsMaterial.depthWrite = false
+ thisAsMaterial.stencilWrite = true
}
}
}
protected set pointSize(value: number) {
- this['size'] = value
+ ;(this as unknown as PointsMaterial).size = value
}
- protected init(defines = []) {
- this['userData'] = new SpeckleUserData()
+ protected init(defines: Array = []) {
+ this.speckleUserData = new SpeckleUserData()
this.setUniforms(this.uniformsDef)
this.setDefines(defines)
- this['onBeforeCompile'] = this.onCompile
+ ;(this as unknown as Material)['onBeforeCompile'] = this.onCompile
}
protected setUniforms(def: Uniforms) {
for (const k in def) {
- this['userData'][k] = {
+ this.speckleUserData[k] = {
value: def[k]
}
}
- this['uniforms'] = UniformsUtils.merge([this.baseUniforms, this['userData']])
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ ;(this as any)['uniforms'] = UniformsUtils.merge([
+ this.baseUniforms,
+ this.speckleUserData
+ ])
}
- protected setDefines(defines = []) {
+ protected setDefines(defines: Array = []) {
if (defines) {
- this['defines'] = {}
+ this.speckleDefines = {}
for (let k = 0; k < defines.length; k++) {
- this['defines'][defines[k]] = ' '
+ this.speckleDefines[defines[k]] = ' '
}
}
}
protected copyUniforms(material: Material) {
for (const k in material.userData) {
- if (this['userData'][k] !== undefined)
- this['userData'][k].value = material.userData[k].value
+ if (this.speckleUserData[k] !== undefined)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (this.speckleUserData[k] as any).value = material.userData[k].value
}
}
@@ -106,21 +131,23 @@ export class SpeckleMaterial {
if (!this._internalUniforms) return
for (const k in this.uniformsDef) {
- this._internalUniforms.uniforms[k] = this['userData'][k]
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ this._internalUniforms.uniforms[k] = this.speckleUserData[k] as IUniform
}
}
- protected copyFrom(source) {
- this['userData'] = new SpeckleUserData()
+ protected copyFrom(source: Material) {
+ this.speckleUserData = new SpeckleUserData()
this.setUniforms(this.uniformsDef)
this.copyUniforms(source)
this.bindUniforms()
- Object.assign(this['defines'], source.defines)
- if (source.needsCopy) this.needsCopy = source.needsCopy
+ Object.assign(this.speckleDefines as object, source.defines)
+ if ((source as unknown as SpeckleMaterial).needsCopy)
+ this.needsCopy = (source as unknown as SpeckleMaterial).needsCopy
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- protected onCompile(shader, renderer) {
+ protected onCompile(shader: Shader) {
this._internalUniforms = shader
this.bindUniforms()
shader.vertexShader = this.vertexProgram
@@ -141,7 +168,7 @@ export class SpeckleMaterial {
to.clippingPlanes = from.clippingPlanes
to.clipShadows = from.clipShadows
to.colorWrite = from.colorWrite
- Object.assign(to.defines, from.defines)
+ Object.assign(to.defines as object, from.defines)
to.depthFunc = from.depthFunc
to.depthTest = from.depthTest
to.depthWrite = from.depthWrite
@@ -182,7 +209,6 @@ export class ExtendedMeshNormalMaterial extends MeshNormalMaterial {}
export class ExtendedLineMaterial extends LineMaterial {}
export class ExtendedPointsMaterial extends PointsMaterial {}
-// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ExtendedMeshStandardMaterial extends SpeckleMaterial {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ExtendedMeshBasicMaterial extends SpeckleMaterial {}
diff --git a/packages/viewer/src/modules/materials/SpeckleNormalMaterial.ts b/packages/viewer/src/modules/materials/SpeckleNormalMaterial.ts
index 76f5c86111..94e302ee7d 100644
--- a/packages/viewer/src/modules/materials/SpeckleNormalMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpeckleNormalMaterial.ts
@@ -1,11 +1,20 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable camelcase */
import { speckleNormalVert } from './shaders/speckle-normal-vert'
import { speckleNormalFrag } from './shaders/speckle-normal-frag'
-import { ShaderLib, Vector3, IUniform } from 'three'
+import {
+ BufferGeometry,
+ Camera,
+ Material,
+ Object3D,
+ Scene,
+ ShaderLib,
+ Vector3,
+ type IUniform,
+ type MeshNormalMaterialParameters
+} from 'three'
import { Matrix4 } from 'three'
-import { Geometry } from '../converter/Geometry'
-import { ExtendedMeshNormalMaterial, Uniforms } from './SpeckleMaterial'
+import { ExtendedMeshNormalMaterial, type Uniforms } from './SpeckleMaterial'
+import type { SpeckleWebGLRenderer } from '../objects/SpeckleWebGLRenderer'
class SpeckleNormalMaterial extends ExtendedMeshNormalMaterial {
protected get vertexProgram(): string {
@@ -29,7 +38,7 @@ class SpeckleNormalMaterial extends ExtendedMeshNormalMaterial {
}
}
- constructor(parameters, defines = ['USE_RTE']) {
+ constructor(parameters: MeshNormalMaterialParameters, defines = ['USE_RTE']) {
super(parameters)
this.init(defines)
}
@@ -39,14 +48,20 @@ class SpeckleNormalMaterial extends ExtendedMeshNormalMaterial {
return this.constructor.name
}
- public copy(source) {
+ public copy(source: Material) {
super.copy(source)
this.copyFrom(source)
return this
}
/** Called by three.js render loop */
- public onBeforeRender(_this, scene, camera, geometry, object, group) {
+ public onBeforeRender(
+ _this: SpeckleWebGLRenderer,
+ _scene: Scene,
+ _camera: Camera,
+ _geometry: BufferGeometry,
+ object: Object3D
+ ) {
object.modelViewMatrix.copy(_this.RTEBuffers.rteViewModelMatrix)
this.userData.uViewer_low.value.copy(_this.RTEBuffers.viewerLow)
this.userData.uViewer_high.value.copy(_this.RTEBuffers.viewerHigh)
diff --git a/packages/viewer/src/modules/materials/SpecklePointColouredMaterial.ts b/packages/viewer/src/modules/materials/SpecklePointColouredMaterial.ts
index f0213a88ad..ba9d21f191 100644
--- a/packages/viewer/src/modules/materials/SpecklePointColouredMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpecklePointColouredMaterial.ts
@@ -1,67 +1,28 @@
/* eslint-disable camelcase */
-/* eslint-disable @typescript-eslint/no-explicit-any */
-/* eslint-disable @typescript-eslint/no-unused-vars */
import { specklePointVert } from './shaders/speckle-point-vert'
import { specklePointFrag } from './shaders/speckle-point-frag'
-import {
- Matrix4,
- NearestFilter,
- PointsMaterial,
- ShaderLib,
- Texture,
- UniformsUtils,
- Vector3
-} from 'three'
-import { Geometry } from '../converter/Geometry'
+import { Material, NearestFilter, Texture, type PointsMaterialParameters } from 'three'
+import type { Uniforms } from './SpeckleMaterial'
+import SpecklePointMaterial from './SpecklePointMaterial'
-class SpecklePointMaterial extends PointsMaterial {
- private static readonly matBuff: Matrix4 = new Matrix4()
- private static readonly vecBuff0: Vector3 = new Vector3()
- private static readonly vecBuff1: Vector3 = new Vector3()
- private static readonly vecBuff2: Vector3 = new Vector3()
+class SpecklePointColouredMaterial extends SpecklePointMaterial {
+ protected get vertexProgram(): string {
+ return specklePointVert
+ }
- constructor(parameters, defines = []) {
- super(parameters)
- this.userData.uViewer_high = {
- value: new Vector3()
- }
- this.userData.uViewer_low = {
- value: new Vector3()
- }
- this.userData.gradientRamp = {
- value: null
- }
- ;(this as any).vertProgram = specklePointVert
- ;(this as any).fragProgram = specklePointFrag
- ;(this as any).uniforms = UniformsUtils.merge([
- ShaderLib.standard.uniforms,
- {
- uViewer_high: {
- value: this.userData.uViewer_high.value
- },
- uViewer_low: {
- value: this.userData.uViewer_low.value
- },
- gradientRamp: {
- value: this.userData.gradientRamp.value
- }
- }
- ])
+ protected get fragmentProgram(): string {
+ return specklePointFrag
+ }
- this.onBeforeCompile = function (shader) {
- shader.uniforms.uViewer_high = this.userData.uViewer_high
- shader.uniforms.uViewer_low = this.userData.uViewer_low
- shader.uniforms.gradientRamp = this.userData.gradientRamp
- shader.vertexShader = this.vertProgram
- shader.fragmentShader = this.fragProgram
- }
+ protected get uniformsDef(): Uniforms {
+ return { ...super.uniformsDef, gradientRamp: null }
+ }
- if (defines) {
- this.defines = { USE_GRADIENT_RAMP: '' }
- }
- for (let k = 0; k < defines.length; k++) {
- this.defines[defines[k]] = ' '
- }
+ constructor(
+ parameters: PointsMaterialParameters,
+ defines: string[] = ['USE_RTE', 'USE_GRADIENT_RAMP']
+ ) {
+ super(parameters, defines)
}
public setGradientTexture(texture: Texture) {
@@ -72,50 +33,10 @@ class SpecklePointMaterial extends PointsMaterial {
this.needsUpdate = true
}
- copy(source) {
- super.copy(source)
- this.userData = {}
- this.userData.uViewer_high = {
- value: new Vector3()
- }
- this.userData.uViewer_low = {
- value: new Vector3()
- }
- this.userData.gradientRamp = {
- value: null
- }
-
- this.defines['USE_RTE'] = ' '
- this.defines['USE_GRADIENT_RAMP'] = ' '
-
- return this
- }
-
- onBeforeRender(_this, scene, camera, geometry, object, group) {
- SpecklePointMaterial.matBuff.copy(camera.matrixWorldInverse)
- SpecklePointMaterial.matBuff.elements[12] = 0
- SpecklePointMaterial.matBuff.elements[13] = 0
- SpecklePointMaterial.matBuff.elements[14] = 0
- SpecklePointMaterial.matBuff.multiply(object.matrixWorld)
- object.modelViewMatrix.copy(SpecklePointMaterial.matBuff)
-
- SpecklePointMaterial.vecBuff0.set(
- camera.matrixWorld.elements[12],
- camera.matrixWorld.elements[13],
- camera.matrixWorld.elements[14]
- )
-
- Geometry.DoubleToHighLowVector(
- SpecklePointMaterial.vecBuff0,
- SpecklePointMaterial.vecBuff1,
- SpecklePointMaterial.vecBuff2
- )
-
- this.userData.uViewer_low.value.copy(SpecklePointMaterial.vecBuff1)
- this.userData.uViewer_high.value.copy(SpecklePointMaterial.vecBuff2)
-
- this.needsUpdate = true
+ public fastCopy(from: Material, to: Material) {
+ super.fastCopy(from, to)
+ to.userData.gradientRamp.value = from.userData.gradientRamp.value
}
}
-export default SpecklePointMaterial
+export default SpecklePointColouredMaterial
diff --git a/packages/viewer/src/modules/materials/SpecklePointMaterial.ts b/packages/viewer/src/modules/materials/SpecklePointMaterial.ts
index 50168cb34c..dbb577cd9f 100644
--- a/packages/viewer/src/modules/materials/SpecklePointMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpecklePointMaterial.ts
@@ -1,10 +1,20 @@
/* eslint-disable camelcase */
-/* eslint-disable @typescript-eslint/no-unused-vars */
import { specklePointVert } from './shaders/speckle-point-vert'
import { specklePointFrag } from './shaders/speckle-point-frag'
-import { IUniform, Material, Matrix4, PointsMaterial, ShaderLib, Vector3 } from 'three'
-import { Geometry } from '../converter/Geometry'
-import { ExtendedPointsMaterial, Uniforms } from './SpeckleMaterial'
+import {
+ type IUniform,
+ Material,
+ PointsMaterial,
+ ShaderLib,
+ Vector3,
+ type PointsMaterialParameters,
+ Scene,
+ Camera,
+ BufferGeometry,
+ Object3D
+} from 'three'
+import { ExtendedPointsMaterial, type Uniforms } from './SpeckleMaterial'
+import type { SpeckleWebGLRenderer } from '../objects/SpeckleWebGLRenderer'
class SpecklePointMaterial extends ExtendedPointsMaterial {
protected get vertexProgram(): string {
@@ -26,7 +36,7 @@ class SpecklePointMaterial extends ExtendedPointsMaterial {
}
}
- constructor(parameters, defines = ['USE_RTE']) {
+ constructor(parameters: PointsMaterialParameters, defines = ['USE_RTE']) {
super(parameters)
this.init(defines)
}
@@ -36,7 +46,7 @@ class SpecklePointMaterial extends ExtendedPointsMaterial {
return this.constructor.name
}
- public copy(source) {
+ public copy(source: Material) {
super.copy(source)
this.copyFrom(source)
return this
@@ -51,7 +61,13 @@ class SpecklePointMaterial extends ExtendedPointsMaterial {
toStandard.sizeAttenuation = fromStandard.sizeAttenuation
}
- onBeforeRender(_this, scene, camera, geometry, object, group) {
+ onBeforeRender(
+ _this: SpeckleWebGLRenderer,
+ _scene: Scene,
+ _camera: Camera,
+ _geometry: BufferGeometry,
+ object: Object3D
+ ) {
object.modelViewMatrix.copy(_this.RTEBuffers.rteViewModelMatrix)
this.userData.uViewer_low.value.copy(_this.RTEBuffers.viewerLow)
this.userData.uViewer_high.value.copy(_this.RTEBuffers.viewerHigh)
diff --git a/packages/viewer/src/modules/materials/SpeckleShadowcatcherMaterial.ts b/packages/viewer/src/modules/materials/SpeckleShadowcatcherMaterial.ts
index b3a682bd15..cdb7dacbd6 100644
--- a/packages/viewer/src/modules/materials/SpeckleShadowcatcherMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpeckleShadowcatcherMaterial.ts
@@ -1,8 +1,8 @@
import { speckleShadowcatcherVert } from './shaders/speckle-shadowcatcher-vert'
import { speckleShadowcatcherFrag } from './shaders/speckle-shadowcatche-frag'
import SpeckleBasicMaterial from './SpeckleBasicMaterial'
-import { Vector4 } from 'three'
-import { Uniforms } from './SpeckleMaterial'
+import { Vector4, type MeshBasicMaterialParameters } from 'three'
+import { type Uniforms } from './SpeckleMaterial'
class SpeckleShadowcatcherMaterial extends SpeckleBasicMaterial {
protected get vertexProgram(): string {
@@ -26,7 +26,7 @@ class SpeckleShadowcatcherMaterial extends SpeckleBasicMaterial {
}
}
- constructor(parameters, defines = []) {
+ constructor(parameters: MeshBasicMaterialParameters, defines = []) {
super(parameters, defines)
}
}
diff --git a/packages/viewer/src/modules/materials/SpeckleStandardColoredMaterial.ts b/packages/viewer/src/modules/materials/SpeckleStandardColoredMaterial.ts
index b79cdcfff8..dad53a1b0a 100644
--- a/packages/viewer/src/modules/materials/SpeckleStandardColoredMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpeckleStandardColoredMaterial.ts
@@ -1,8 +1,8 @@
import { speckleStandardColoredVert } from './shaders/speckle-standard-colored-vert'
import { speckleStandardColoredFrag } from './shaders/speckle-standard-colored-frag'
-import { Texture, NearestFilter } from 'three'
+import { Texture, NearestFilter, type MeshStandardMaterialParameters } from 'three'
import SpeckleStandardMaterial from './SpeckleStandardMaterial'
-import { Uniforms } from './SpeckleMaterial'
+import { type Uniforms } from './SpeckleMaterial'
class SpeckleStandardColoredMaterial extends SpeckleStandardMaterial {
protected get vertexProgram(): string {
@@ -17,7 +17,7 @@ class SpeckleStandardColoredMaterial extends SpeckleStandardMaterial {
return { ...super.uniformsDef, gradientRamp: null }
}
- constructor(parameters, defines = ['USE_RTE']) {
+ constructor(parameters: MeshStandardMaterialParameters, defines = ['USE_RTE']) {
super(parameters, defines)
}
diff --git a/packages/viewer/src/modules/materials/SpeckleStandardMaterial.ts b/packages/viewer/src/modules/materials/SpeckleStandardMaterial.ts
index 96c8a1d8e7..49936ac208 100644
--- a/packages/viewer/src/modules/materials/SpeckleStandardMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpeckleStandardMaterial.ts
@@ -1,10 +1,19 @@
-/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable camelcase */
import { speckleStandardVert } from './shaders/speckle-standard-vert'
import { speckleStandardFrag } from './shaders/speckle-standard-frag'
-import { ShaderLib, Vector3, Material, IUniform } from 'three'
+import {
+ ShaderLib,
+ Vector3,
+ Material,
+ type IUniform,
+ type MeshStandardMaterialParameters,
+ Scene,
+ Camera,
+ BufferGeometry,
+ Object3D
+} from 'three'
import { Matrix4 } from 'three'
-import { ExtendedMeshStandardMaterial, Uniforms } from './SpeckleMaterial'
+import { ExtendedMeshStandardMaterial, type Uniforms } from './SpeckleMaterial'
import { SpeckleWebGLRenderer } from '../objects/SpeckleWebGLRenderer'
class SpeckleStandardMaterial extends ExtendedMeshStandardMaterial {
@@ -36,7 +45,7 @@ class SpeckleStandardMaterial extends ExtendedMeshStandardMaterial {
}
}
- constructor(parameters, defines = ['USE_RTE']) {
+ constructor(parameters: MeshStandardMaterialParameters, defines = ['USE_RTE']) {
super(parameters)
this.init(defines)
}
@@ -46,11 +55,13 @@ class SpeckleStandardMaterial extends ExtendedMeshStandardMaterial {
return this.constructor.name
}
- public copy(source) {
+ public copy(source: Material) {
super.copy(source)
this.copyFrom(source)
- this.originalRoughness = source.originalRoughness
- this.artificialRoughness = source.artificialRoughness
+ if (source instanceof SpeckleStandardMaterial) {
+ this.originalRoughness = source.originalRoughness
+ this.artificialRoughness = source.artificialRoughness
+ }
return this
}
@@ -94,16 +105,26 @@ class SpeckleStandardMaterial extends ExtendedMeshStandardMaterial {
if (this.originalRoughness === undefined) this.originalRoughness = this.roughness
this.artificialRoughness = artificialRougness
}
+ if (this.originalRoughness === undefined || this.artificialRoughness === undefined)
+ return
+
const applyRoughness =
artificialRougness !== undefined
? Math.min(this.originalRoughness, this.artificialRoughness)
: this.originalRoughness
+
this.roughness = applyRoughness
this.needsCopy = true
}
/** Called by three.js render loop */
- public onBeforeRender(_this: SpeckleWebGLRenderer, scene, camera, geometry, object) {
+ public onBeforeRender(
+ _this: SpeckleWebGLRenderer,
+ _scene: Scene,
+ _camera: Camera,
+ _geometry: BufferGeometry,
+ object: Object3D
+ ) {
if (this.defines['USE_RTE']) {
object.modelViewMatrix.copy(_this.RTEBuffers.rteViewModelMatrix)
this.userData.uViewer_low.value.copy(_this.RTEBuffers.viewerLow)
diff --git a/packages/viewer/src/modules/materials/SpeckleTextMaterial.ts b/packages/viewer/src/modules/materials/SpeckleTextMaterial.ts
index 6dd11a5fa4..9eb3ac2038 100644
--- a/packages/viewer/src/modules/materials/SpeckleTextMaterial.ts
+++ b/packages/viewer/src/modules/materials/SpeckleTextMaterial.ts
@@ -2,17 +2,31 @@
/* eslint-disable camelcase */
import { speckleTextVert } from './shaders/speckle-text-vert'
import { speckleTextFrag } from './shaders/speckle-text-frag'
-import { ShaderLib, Vector3, IUniform, Vector2, Material } from 'three'
+import {
+ ShaderLib,
+ Vector3,
+ type IUniform,
+ Vector2,
+ Material,
+ type MeshBasicMaterialParameters,
+ Scene,
+ Camera,
+ BufferGeometry,
+ Object3D
+} from 'three'
import { Matrix4 } from 'three'
-import { ExtendedMeshBasicMaterial, Uniforms } from './SpeckleMaterial'
+import { ExtendedMeshBasicMaterial, type Uniforms } from './SpeckleMaterial'
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+//@ts-ignore
import { createTextDerivedMaterial } from 'troika-three-text'
+import type { SpeckleWebGLRenderer } from '../objects/SpeckleWebGLRenderer'
class SpeckleTextMaterial extends ExtendedMeshBasicMaterial {
protected static readonly matBuff: Matrix4 = new Matrix4()
protected static readonly vecBuff: Vector2 = new Vector2()
- private _billboardPixelHeight: number
+ private _billboardPixelHeight!: number
protected get vertexProgram(): string {
return speckleTextVert
@@ -47,7 +61,7 @@ class SpeckleTextMaterial extends ExtendedMeshBasicMaterial {
return this._billboardPixelHeight
}
- constructor(parameters, defines = []) {
+ constructor(parameters: MeshBasicMaterialParameters, defines: Array = []) {
super(parameters)
this.init(defines)
}
@@ -57,7 +71,7 @@ class SpeckleTextMaterial extends ExtendedMeshBasicMaterial {
return this.constructor.name
}
- public copy(source) {
+ public copy(source: Material) {
super.copy(source)
this.copyFrom(source)
return this
@@ -83,8 +97,14 @@ class SpeckleTextMaterial extends ExtendedMeshBasicMaterial {
}
/** Called by three.js render loop */
- public onBeforeRender(_this, scene, camera, geometry, object, group) {
- if (this.defines['BILLBOARD_FIXED']) {
+ public onBeforeRender(
+ _this: SpeckleWebGLRenderer,
+ _scene: Scene,
+ camera: Camera,
+ _geometry: BufferGeometry,
+ _object: Object3D
+ ) {
+ if (this.defines && this.defines['BILLBOARD_FIXED']) {
const resolution = _this.getDrawingBufferSize(SpeckleTextMaterial.vecBuff)
SpeckleTextMaterial.vecBuff.set(
(this._billboardPixelHeight / resolution.x) * 2,
diff --git a/packages/viewer/src/modules/objects/AccelerationStructure.ts b/packages/viewer/src/modules/objects/AccelerationStructure.ts
index d34cb7f18f..a2a8aabac4 100644
--- a/packages/viewer/src/modules/objects/AccelerationStructure.ts
+++ b/packages/viewer/src/modules/objects/AccelerationStructure.ts
@@ -3,16 +3,13 @@ import {
BufferGeometry,
Float32BufferAttribute,
FrontSide,
- Intersection,
Material,
Matrix4,
- Object3D,
Ray,
Side,
Uint16BufferAttribute,
Uint32BufferAttribute,
- Vector3,
- Event
+ Vector3
} from 'three'
import {
CENTER,
@@ -21,6 +18,7 @@ import {
ShapecastIntersection,
SplitStrategy
} from 'three-mesh-bvh'
+import { MeshIntersection } from './SpeckleRaycaster'
const SKIP_GENERATION = Symbol('skip tree generation')
@@ -31,7 +29,7 @@ export interface BVHOptions {
verbose: boolean
useSharedArrayBuffer: boolean
setBoundingBox: boolean
- onProgress: () => void
+ onProgress?: () => void
[SKIP_GENERATION]: boolean
}
@@ -42,7 +40,6 @@ export const DefaultBVHOptions = {
verbose: true,
useSharedArrayBuffer: false,
setBoundingBox: false,
- onProgress: null,
[SKIP_GENERATION]: false
}
@@ -71,10 +68,10 @@ to get the correct values for Vectors, Rays, Boxes, etc
export class AccelerationStructure {
private static readonly MatBuff: Matrix4 = new Matrix4()
private _bvh: MeshBVH
- public inputTransform: Matrix4
- public outputTransform: Matrix4
- public inputOriginTransform: Matrix4
- public outputOriginTransfom: Matrix4
+ public inputTransform!: Matrix4
+ public outputTransform!: Matrix4
+ public inputOriginTransform!: Matrix4
+ public outputOriginTransfom!: Matrix4
public get geometry() {
return this._bvh.geometry
@@ -85,12 +82,17 @@ export class AccelerationStructure {
}
public static buildBVH(
- indices: number[],
- position: Float32Array,
+ indices: number[] | undefined,
+ position: number[] | undefined,
options: BVHOptions = DefaultBVHOptions,
transform?: Matrix4
): MeshBVH {
- let bvhPositions = position
+ /** There is no unniverse where you can build a BVH without proper indices or positions */
+ if (!indices || !position) {
+ throw new Error('Cannot build BVH with undefined indices or position!')
+ }
+
+ let bvhPositions = new Float32Array(position)
if (transform) {
bvhPositions = new Float32Array(position.length)
const vecBuff = new Vector3()
@@ -129,21 +131,23 @@ export class AccelerationStructure {
public raycast(
ray: Ray,
materialOrSide: Side | Material | Material[] = FrontSide
- ): Intersection>[] {
+ ): MeshIntersection[] {
const res = this._bvh.raycast(this.transformInput(ray), materialOrSide)
res.forEach((value) => {
value.point = this.transformOutput(value.point)
})
- return res
+ /** The intersection results from raycasting a bvh will always overlap with MeshIntersection because the bvh uses indexed geometry */
+ return res as MeshIntersection[]
}
public raycastFirst(
ray: Ray,
materialOrSide: Side | Material | Material[] = FrontSide
- ): Intersection> {
+ ): MeshIntersection {
const res = this._bvh.raycastFirst(this.transformInput(ray), materialOrSide)
res.point = this.transformOutput(res.point)
- return res
+ /** The intersection results from raycasting a bvh will always overlap with MeshIntersection because the bvh uses indexed geometry */
+ return res as MeshIntersection
}
public shapecast(
@@ -274,9 +278,10 @@ export class AccelerationStructure {
return output.applyMatrix4(AccelerationStructure.MatBuff) as T
}
- public getBoundingBox(target) {
- this._bvh.getBoundingBox(target)
- return this.transformOutput(target)
+ public getBoundingBox(target?: Box3): Box3 {
+ const _target: Box3 = target ? target : new Box3()
+ this._bvh.getBoundingBox(_target)
+ return this.transformOutput(_target)
}
public getVertexAtIndex(index: number): Vector3 {
diff --git a/packages/viewer/src/modules/objects/RotatablePMREMGenerator.ts b/packages/viewer/src/modules/objects/RotatablePMREMGenerator.ts
index 9a11f1204a..9d9638f0d1 100644
--- a/packages/viewer/src/modules/objects/RotatablePMREMGenerator.ts
+++ b/packages/viewer/src/modules/objects/RotatablePMREMGenerator.ts
@@ -1,7 +1,13 @@
-import { Matrix4, NoBlending, PMREMGenerator, ShaderMaterial } from 'three'
+import {
+ Matrix4,
+ NoBlending,
+ PMREMGenerator,
+ ShaderMaterial,
+ WebGLRenderer
+} from 'three'
export class RotatablePMREMGenerator extends PMREMGenerator {
- constructor(renderer) {
+ constructor(renderer: WebGLRenderer) {
super(renderer)
}
diff --git a/packages/viewer/src/modules/objects/SpeckleCamera.ts b/packages/viewer/src/modules/objects/SpeckleCamera.ts
index e199ceaf8d..39e00cda30 100644
--- a/packages/viewer/src/modules/objects/SpeckleCamera.ts
+++ b/packages/viewer/src/modules/objects/SpeckleCamera.ts
@@ -1,10 +1,17 @@
import { Box3, OrthographicCamera, PerspectiveCamera } from 'three'
export enum CameraEvent {
- Stationary,
- Dynamic,
- FrameUpdate,
- ProjectionChanged
+ Stationary = 'stationary',
+ Dynamic = 'dynamic',
+ FrameUpdate = 'frame-update',
+ ProjectionChanged = 'projection-changed'
+}
+
+export interface CameraEventPayload {
+ [CameraEvent.Stationary]: void
+ [CameraEvent.Dynamic]: void
+ [CameraEvent.FrameUpdate]: boolean
+ [CameraEvent.ProjectionChanged]: CameraProjection
}
export interface SpeckleCamera {
@@ -12,8 +19,11 @@ export interface SpeckleCamera {
get fieldOfView(): number
set fieldOfView(value: number)
get aspect(): number
- on(type: CameraEvent, handler: (...args) => void)
- setCameraPlanes(targetVolume: Box3, offsetScale?: number)
+ on(
+ eventType: T,
+ listener: (arg: CameraEventPayload[T]) => void
+ ): void
+ setCameraPlanes(targetVolume: Box3, offsetScale?: number): void
}
export enum CameraProjection {
PERSPECTIVE,
diff --git a/packages/viewer/src/modules/objects/SpeckleCameraControls.ts b/packages/viewer/src/modules/objects/SpeckleCameraControls.ts
index df1e1990d6..1c95ceffb9 100644
--- a/packages/viewer/src/modules/objects/SpeckleCameraControls.ts
+++ b/packages/viewer/src/modules/objects/SpeckleCameraControls.ts
@@ -1,52 +1,55 @@
import CameraControls from 'camera-controls'
-import { MathUtils, PerspectiveCamera, Vector3 } from 'three'
+import { MathUtils, OrthographicCamera, PerspectiveCamera, Vector3 } from 'three'
-let ACTION
-;(function (ACTION) {
- ACTION[(ACTION['NONE'] = 0)] = 'NONE'
- ACTION[(ACTION['ROTATE'] = 1)] = 'ROTATE'
- ACTION[(ACTION['TRUCK'] = 2)] = 'TRUCK'
- ACTION[(ACTION['OFFSET'] = 3)] = 'OFFSET'
- ACTION[(ACTION['DOLLY'] = 4)] = 'DOLLY'
- ACTION[(ACTION['ZOOM'] = 5)] = 'ZOOM'
- ACTION[(ACTION['TOUCH_ROTATE'] = 6)] = 'TOUCH_ROTATE'
- ACTION[(ACTION['TOUCH_TRUCK'] = 7)] = 'TOUCH_TRUCK'
- ACTION[(ACTION['TOUCH_OFFSET'] = 8)] = 'TOUCH_OFFSET'
- ACTION[(ACTION['TOUCH_DOLLY'] = 9)] = 'TOUCH_DOLLY'
- ACTION[(ACTION['TOUCH_ZOOM'] = 10)] = 'TOUCH_ZOOM'
- ACTION[(ACTION['TOUCH_DOLLY_TRUCK'] = 11)] = 'TOUCH_DOLLY_TRUCK'
- ACTION[(ACTION['TOUCH_DOLLY_OFFSET'] = 12)] = 'TOUCH_DOLLY_OFFSET'
- ACTION[(ACTION['TOUCH_ZOOM_TRUCK'] = 13)] = 'TOUCH_ZOOM_TRUCK'
- ACTION[(ACTION['TOUCH_ZOOM_OFFSET'] = 14)] = 'TOUCH_ZOOM_OFFSET'
-})(ACTION || (ACTION = {}))
-function isPerspectiveCamera(camera) {
- return camera.isPerspectiveCamera
+enum ACTION {
+ NONE = 0,
+ ROTATE = 1,
+ TRUCK = 2,
+ OFFSET = 3,
+ DOLLY = 4,
+ ZOOM = 5,
+ TOUCH_ROTATE = 6,
+ TOUCH_TRUCK = 7,
+ TOUCH_OFFSET = 8,
+ TOUCH_DOLLY = 9,
+ TOUCH_ZOOM = 10,
+ TOUCH_DOLLY_TRUCK = 11,
+ TOUCH_DOLLY_OFFSET = 12,
+ TOUCH_ZOOM_TRUCK = 13,
+ TOUCH_ZOOM_OFFSET = 14
}
-function isOrthographicCamera(camera) {
- return camera.isOrthographicCamera
+
+function isPerspectiveCamera(camera: PerspectiveCamera | OrthographicCamera) {
+ return (camera as PerspectiveCamera).isPerspectiveCamera
+}
+function isOrthographicCamera(camera: PerspectiveCamera | OrthographicCamera) {
+ return (camera as OrthographicCamera).isOrthographicCamera
}
const EPSILON = 1e-5
-function approxZero(number, error = EPSILON) {
+function approxZero(number: number, error = EPSILON) {
return Math.abs(number) < error
}
-function approxEquals(a, b, error = EPSILON) {
+function approxEquals(a: number, b: number, error = EPSILON) {
return approxZero(a - b, error)
}
-let _deltaTarget, _deltaOffset, _v3A, _v3B, _v3C
-let _xColumn
-let _yColumn
-let _zColumn
+let _deltaTarget: Vector3,
+ _deltaOffset: Vector3,
+ _v3A: Vector3,
+ _v3B: Vector3,
+ _v3C: Vector3
+let _xColumn: Vector3
+let _yColumn: Vector3
+let _zColumn: Vector3
export class SpeckleCameraControls extends CameraControls {
private _didDolly = false
private _didDollyLastFrame = false
public _isTrucking = false
- private _hasRestedLastFrame = false
private _didZoom = false
- private overrideDollyLerpRatio = 0
- private overrideZoomLerpRatio = 0
+ private overrideDollyLerpRatio: number | undefined = 0
+ private overrideZoomLerpRatio: number | undefined = 0
static install() {
_v3A = new Vector3()
@@ -127,11 +130,7 @@ export class SpeckleCameraControls extends CameraControls {
* @param enableTransition
* @category Methods
*/
- zoomTo(
- zoom: number,
- enableTransition = false,
- lerpRatio: number = undefined
- ): Promise {
+ zoomTo(zoom: number, enableTransition = false, lerpRatio?: number): Promise {
this._zoomEnd = MathUtils.clamp(zoom, this.minZoom, this.maxZoom)
this._needsUpdate = true
this.overrideZoomLerpRatio = enableTransition ? 0.05 : lerpRatio
@@ -153,7 +152,7 @@ export class SpeckleCameraControls extends CameraControls {
dollyTo(
distance: number,
enableTransition = true,
- lerpRatio = undefined
+ lerpRatio?: number
): Promise {
const lastRadius = this._sphericalEnd.radius
const newRadius = MathUtils.clamp(distance, this.minDistance, this.maxDistance)
@@ -193,8 +192,7 @@ export class SpeckleCameraControls extends CameraControls {
return this._createOnRestPromise(resolveImmediately)
}
- update(delta) {
- this._hasRestedLastFrame = this._hasRested
+ update(delta: number) {
const dampingFactor =
this._state === ACTION.NONE ? this.dampingFactor : this.draggingDampingFactor
const lerpRatio = Math.min(dampingFactor * delta * 60, 1)
diff --git a/packages/viewer/src/modules/objects/SpeckleInstancedMesh.ts b/packages/viewer/src/modules/objects/SpeckleInstancedMesh.ts
index 75418eeac5..8c66d95bb7 100644
--- a/packages/viewer/src/modules/objects/SpeckleInstancedMesh.ts
+++ b/packages/viewer/src/modules/objects/SpeckleInstancedMesh.ts
@@ -1,5 +1,6 @@
import {
BackSide,
+ BufferAttribute,
BufferGeometry,
DoubleSide,
Group,
@@ -7,23 +8,26 @@ import {
InstancedMesh,
Material,
Matrix4,
+ Object3D,
Ray,
Raycaster,
+ SkinnedMesh,
Sphere,
Triangle,
Vector2,
- Vector3
+ Vector3,
+ type Intersection
} from 'three'
import { BatchObject } from '../batching/BatchObject'
import Materials from '../materials/Materials'
import { TopLevelAccelerationStructure } from './TopLevelAccelerationStructure'
+import { ObjectLayers } from '../../IViewer'
+import Logger from 'js-logger'
import {
- DrawGroup,
+ type DrawGroup,
INSTANCE_GRADIENT_BUFFER_STRIDE,
INSTANCE_TRANSFORM_BUFFER_STRIDE
} from '../batching/Batch'
-import { ObjectLayers } from '../../IViewer'
-import Logger from 'js-logger'
const _inverseMatrix = new Matrix4()
const _ray = new Ray()
@@ -54,20 +58,20 @@ const tmpInverseMatrix = /* @__PURE__ */ new Matrix4()
export default class SpeckleInstancedMesh extends Group {
public static MeshBatchNumber = 0
- private tas: TopLevelAccelerationStructure = null
- private batchMaterial: Material = null
+ private tas: TopLevelAccelerationStructure
+ private batchMaterial: Material | null = null
private materialCache: { [id: string]: Material } = {}
- private materialStack: Array = []
+ private materialStack: Array> = []
private materialCacheLUT: { [id: string]: number } = {}
- private _batchObjects: BatchObject[]
+ private _batchObjects!: BatchObject[]
public groups: Array = []
public materials: Material[] = []
- private instanceGeometry: BufferGeometry = null
+ private instanceGeometry: BufferGeometry | undefined = undefined
private instances: InstancedMesh[] = []
- public get TAS() {
+ public get TAS(): TopLevelAccelerationStructure {
return this.tas
}
@@ -92,8 +96,7 @@ export default class SpeckleInstancedMesh extends Group {
}
public setOverrideMaterial(material: Material) {
- material
- const saveMaterials = []
+ const saveMaterials: Array = []
for (let k = 0; k < this.instances.length; k++) {
saveMaterials.push(this.instances[k].material)
}
@@ -118,7 +121,11 @@ export default class SpeckleInstancedMesh extends Group {
this.materialCacheLUT[clone.id] = material.id
cachedMaterial = clone
this.updateMaterialTransformsUniform(this.materialCache[material.id])
- } else if (copy || material['needsCopy'] || cachedMaterial['needsCopy']) {
+ } else if (
+ copy ||
+ (material as never)['needsCopy'] ||
+ (cachedMaterial as never)['needsCopy']
+ ) {
Materials.fastCopy(material, cachedMaterial)
}
return cachedMaterial
@@ -149,7 +156,8 @@ export default class SpeckleInstancedMesh extends Group {
this.instances.length = 0
for (let k = 0; k < this.groups.length; k++) {
- const material = this.materials[this.groups[k].materialIndex]
+ const materialIndex = this.groups[k].materialIndex
+ const material = this.materials[materialIndex]
const group = new InstancedMesh(this.instanceGeometry, material, 0)
group.instanceMatrix = new InstancedBufferAttribute(
transformBuffer.subarray(
@@ -231,7 +239,11 @@ export default class SpeckleInstancedMesh extends Group {
// converts the given BVH raycast intersection to align with the three.js raycast
// structure (include object, world space distance and point).
- private convertRaycastIntersect(hit, object, raycaster) {
+ private convertRaycastIntersect(
+ hit: Intersection | null,
+ object: Object3D,
+ raycaster: Raycaster
+ ) {
if (hit === null) {
return null
}
@@ -247,9 +259,9 @@ export default class SpeckleInstancedMesh extends Group {
}
}
- raycast(raycaster: Raycaster, intersects) {
+ raycast(raycaster: Raycaster, intersects: Array) {
if (this.tas) {
- if (this.batchMaterial === undefined) return
+ if (!this.batchMaterial) return
tmpInverseMatrix.copy(this.matrixWorld).invert()
ray.copy(raycaster.ray).applyMatrix4(tmpInverseMatrix)
@@ -278,12 +290,13 @@ export default class SpeckleInstancedMesh extends Group {
const matrixWorld = this.matrixWorld
if (material === undefined) return
+ if (geometry === undefined) return
// Checking boundingSphere distance to ray
if (geometry.boundingSphere === null) geometry.computeBoundingSphere()
- _sphere.copy(geometry.boundingSphere)
+ _sphere.copy(geometry.boundingSphere || new Sphere())
_sphere.applyMatrix4(matrixWorld)
if (raycaster.ray.intersectsSphere(_sphere) === false) return
@@ -303,13 +316,13 @@ export default class SpeckleInstancedMesh extends Group {
const index = geometry.index
/** Stored high component if RTE is being used. Regular positions otherwise */
- const position = geometry.attributes.position
+ const position = geometry.attributes.position as BufferAttribute
/** Stored low component if RTE is being used. undefined otherwise */
- const positionLow = geometry.attributes['position_low']
- const morphPosition = geometry.morphAttributes.position
+ const positionLow = geometry.attributes['position_low'] as BufferAttribute
+ const morphPosition = geometry.morphAttributes.position as Array
const morphTargetsRelative = geometry.morphTargetsRelative
- const uv = geometry.attributes.uv
- const uv2 = geometry.attributes.uv2
+ const uv = geometry.attributes.uv as BufferAttribute
+ const uv2 = geometry.attributes.uv2 as BufferAttribute
const groups = geometry.groups
const drawRange = geometry.drawRange
@@ -319,6 +332,10 @@ export default class SpeckleInstancedMesh extends Group {
if (Array.isArray(material)) {
for (let i = 0, il = groups.length; i < il; i++) {
const group = groups[i]
+ if (!group.materialIndex) {
+ Logger.error(`Group with no material, skipping!`)
+ continue
+ }
const groupMaterial = material[group.materialIndex]
const start = Math.max(group.start, drawRange.start)
@@ -350,7 +367,8 @@ export default class SpeckleInstancedMesh extends Group {
if (intersection) {
intersection.faceIndex = Math.floor(j / 3) // triangle number in indexed buffer semantics
- intersection.face.materialIndex = group.materialIndex
+ if (intersection.face)
+ intersection.face.materialIndex = group.materialIndex as number
intersects.push(intersection)
}
}
@@ -392,6 +410,10 @@ export default class SpeckleInstancedMesh extends Group {
if (Array.isArray(material)) {
for (let i = 0, il = groups.length; i < il; i++) {
const group = groups[i]
+ if (!group.materialIndex) {
+ Logger.error(`Group with no material, skipping!`)
+ continue
+ }
const groupMaterial = material[group.materialIndex]
const start = Math.max(group.start, drawRange.start)
@@ -423,7 +445,8 @@ export default class SpeckleInstancedMesh extends Group {
if (intersection) {
intersection.faceIndex = Math.floor(j / 3) // triangle number in non-indexed buffer semantics
- intersection.face.materialIndex = group.materialIndex
+ if (intersection.face)
+ intersection.face.materialIndex = group.materialIndex as number
intersects.push(intersection)
}
}
@@ -464,7 +487,16 @@ export default class SpeckleInstancedMesh extends Group {
}
}
-function checkIntersection(object, material, raycaster, ray, pA, pB, pC, point) {
+function checkIntersection(
+ object: Object3D,
+ material: Material,
+ raycaster: Raycaster,
+ ray: Ray,
+ pA: Vector3,
+ pB: Vector3,
+ pC: Vector3,
+ point: Vector3
+): (Intersection & { uv2: Vector2 | undefined }) | null {
let intersect
if (material.side === BackSide) {
@@ -488,7 +520,8 @@ function checkIntersection(object, material, raycaster, ray, pA, pB, pC, point)
object,
uv: undefined,
uv2: undefined,
- face: undefined
+ face: undefined,
+ faceIndex: undefined
}
}
@@ -496,19 +529,19 @@ function checkIntersection(object, material, raycaster, ray, pA, pB, pC, point)
* hold the default `position` attribute values
*/
function checkBufferGeometryIntersection(
- object,
- material,
- raycaster,
- ray,
- positionLow,
- positionHigh,
- morphPosition,
- morphTargetsRelative,
- uv,
- uv2,
- a,
- b,
- c
+ object: Object3D,
+ material: Material,
+ raycaster: Raycaster,
+ ray: Ray,
+ positionLow: BufferAttribute,
+ positionHigh: BufferAttribute,
+ morphPosition: Array,
+ morphTargetsRelative: boolean,
+ uv: BufferAttribute,
+ uv2: BufferAttribute,
+ a: number,
+ b: number,
+ c: number
) {
_vA.fromBufferAttribute(positionHigh, a)
_vB.fromBufferAttribute(positionHigh, b)
@@ -519,7 +552,7 @@ function checkBufferGeometryIntersection(
_vC.add(_vTemp.fromBufferAttribute(positionLow, c))
}
- const morphInfluences = object.morphTargetInfluences
+ const morphInfluences = (object as SkinnedMesh).morphTargetInfluences
if (morphPosition && morphInfluences) {
_morphA.set(0, 0, 0)
@@ -552,10 +585,10 @@ function checkBufferGeometryIntersection(
_vC.add(_morphC)
}
- if (object.isSkinnedMesh) {
- object.boneTransform(a, _vA)
- object.boneTransform(b, _vB)
- object.boneTransform(c, _vC)
+ if ((object as SkinnedMesh).isSkinnedMesh) {
+ ;(object as SkinnedMesh).boneTransform(a, _vA)
+ ;(object as SkinnedMesh).boneTransform(b, _vB)
+ ;(object as SkinnedMesh).boneTransform(c, _vC)
}
const intersection = checkIntersection(
diff --git a/packages/viewer/src/modules/objects/SpeckleMesh.ts b/packages/viewer/src/modules/objects/SpeckleMesh.ts
index d128cbcf17..7bf2565f76 100644
--- a/packages/viewer/src/modules/objects/SpeckleMesh.ts
+++ b/packages/viewer/src/modules/objects/SpeckleMesh.ts
@@ -1,7 +1,9 @@
import Logger from 'js-logger'
import {
BackSide,
+ Box3,
Box3Helper,
+ BufferAttribute,
BufferGeometry,
Color,
DataTexture,
@@ -10,13 +12,16 @@ import {
Material,
Matrix4,
Mesh,
+ Object3D,
Ray,
Raycaster,
RGBAFormat,
+ SkinnedMesh,
Sphere,
Triangle,
Vector2,
- Vector3
+ Vector3,
+ type Intersection
} from 'three'
import { BatchObject } from '../batching/BatchObject'
import Materials from '../materials/Materials'
@@ -57,23 +62,23 @@ export enum TransformStorage {
export default class SpeckleMesh extends Mesh {
public static MeshBatchNumber = 0
- private tas: TopLevelAccelerationStructure = null
- private batchMaterial: Material = null
+ private tas: TopLevelAccelerationStructure
+ private batchMaterial: Material
private materialCache: { [id: string]: Material } = {}
private materialStack: Array = []
private materialCacheLUT: { [id: string]: number } = {}
- private _batchObjects: BatchObject[]
- private transformsBuffer: Float32Array = null
- private transformStorage: TransformStorage
+ private _batchObjects!: BatchObject[]
+ private transformsBuffer: Float32Array | undefined = undefined
+ private transformStorage!: TransformStorage
- public transformsTextureUniform: DataTexture = null
- public transformsArrayUniforms: Matrix4[] = null
+ public transformsTextureUniform: DataTexture
+ public transformsArrayUniforms: Matrix4[] | null = null
private debugBatchBox = false
- private boxHelper: Box3Helper
+ private boxHelper!: Box3Helper
- public get TAS() {
+ public get TAS(): TopLevelAccelerationStructure {
return this.tas
}
@@ -134,17 +139,23 @@ export default class SpeckleMesh extends Mesh {
this.materialCacheLUT[clone.id] = material.id
cachedMaterial = clone
this.updateMaterialTransformsUniform(this.materialCache[material.id])
- } else if (copy || material['needsCopy'] || cachedMaterial['needsCopy']) {
+ } else if (
+ copy ||
+ (material as never)['needsCopy'] ||
+ (cachedMaterial as never)['needsCopy']
+ ) {
Materials.fastCopy(material, cachedMaterial)
}
return cachedMaterial
}
public restoreMaterial() {
- if (this.materialStack.length > 0) this.material = this.materialStack.pop()
+ if (this.materialStack.length > 0)
+ this.material = this.materialStack.pop() as Material | Material[]
}
public updateMaterialTransformsUniform(material: Material) {
+ if (!material.defines) material.defines = {}
material.defines['TRANSFORM_STORAGE'] = this.transformStorage
if (this.transformStorage === TransformStorage.VERTEX_TEXTURE) {
@@ -165,6 +176,7 @@ export default class SpeckleMesh extends Mesh {
}
public updateTransformsUniform() {
+ if (!this.transformsBuffer) return
let needsUpdate = false
if (this.transformStorage === TransformStorage.VERTEX_TEXTURE) {
for (let k = 0; k < this._batchObjects.length; k++) {
@@ -196,6 +208,7 @@ export default class SpeckleMesh extends Mesh {
}
this.transformsTextureUniform.needsUpdate = needsUpdate
} else {
+ if (!this.transformsArrayUniforms) return
for (let k = 0; k < this._batchObjects.length; k++) {
const batchObject = this._batchObjects[k]
if (!(needsUpdate ||= batchObject.transformDirty)) continue
@@ -223,12 +236,17 @@ export default class SpeckleMesh extends Mesh {
if (this.tas && needsUpdate) {
this.tas.refit()
this.tas.getBoundingBox(this.tas.bounds)
+ /** Caterint to typescript
+ * There is no unniverse where the geomery bounding box/sphere is null at this point
+ */
+ if (!this.geometry.boundingBox) this.geometry.boundingBox = new Box3()
this.geometry.boundingBox.copy(this.tas.bounds)
+ if (!this.geometry.boundingSphere) this.geometry.boundingSphere = new Sphere()
this.geometry.boundingBox.getBoundingSphere(this.geometry.boundingSphere)
if (!this.boxHelper && this.debugBatchBox) {
this.boxHelper = new Box3Helper(this.tas.bounds, new Color(0xff0000))
this.boxHelper.layers.set(ObjectLayers.PROPS)
- this.parent.add(this.boxHelper)
+ if (this.parent) this.parent.add(this.boxHelper)
}
}
}
@@ -258,13 +276,17 @@ export default class SpeckleMesh extends Mesh {
)
return null
}
- return this.material[group.materialIndex]
+ return this.material[group.materialIndex as number]
}
}
// converts the given BVH raycast intersection to align with the three.js raycast
// structure (include object, world space distance and point).
- private convertRaycastIntersect(hit, object, raycaster) {
+ private convertRaycastIntersect(
+ hit: Intersection | null,
+ object: Object3D,
+ raycaster: Raycaster
+ ) {
if (hit === null) {
return null
}
@@ -280,7 +302,7 @@ export default class SpeckleMesh extends Mesh {
}
}
- raycast(raycaster: Raycaster, intersects) {
+ raycast(raycaster: Raycaster, intersects: Array) {
if (this.tas) {
if (this.batchMaterial === undefined) return
@@ -316,7 +338,7 @@ export default class SpeckleMesh extends Mesh {
if (geometry.boundingSphere === null) geometry.computeBoundingSphere()
- _sphere.copy(geometry.boundingSphere)
+ _sphere.copy(geometry.boundingSphere || new Sphere())
_sphere.applyMatrix4(matrixWorld)
if (raycaster.ray.intersectsSphere(_sphere) === false) return
@@ -336,13 +358,13 @@ export default class SpeckleMesh extends Mesh {
const index = geometry.index
/** Stored high component if RTE is being used. Regular positions otherwise */
- const position = geometry.attributes.position
+ const position = geometry.attributes.position as BufferAttribute
/** Stored low component if RTE is being used. undefined otherwise */
- const positionLow = geometry.attributes['position_low']
- const morphPosition = geometry.morphAttributes.position
+ const positionLow = geometry.attributes['position_low'] as BufferAttribute
+ const morphPosition = geometry.morphAttributes.position as Array
const morphTargetsRelative = geometry.morphTargetsRelative
- const uv = geometry.attributes.uv
- const uv2 = geometry.attributes.uv2
+ const uv = geometry.attributes.uv as BufferAttribute
+ const uv2 = geometry.attributes.uv2 as BufferAttribute
const groups = geometry.groups
const drawRange = geometry.drawRange
@@ -352,6 +374,10 @@ export default class SpeckleMesh extends Mesh {
if (Array.isArray(material)) {
for (let i = 0, il = groups.length; i < il; i++) {
const group = groups[i]
+ if (!group.materialIndex) {
+ Logger.error(`Group with no material, skipping!`)
+ continue
+ }
const groupMaterial = material[group.materialIndex]
const start = Math.max(group.start, drawRange.start)
@@ -383,7 +409,8 @@ export default class SpeckleMesh extends Mesh {
if (intersection) {
intersection.faceIndex = Math.floor(j / 3) // triangle number in indexed buffer semantics
- intersection.face.materialIndex = group.materialIndex
+ if (intersection.face)
+ intersection.face.materialIndex = group.materialIndex as number
intersects.push(intersection)
}
}
@@ -425,7 +452,7 @@ export default class SpeckleMesh extends Mesh {
if (Array.isArray(material)) {
for (let i = 0, il = groups.length; i < il; i++) {
const group = groups[i]
- const groupMaterial = material[group.materialIndex]
+ const groupMaterial = material[group.materialIndex as number]
const start = Math.max(group.start, drawRange.start)
const end = Math.min(
@@ -456,7 +483,8 @@ export default class SpeckleMesh extends Mesh {
if (intersection) {
intersection.faceIndex = Math.floor(j / 3) // triangle number in non-indexed buffer semantics
- intersection.face.materialIndex = group.materialIndex
+ if (intersection.face)
+ intersection.face.materialIndex = group.materialIndex as number
intersects.push(intersection)
}
}
@@ -497,7 +525,16 @@ export default class SpeckleMesh extends Mesh {
}
}
-function checkIntersection(object, material, raycaster, ray, pA, pB, pC, point) {
+function checkIntersection(
+ object: Object3D,
+ material: Material,
+ raycaster: Raycaster,
+ ray: Ray,
+ pA: Vector3,
+ pB: Vector3,
+ pC: Vector3,
+ point: Vector3
+): (Intersection & { uv2: Vector2 | undefined }) | null {
let intersect
if (material.side === BackSide) {
@@ -529,19 +566,19 @@ function checkIntersection(object, material, raycaster, ray, pA, pB, pC, point)
* hold the default `position` attribute values
*/
function checkBufferGeometryIntersection(
- object,
- material,
- raycaster,
- ray,
- positionLow,
- positionHigh,
- morphPosition,
- morphTargetsRelative,
- uv,
- uv2,
- a,
- b,
- c
+ object: Object3D,
+ material: Material,
+ raycaster: Raycaster,
+ ray: Ray,
+ positionLow: BufferAttribute,
+ positionHigh: BufferAttribute,
+ morphPosition: Array,
+ morphTargetsRelative: boolean,
+ uv: BufferAttribute,
+ uv2: BufferAttribute,
+ a: number,
+ b: number,
+ c: number
) {
_vA.fromBufferAttribute(positionHigh, a)
_vB.fromBufferAttribute(positionHigh, b)
@@ -552,7 +589,7 @@ function checkBufferGeometryIntersection(
_vC.add(_vTemp.fromBufferAttribute(positionLow, c))
}
- const morphInfluences = object.morphTargetInfluences
+ const morphInfluences = (object as SkinnedMesh).morphTargetInfluences
if (morphPosition && morphInfluences) {
_morphA.set(0, 0, 0)
@@ -585,10 +622,10 @@ function checkBufferGeometryIntersection(
_vC.add(_morphC)
}
- if (object.isSkinnedMesh) {
- object.boneTransform(a, _vA)
- object.boneTransform(b, _vB)
- object.boneTransform(c, _vC)
+ if ((object as SkinnedMesh).isSkinnedMesh) {
+ ;(object as SkinnedMesh).boneTransform(a, _vA)
+ ;(object as SkinnedMesh).boneTransform(b, _vB)
+ ;(object as SkinnedMesh).boneTransform(c, _vC)
}
const intersection = checkIntersection(
diff --git a/packages/viewer/src/modules/objects/SpeckleRaycaster.ts b/packages/viewer/src/modules/objects/SpeckleRaycaster.ts
index c9a223a26a..f94912ab76 100644
--- a/packages/viewer/src/modules/objects/SpeckleRaycaster.ts
+++ b/packages/viewer/src/modules/objects/SpeckleRaycaster.ts
@@ -1,7 +1,17 @@
-import { Box3, Intersection, Material, Object3D, Raycaster } from 'three'
+import {
+ Box3,
+ type Intersection,
+ Object3D,
+ Raycaster,
+ Vector3,
+ RaycasterParameters,
+ Face
+} from 'three'
import { ExtendedTriangle, ShapecastIntersection } from 'three-mesh-bvh'
import { BatchObject } from '../batching/BatchObject'
import { ObjectLayers } from '../../IViewer'
+import SpeckleMesh from './SpeckleMesh'
+import SpeckleInstancedMesh from './SpeckleInstancedMesh'
export type ExtendedShapeCastCallbacks = {
intersectsTAS?: (
@@ -45,13 +55,29 @@ export type ExtendedShapeCastCallbacks = {
export interface ExtendedIntersection extends Intersection {
batchObject?: BatchObject
- material?: Material
+ pointOnLine?: Vector3
+ // material?: Material
+}
+
+export interface MeshIntersection extends Intersection {
+ face: Face
+ faceIndex: number
+}
+
+export interface ExtendedMeshIntersection extends MeshIntersection {
+ batchObject: BatchObject
+ object: SpeckleMesh | SpeckleInstancedMesh
+}
+
+export interface ExtendedRaycasterParameters extends RaycasterParameters {
+ Line2: { threshold: number }
}
export class SpeckleRaycaster extends Raycaster {
- public onObjectIntersectionTest: (object: Object3D) => void = null
+ public onObjectIntersectionTest: ((object: Object3D) => void) | null = null
+ public params: ExtendedRaycasterParameters
- constructor(origin?, direction?, near = 0, far = Infinity) {
+ constructor(origin?: Vector3, direction?: Vector3, near = 0, far = Infinity) {
super(origin, direction, near, far)
this.layers.disableAll()
this.layers.enable(ObjectLayers.STREAM_CONTENT)
@@ -61,9 +87,10 @@ export class SpeckleRaycaster extends Raycaster {
this.layers.enable(ObjectLayers.STREAM_CONTENT_POINT_CLOUD)
// OFF by default
this.layers.enable(ObjectLayers.STREAM_CONTENT_POINT)
+ this.params = { Line2: { threshold: 0 } }
}
- public intersectObjects(objects, recursive = true, intersects = []) {
+ public intersectObjects(objects: Array, recursive = true, intersects = []) {
for (let i = 0, l = objects.length; i < l; i++) {
intersectObject(objects[i], this, intersects, recursive)
}
@@ -74,11 +101,16 @@ export class SpeckleRaycaster extends Raycaster {
}
}
-function ascSort(a, b) {
+function ascSort(a: Intersection, b: Intersection) {
return a.distance - b.distance
}
-function intersectObject(object, raycaster, intersects, recursive) {
+function intersectObject(
+ object: Object3D,
+ raycaster: SpeckleRaycaster,
+ intersects: Array,
+ recursive: boolean
+) {
if (object.layers.test(raycaster.layers)) {
if (raycaster.onObjectIntersectionTest) {
raycaster.onObjectIntersectionTest(object)
@@ -87,7 +119,9 @@ function intersectObject(object, raycaster, intersects, recursive) {
}
recursive &&=
// eslint-disable-next-line eqeqeq
- object.userData.raycastChildren != undefined ? object.raycastChildren : true
+ object.userData.raycastChildren != undefined
+ ? object.userData.raycastChildren
+ : true
if (recursive === true) {
const children = object.children
diff --git a/packages/viewer/src/modules/objects/SpeckleText.ts b/packages/viewer/src/modules/objects/SpeckleText.ts
index 6610d60c03..589522f15a 100644
--- a/packages/viewer/src/modules/objects/SpeckleText.ts
+++ b/packages/viewer/src/modules/objects/SpeckleText.ts
@@ -8,14 +8,17 @@ import {
MeshBasicMaterial,
PlaneGeometry,
Quaternion,
+ Raycaster,
Vector2,
Vector3,
- Vector4
+ Vector4,
+ type Intersection
} from 'three'
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+//@ts-ignore
import { Text } from 'troika-three-text'
-import { SpeckleObject } from '../tree/DataTree'
import SpeckleBasicMaterial from '../materials/SpeckleBasicMaterial'
-import { ObjectLayers } from '../../IViewer'
+import { ObjectLayers, type SpeckleObject } from '../../IViewer'
export interface SpeckleTextParams {
textValue?: string
@@ -26,7 +29,7 @@ export interface SpeckleTextParams {
}
export interface SpeckleTextStyle {
- backgroundColor?: Color
+ backgroundColor?: Color | null
backgroundCornerRadius?: number
backgroundPixelHeight?: number
textColor?: Color
@@ -44,7 +47,7 @@ const DefaultSpeckleTextStyle: SpeckleTextStyle = {
export class SpeckleText extends Mesh {
private _layer: ObjectLayers = ObjectLayers.NONE
private _text: Text = null
- private _background: Mesh = null
+ private _background: Mesh | null = null
private _backgroundSize: Vector3 = new Vector3()
private _style: SpeckleTextStyle = Object.assign({}, DefaultSpeckleTextStyle)
private _resolution: Vector2 = new Vector2()
@@ -136,9 +139,11 @@ export class SpeckleText extends Mesh {
if (position) {
if (this._style.billboard) {
this.textMesh.material.userData.billboardPos.value.copy(position)
- ;(
- this._background.material as SpeckleBasicMaterial
- ).userData.billboardPos.value.copy(position)
+ if (this._background) {
+ ;(
+ this._background.material as SpeckleBasicMaterial
+ ).userData.billboardPos.value.copy(position)
+ }
}
this.position.copy(position)
}
@@ -146,7 +151,7 @@ export class SpeckleText extends Mesh {
if (scale) this.scale.copy(scale)
}
- public raycast(raycaster, intersects) {
+ public raycast(raycaster: Raycaster, intersects: Array) {
const { textRenderInfo, curveRadius } = this.textMesh
if (textRenderInfo) {
const bounds = textRenderInfo.blockBounds
@@ -206,7 +211,7 @@ export class SpeckleText extends Mesh {
raycastMesh.matrixWorld = this.textMesh.matrixWorld
}
raycastMesh.material.side = this.textMesh.material.side
- const tempArray = []
+ const tempArray: Array = []
raycastMesh.raycast(raycaster, tempArray)
for (let i = 0; i < tempArray.length; i++) {
tempArray[i].object = this
@@ -221,7 +226,7 @@ export class SpeckleText extends Mesh {
private updateBackground() {
if (!this._style.backgroundColor) {
- this.remove(this._background)
+ if (this._background) this.remove(this._background)
this._background = null
return
}
@@ -251,7 +256,9 @@ export class SpeckleText extends Mesh {
const color = new Color(this._style.backgroundColor).convertSRGBToLinear()
;(this._background.material as SpeckleBasicMaterial).color = color
;(this._background.material as SpeckleBasicMaterial).billboardPixelHeight =
- this._style.backgroundPixelHeight * window.devicePixelRatio
+ (this._style.backgroundPixelHeight !== undefined
+ ? this._style.backgroundPixelHeight
+ : DefaultSpeckleTextStyle.backgroundPixelHeight || 0) * window.devicePixelRatio
}
/** From https://discourse.threejs.org/t/roundedrectangle-squircle/28645 */
@@ -282,7 +289,7 @@ export class SpeckleText extends Mesh {
return geometry
- function contour(j) {
+ function contour(j: number) {
qu = Math.trunc((4 * j) / n) + 1 // quadrant qu: 1..4
sgx = qu === 1 || qu === 4 ? 1 : -1 // signum left/right
sgy = qu < 3 ? 1 : -1 // signum top / bottom
diff --git a/packages/viewer/src/modules/objects/SpeckleWebGLRenderer.ts b/packages/viewer/src/modules/objects/SpeckleWebGLRenderer.ts
index 2a08fe2b6d..c5af9c90a4 100644
--- a/packages/viewer/src/modules/objects/SpeckleWebGLRenderer.ts
+++ b/packages/viewer/src/modules/objects/SpeckleWebGLRenderer.ts
@@ -1,7 +1,7 @@
import { Camera, Matrix4, Vector3, WebGLRenderer } from 'three'
import { Geometry } from '../converter/Geometry'
export class RTEBuffers {
- private _cache: RTEBuffers = null
+ private _cache: RTEBuffers | undefined
viewer: Vector3 = new Vector3()
viewerLow: Vector3 = new Vector3()
diff --git a/packages/viewer/src/modules/objects/TopLevelAccelerationStructure.ts b/packages/viewer/src/modules/objects/TopLevelAccelerationStructure.ts
index 6d2b2f08df..69cbb961f7 100644
--- a/packages/viewer/src/modules/objects/TopLevelAccelerationStructure.ts
+++ b/packages/viewer/src/modules/objects/TopLevelAccelerationStructure.ts
@@ -1,19 +1,22 @@
import {
Box3,
Box3Helper,
+ BufferAttribute,
Color,
FrontSide,
- Intersection,
Material,
Matrix4,
- Object3D,
Ray,
Side,
Vector3
} from 'three'
import { ExtendedTriangle } from 'three-mesh-bvh'
import { BatchObject } from '../batching/BatchObject'
-import { ExtendedIntersection, ExtendedShapeCastCallbacks } from './SpeckleRaycaster'
+import type {
+ ExtendedMeshIntersection,
+ ExtendedShapeCastCallbacks,
+ MeshIntersection
+} from './SpeckleRaycaster'
import { ObjectLayers } from '../../IViewer'
import { AccelerationStructure } from './AccelerationStructure'
@@ -55,8 +58,7 @@ export class TopLevelAccelerationStructure {
public bounds: Box3 = new Box3(new Vector3(0, 0, 0), new Vector3(0, 0, 0))
public boxHelpers: Box3Helper[] = []
- public accelerationStructure: AccelerationStructure = null
- public lastRefitTime = 0
+ public accelerationStructure: AccelerationStructure
public constructor(batchObjects: BatchObject[]) {
this.batchObjects = batchObjects
@@ -66,7 +68,7 @@ export class TopLevelAccelerationStructure {
private buildBVH() {
const indices = []
- const vertices = new Float32Array(
+ const vertices: number[] = new Array(
TopLevelAccelerationStructure.CUBE_VERTS * 3 * this.batchObjects.length
)
let vertOffset = 0
@@ -99,7 +101,7 @@ export class TopLevelAccelerationStructure {
this.accelerationStructure.outputOriginTransfom = new Matrix4()
}
- private updateVertArray(box: Box3, offset: number, outPositions: Float32Array) {
+ private updateVertArray(box: Box3, offset: number, outPositions: number[]) {
outPositions[offset] = box.min.x
outPositions[offset + 1] = box.min.y
outPositions[offset + 2] = box.max.z
@@ -134,38 +136,39 @@ export class TopLevelAccelerationStructure {
}
public refit() {
- const start = performance.now()
- const positions = this.accelerationStructure.geometry.attributes.position.array
+ const positions = this.accelerationStructure.geometry.attributes.position
+ .array as number[]
const boxBuffer: Box3 = new Box3()
for (let k = 0; k < this.batchObjects.length; k++) {
const start = this.batchObjects[k].tasVertIndexStart
const basBox =
this.batchObjects[k].accelerationStructure.getBoundingBox(boxBuffer)
- this.updateVertArray(basBox, start * 3, positions as Float32Array)
+ this.updateVertArray(basBox, start * 3, positions)
if (TopLevelAccelerationStructure.debugBoxes) this.boxHelpers[k].box.copy(basBox)
}
this.accelerationStructure.bvh.refit()
- this.lastRefitTime = performance.now() - start
}
/* Core Cast Functions */
public raycast(
ray: Ray,
materialOrSide: Side | Material | Material[] = FrontSide
- ): ExtendedIntersection[] {
- const res = []
+ ): ExtendedMeshIntersection[] {
+ const res: ExtendedMeshIntersection[] = []
const rayBuff = new Ray()
rayBuff.copy(ray)
- const tasResults: Intersection[] = this.accelerationStructure.raycast(
+ const tasResults: MeshIntersection[] = this.accelerationStructure.raycast(
rayBuff,
materialOrSide
)
if (!tasResults.length) return res
- tasResults.forEach((tasRes: Intersection) => {
- const vertIndex =
- this.accelerationStructure.geometry.index.array[tasRes.faceIndex * 3]
+ /** The index buffer for the bvh's geometry will *never* be undefined as it uses indexed geometry */
+ const indexBufferAttribute: BufferAttribute = this.accelerationStructure.geometry
+ .index as BufferAttribute
+ tasResults.forEach((tasRes: MeshIntersection) => {
+ const vertIndex = indexBufferAttribute.array[tasRes.faceIndex * 3]
const batchObjectIndex = Math.trunc(
vertIndex / TopLevelAccelerationStructure.CUBE_VERTS
)
@@ -175,9 +178,13 @@ export class TopLevelAccelerationStructure {
materialOrSide
)
hits.forEach((hit) => {
- ;(hit as ExtendedIntersection).batchObject = this.batchObjects[batchObjectIndex]
+ /** We're promoting the MeshIntersection to ExtendedMeshIntersection because
+ * now we know it's corresponding batch object
+ */
+ const extendedHit: ExtendedMeshIntersection = hit as ExtendedMeshIntersection
+ extendedHit.batchObject = this.batchObjects[batchObjectIndex]
+ res.push(extendedHit)
})
- res.push(...hits)
})
return res
@@ -186,30 +193,33 @@ export class TopLevelAccelerationStructure {
public raycastFirst(
ray: Ray,
materialOrSide: Side | Material | Material[] = FrontSide
- ): ExtendedIntersection {
- const res = null
+ ): ExtendedMeshIntersection | null {
const rayBuff = new Ray()
rayBuff.copy(ray)
- const tasRes: Intersection = this.accelerationStructure.raycastFirst(
+ const tasRes: MeshIntersection = this.accelerationStructure.raycastFirst(
rayBuff,
materialOrSide
)
- if (!tasRes) return res
+ if (!tasRes) return null
- const vertIndex =
- this.accelerationStructure.geometry.index.array[tasRes.faceIndex * 3]
+ /** The index buffer for the bvh's geometry will *never* be undefined as it uses indexed geometry */
+ const indexBufferAttribute: BufferAttribute = this.accelerationStructure.geometry
+ .index as BufferAttribute
+ const vertIndex = indexBufferAttribute.array[tasRes.faceIndex * 3]
const batchObjectIndex = Math.trunc(
vertIndex / TopLevelAccelerationStructure.CUBE_VERTS
)
rayBuff.copy(ray)
- const hits = this.batchObjects[batchObjectIndex].accelerationStructure.raycast(
- rayBuff,
- materialOrSide
- )
- hits.forEach((hit) => {
- ;(hit as ExtendedIntersection).batchObject = this.batchObjects[batchObjectIndex]
- })
- res.push(...hits)
+ const hit: MeshIntersection = this.batchObjects[
+ batchObjectIndex
+ ].accelerationStructure.raycastFirst(rayBuff, materialOrSide)
+ /** We're promoting the MeshIntersection to ExtendedMeshIntersection because
+ * now we know it's corresponding batch object
+ */
+ const extendedHit: ExtendedMeshIntersection = hit as ExtendedMeshIntersection
+ extendedHit.batchObject = this.batchObjects[batchObjectIndex]
+
+ return extendedHit
}
public shapecast(callbacks: ExtendedShapeCastCallbacks): boolean {
@@ -254,12 +264,15 @@ export class TopLevelAccelerationStructure {
let ret = false
this.accelerationStructure.shapecast({
intersectsBounds: (box, isLeaf, score, depth, nodeIndex) => {
- const res = callbacks.intersectsTAS(box, isLeaf, score, depth, nodeIndex)
- return res
+ if (callbacks.intersectsTAS)
+ return callbacks.intersectsTAS(box, isLeaf, score, depth, nodeIndex)
+ return false
},
intersectsRange: (triangleOffset: number) => {
- const vertIndex =
- this.accelerationStructure.geometry.index.array[triangleOffset * 3]
+ /** The index buffer for the bvh's geometry will *never* be undefined as it uses indexed geometry */
+ const indexBufferAttribute: BufferAttribute = this.accelerationStructure
+ .geometry.index as BufferAttribute
+ const vertIndex = indexBufferAttribute.array[triangleOffset * 3]
const batchObjectIndex = Math.trunc(
vertIndex / TopLevelAccelerationStructure.CUBE_VERTS
)
diff --git a/packages/viewer/src/modules/pipeline/ApplyAOPass.ts b/packages/viewer/src/modules/pipeline/ApplyAOPass.ts
index 66243f6ac9..6e2d526f9b 100644
--- a/packages/viewer/src/modules/pipeline/ApplyAOPass.ts
+++ b/packages/viewer/src/modules/pipeline/ApplyAOPass.ts
@@ -1,22 +1,24 @@
import {
AddEquation,
- Camera,
CustomBlending,
DstAlphaFactor,
DstColorFactor,
NoBlending,
+ OrthographicCamera,
+ PerspectiveCamera,
Scene,
ShaderMaterial,
Texture,
+ WebGLRenderer,
ZeroFactor
} from 'three'
import { FullScreenQuad, Pass } from 'three/examples/jsm/postprocessing/Pass.js'
import { speckleApplyAoFrag } from '../materials/shaders/speckle-apply-ao-frag'
import { speckleApplyAoVert } from '../materials/shaders/speckle-apply-ao-vert'
import {
- InputColorTextureUniform,
- InputColorInterpolateTextureUniform,
- SpeckleProgressivePass,
+ type InputColorTextureUniform,
+ type InputColorInterpolateTextureUniform,
+ type SpeckleProgressivePass,
RenderType
} from './SpecklePass'
@@ -68,7 +70,7 @@ export class ApplySAOPass extends Pass implements SpeckleProgressivePass {
return 'APPLYSAO'
}
- get outputTexture(): Texture {
+ get outputTexture(): Texture | null {
return null
}
@@ -94,7 +96,7 @@ export class ApplySAOPass extends Pass implements SpeckleProgressivePass {
this.materialCopy.needsUpdate = true
}
- public update(scene: Scene, camera: Camera) {
+ public update(scene: Scene, camera: PerspectiveCamera | OrthographicCamera) {
scene
camera
this.materialCopy.defines['NUM_FRAMES'] = this.accumulatioFrames
@@ -102,9 +104,7 @@ export class ApplySAOPass extends Pass implements SpeckleProgressivePass {
this.materialCopy.needsUpdate = true
}
- render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) {
- writeBuffer
- readBuffer
+ render(renderer: WebGLRenderer) {
renderer.setRenderTarget(null)
const rendereAutoClear = renderer.autoClear
renderer.autoClear = false
diff --git a/packages/viewer/src/modules/pipeline/ColorPass.ts b/packages/viewer/src/modules/pipeline/ColorPass.ts
index c43d06e08f..394fd16f0b 100644
--- a/packages/viewer/src/modules/pipeline/ColorPass.ts
+++ b/packages/viewer/src/modules/pipeline/ColorPass.ts
@@ -1,19 +1,29 @@
-import { Camera, Color, Material, Scene, Texture } from 'three'
-import { BaseSpecklePass, SpecklePass } from './SpecklePass'
+import {
+ Camera,
+ Color,
+ Material,
+ OrthographicCamera,
+ PerspectiveCamera,
+ Scene,
+ Texture,
+ WebGLRenderTarget,
+ WebGLRenderer
+} from 'three'
+import { BaseSpecklePass, type SpecklePass } from './SpecklePass'
export class ColorPass extends BaseSpecklePass implements SpecklePass {
- private camera: Camera
- private scene: Scene
- private overrideMaterial: Material = null
+ private camera: Camera | null = null
+ private scene: Scene | null = null
+ private overrideMaterial: Material
private _oldClearColor: Color = new Color()
- private clearColor: Color = null
+ private clearColor: Color
private clearAlpha = 0
private clearDepth = true
- public onBeforeRenderOpauqe: () => void = null
- public onAfterRenderOpaque: () => void = null
- public onBeforeRenderTransparent: () => void = null
- public onAfterRenderTransparent: () => void = null
+ public onBeforeRenderOpauqe: (() => void) | undefined = undefined
+ public onAfterRenderOpaque: (() => void) | undefined = undefined
+ public onBeforeRenderTransparent: (() => void) | undefined = undefined
+ public onAfterRenderTransparent: (() => void) | undefined = undefined
public constructor() {
super()
@@ -21,20 +31,26 @@ export class ColorPass extends BaseSpecklePass implements SpecklePass {
public get displayName(): string {
return 'COLOR'
}
- public get outputTexture(): Texture {
+ public get outputTexture(): Texture | null {
return null
}
- public update(scene: Scene, camera: Camera) {
+ public update(scene: Scene, camera: PerspectiveCamera | OrthographicCamera) {
this.camera = camera
this.scene = scene
}
- render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) {
+ render(
+ renderer: WebGLRenderer,
+ _writeBuffer: WebGLRenderTarget,
+ readBuffer: WebGLRenderTarget
+ ) {
+ if (!this.camera || !this.scene) return
+
const oldAutoClear = renderer.autoClear
renderer.autoClear = false
- let oldClearAlpha, oldOverrideMaterial
+ let oldClearAlpha, oldOverrideMaterial!: Material | null
if (this.overrideMaterial !== undefined) {
oldOverrideMaterial = this.scene.overrideMaterial
diff --git a/packages/viewer/src/modules/pipeline/CopyOutputPass.ts b/packages/viewer/src/modules/pipeline/CopyOutputPass.ts
index 1de9884acd..eea087451e 100644
--- a/packages/viewer/src/modules/pipeline/CopyOutputPass.ts
+++ b/packages/viewer/src/modules/pipeline/CopyOutputPass.ts
@@ -1,10 +1,16 @@
-import { NoBlending, ShaderMaterial, Texture, UniformsUtils } from 'three'
+import {
+ NoBlending,
+ ShaderMaterial,
+ Texture,
+ UniformsUtils,
+ WebGLRenderer
+} from 'three'
import { FullScreenQuad, Pass } from 'three/examples/jsm/postprocessing/Pass.js'
import { CopyShader } from 'three/examples/jsm/shaders/CopyShader.js'
import { speckleCopyOutputFrag } from '../materials/shaders/speckle-copy-output-frag'
import { speckleCopyOutputVert } from '../materials/shaders/speckle-copy-output-vert'
import { PipelineOutputType } from './Pipeline'
-import { InputColorTextureUniform, SpecklePass } from './SpecklePass'
+import type { InputColorTextureUniform, SpecklePass } from './SpecklePass'
export class CopyOutputPass extends Pass implements SpecklePass {
private fsQuad: FullScreenQuad
@@ -40,13 +46,11 @@ export class CopyOutputPass extends Pass implements SpecklePass {
return 'COPY-OUTPUT'
}
- get outputTexture(): Texture {
+ get outputTexture(): Texture | null {
return null
}
- render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) {
- writeBuffer
- readBuffer
+ render(renderer: WebGLRenderer) {
renderer.setRenderTarget(null)
const rendereAutoClear = renderer.autoClear
renderer.autoClear = false
diff --git a/packages/viewer/src/modules/pipeline/DepthPass.ts b/packages/viewer/src/modules/pipeline/DepthPass.ts
index 26d7bfb516..4f643df593 100644
--- a/packages/viewer/src/modules/pipeline/DepthPass.ts
+++ b/packages/viewer/src/modules/pipeline/DepthPass.ts
@@ -12,10 +12,11 @@ import {
Scene,
Side,
Texture,
- WebGLRenderTarget
+ WebGLRenderTarget,
+ WebGLRenderer
} from 'three'
import SpeckleDepthMaterial from '../materials/SpeckleDepthMaterial'
-import { BaseSpecklePass, SpecklePass } from './SpecklePass'
+import { BaseSpecklePass, type SpecklePass } from './SpecklePass'
export enum DepthType {
PERSPECTIVE_DEPTH,
@@ -30,15 +31,15 @@ export enum DepthSize {
export class DepthPass extends BaseSpecklePass implements SpecklePass {
private renderTarget: WebGLRenderTarget
private renderTargetHalf: WebGLRenderTarget
- private depthMaterial: SpeckleDepthMaterial = null
+ private depthMaterial: SpeckleDepthMaterial
private depthBufferSize: DepthSize = DepthSize.FULL
- private scene: Scene
- private camera: Camera
+ private scene: Scene | null
+ private camera: Camera | null
private colorBuffer: Color = new Color()
- public onBeforeRender: () => void = null
- public onAfterRender: () => void = null
+ public onBeforeRender: (() => void) | undefined = undefined
+ public onAfterRender: (() => void) | undefined = undefined
get displayName(): string {
return 'DEPTH'
@@ -58,8 +59,16 @@ export class DepthPass extends BaseSpecklePass implements SpecklePass {
public set depthType(value: DepthType) {
if (value === DepthType.LINEAR_DEPTH)
- this.depthMaterial.defines['LINEAR_DEPTH'] = ' '
- else delete this.depthMaterial.defines['LINEAR_DEPTH']
+ if (this.depthMaterial.defines) {
+ /** Catering to typescript
+ * SpeckleDepthMaterial always has it's 'defines' defined
+ */
+ this.depthMaterial.defines['LINEAR_DEPTH'] = ' '
+ } else {
+ if (this.depthMaterial.defines) {
+ delete this.depthMaterial.defines['LINEAR_DEPTH']
+ }
+ }
this.depthMaterial.needsUpdate = true
}
@@ -107,7 +116,7 @@ export class DepthPass extends BaseSpecklePass implements SpecklePass {
this.depthMaterial.clippingPlanes = planes
}
- public update(scene: Scene, camera: Camera) {
+ public update(scene: Scene, camera: PerspectiveCamera | OrthographicCamera) {
this.camera = camera
this.scene = scene
this.depthMaterial.userData.near.value = (
@@ -119,11 +128,10 @@ export class DepthPass extends BaseSpecklePass implements SpecklePass {
this.depthMaterial.needsUpdate = true
}
- public render(renderer, writeBuffer, readBuffer) {
- writeBuffer
- readBuffer
+ public render(renderer: WebGLRenderer) {
+ if (!this.camera || !this.scene) return
- this.onBeforeRender()
+ if (this.onBeforeRender) this.onBeforeRender()
renderer.getClearColor(this.colorBuffer)
const originalClearAlpha = renderer.getClearAlpha()
const originalAutoClear = renderer.autoClear
@@ -154,7 +162,7 @@ export class DepthPass extends BaseSpecklePass implements SpecklePass {
renderer.autoClear = originalAutoClear
renderer.setClearColor(this.colorBuffer)
renderer.setClearAlpha(originalClearAlpha)
- this.onAfterRender()
+ if (this.onAfterRender) this.onAfterRender()
}
public setSize(width: number, height: number) {
diff --git a/packages/viewer/src/modules/pipeline/DynamicAOPass.ts b/packages/viewer/src/modules/pipeline/DynamicAOPass.ts
index ba96eff665..ebf9cabdd5 100644
--- a/packages/viewer/src/modules/pipeline/DynamicAOPass.ts
+++ b/packages/viewer/src/modules/pipeline/DynamicAOPass.ts
@@ -1,5 +1,4 @@
import {
- Camera,
Color,
NoBlending,
OrthographicCamera,
@@ -9,7 +8,8 @@ import {
Texture,
UniformsUtils,
Vector2,
- WebGLRenderTarget
+ WebGLRenderTarget,
+ WebGLRenderer
} from 'three'
import { FullScreenQuad, Pass } from 'three/examples/jsm/postprocessing/Pass.js'
import { speckleSaoFrag } from '../materials/shaders/speckle-sao-frag'
@@ -17,7 +17,7 @@ import { speckleSaoVert } from '../materials/shaders/speckle-sao-vert'
import { SAOShader } from 'three/examples/jsm/shaders/SAOShader.js'
import { DepthLimitedBlurShader } from 'three/examples/jsm/shaders/DepthLimitedBlurShader.js'
import { BlurShaderUtils } from 'three/examples/jsm/shaders/DepthLimitedBlurShader.js'
-import {
+import type {
InputDepthTextureUniform,
InputNormalsTextureUniform,
SpecklePass
@@ -62,17 +62,17 @@ export const DefaultDynamicAOPassParams = {
export class DynamicSAOPass extends Pass implements SpecklePass {
private params: DynamicAOPassParams = DefaultDynamicAOPassParams
private colorBuffer: Color = new Color()
- private saoMaterial: ShaderMaterial = null
- private vBlurMaterial: ShaderMaterial = null
- private hBlurMaterial: ShaderMaterial = null
- private saoRenderTarget: WebGLRenderTarget = null
- private blurIntermediateRenderTarget: WebGLRenderTarget = null
- private fsQuad: FullScreenQuad = null
+ private saoMaterial: ShaderMaterial
+ private vBlurMaterial: ShaderMaterial
+ private hBlurMaterial: ShaderMaterial
+ private saoRenderTarget: WebGLRenderTarget
+ private blurIntermediateRenderTarget: WebGLRenderTarget
+ private fsQuad: FullScreenQuad
private _outputType: DynamicAOOutputType = DynamicAOOutputType.AO_BLURRED
private outputScale = 0.5
- private prevStdDev: number
- private prevNumSamples: number
+ private prevStdDev: number = 0
+ private prevNumSamples: number = 0
public get displayName(): string {
return 'SAO'
@@ -163,7 +163,7 @@ export class DynamicSAOPass extends Pass implements SpecklePass {
this.hBlurMaterial.needsUpdate = true
}
- public update(scene: Scene, camera: Camera) {
+ public update(_scene: Scene, camera: PerspectiveCamera | OrthographicCamera) {
if (this._outputType === DynamicAOOutputType.RECONSTRUCTED_NORMALS) {
this.saoMaterial.defines['OUTPUT_RECONSTRUCTED_NORMALS'] = ''
} else {
@@ -260,7 +260,7 @@ export class DynamicSAOPass extends Pass implements SpecklePass {
this.hBlurMaterial.needsUpdate = true
}
- public render(renderer) {
+ public render(renderer: WebGLRenderer) {
// Rendering SAO texture
renderer.getClearColor(this.colorBuffer)
const originalClearAlpha = renderer.getClearAlpha()
diff --git a/packages/viewer/src/modules/pipeline/NormalsPass.ts b/packages/viewer/src/modules/pipeline/NormalsPass.ts
index a8dd4ddd53..180bb31cd2 100644
--- a/packages/viewer/src/modules/pipeline/NormalsPass.ts
+++ b/packages/viewer/src/modules/pipeline/NormalsPass.ts
@@ -4,24 +4,27 @@ import {
DoubleSide,
Material,
NoBlending,
+ OrthographicCamera,
+ PerspectiveCamera,
Plane,
Scene,
Texture,
- WebGLRenderTarget
+ WebGLRenderTarget,
+ WebGLRenderer
} from 'three'
import SpeckleNormalMaterial from '../materials/SpeckleNormalMaterial'
-import { BaseSpecklePass, SpecklePass } from './SpecklePass'
+import { BaseSpecklePass, type SpecklePass } from './SpecklePass'
export class NormalsPass extends BaseSpecklePass implements SpecklePass {
private renderTarget: WebGLRenderTarget
- private normalsMaterial: SpeckleNormalMaterial = null
+ private normalsMaterial: SpeckleNormalMaterial
private scene: Scene
private camera: Camera
private colorBuffer: Color = new Color()
- public onBeforeRender: () => void = null
- public onAfterRender: () => void = null
+ public onBeforeRender: (() => void) | undefined = undefined
+ public onAfterRender: (() => void) | undefined = undefined
get displayName(): string {
return 'GEOMETRY-NORMALS'
@@ -55,16 +58,13 @@ export class NormalsPass extends BaseSpecklePass implements SpecklePass {
this.normalsMaterial.clippingPlanes = planes
}
- public update(scene: Scene, camera: Camera) {
+ public update(scene: Scene, camera: PerspectiveCamera | OrthographicCamera) {
this.camera = camera
this.scene = scene
}
- public render(renderer, writeBuffer, readBuffer) {
- writeBuffer
- readBuffer
-
- this.onBeforeRender()
+ public render(renderer: WebGLRenderer) {
+ if (this.onBeforeRender) this.onBeforeRender()
renderer.getClearColor(this.colorBuffer)
const originalClearAlpha = renderer.getClearAlpha()
const originalAutoClear = renderer.autoClear
@@ -91,7 +91,7 @@ export class NormalsPass extends BaseSpecklePass implements SpecklePass {
renderer.autoClear = originalAutoClear
renderer.setClearColor(this.colorBuffer)
renderer.setClearAlpha(originalClearAlpha)
- this.onAfterRender()
+ if (this.onAfterRender) this.onAfterRender()
}
public setSize(width: number, height: number) {
diff --git a/packages/viewer/src/modules/pipeline/OverlayPass.ts b/packages/viewer/src/modules/pipeline/OverlayPass.ts
index b779942884..825df75c2c 100644
--- a/packages/viewer/src/modules/pipeline/OverlayPass.ts
+++ b/packages/viewer/src/modules/pipeline/OverlayPass.ts
@@ -1,12 +1,20 @@
-import { Camera, Scene, Texture } from 'three'
-import { BaseSpecklePass, SpecklePass } from './SpecklePass'
+import {
+ Camera,
+ OrthographicCamera,
+ PerspectiveCamera,
+ Scene,
+ Texture,
+ WebGLRenderTarget,
+ WebGLRenderer
+} from 'three'
+import { BaseSpecklePass, type SpecklePass } from './SpecklePass'
export class OverlayPass extends BaseSpecklePass implements SpecklePass {
- private camera: Camera
- private scene: Scene
+ private camera!: Camera
+ private scene!: Scene
- public onBeforeRender: () => void = null
- public onAfterRender: () => void = null
+ public onBeforeRender: (() => void) | undefined = undefined
+ public onAfterRender: (() => void) | undefined = undefined
public constructor() {
super()
@@ -14,16 +22,20 @@ export class OverlayPass extends BaseSpecklePass implements SpecklePass {
public get displayName(): string {
return 'OVERLAY'
}
- public get outputTexture(): Texture {
+ public get outputTexture(): Texture | null {
return null
}
- public update(scene: Scene, camera: Camera) {
+ public update(scene: Scene, camera: PerspectiveCamera | OrthographicCamera) {
this.camera = camera
this.scene = scene
}
- render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) {
+ render(
+ renderer: WebGLRenderer,
+ _writeBuffer: WebGLRenderTarget,
+ readBuffer: WebGLRenderTarget
+ ) {
const oldAutoClear = renderer.autoClear
renderer.autoClear = false
this.applyLayers(this.camera)
diff --git a/packages/viewer/src/modules/pipeline/Pipeline.ts b/packages/viewer/src/modules/pipeline/Pipeline.ts
index 2783f13e51..8d48202647 100644
--- a/packages/viewer/src/modules/pipeline/Pipeline.ts
+++ b/packages/viewer/src/modules/pipeline/Pipeline.ts
@@ -1,8 +1,5 @@
import { DoubleSide, Plane, Side, Vector2, WebGLRenderer } from 'three'
-import {
- EffectComposer,
- Pass
-} from 'three/examples/jsm/postprocessing/EffectComposer.js'
+import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import Batcher from '../batching/Batcher'
import SpeckleRenderer from '../SpeckleRenderer'
import { ApplySAOPass } from './ApplyAOPass'
@@ -13,20 +10,21 @@ import {
DefaultDynamicAOPassParams,
DynamicSAOPass,
DynamicAOOutputType,
- DynamicAOPassParams,
+ type DynamicAOPassParams,
NormalsType
} from './DynamicAOPass'
import {
DefaultStaticAoPassParams,
StaticAOPass,
- StaticAoPassParams
+ type StaticAoPassParams
} from './StaticAOPass'
-import { RenderType, SpecklePass } from './SpecklePass'
+import { BaseSpecklePass, RenderType, type SpecklePass } from './SpecklePass'
import { ColorPass } from './ColorPass'
import { StencilPass } from './StencilPass'
import { StencilMaskPass } from './StencilMaskPass'
import { OverlayPass } from './OverlayPass'
import { ObjectLayers } from '../../IViewer'
+import type { BatchUpdateRange } from '../batching/Batch'
export enum PipelineOutputType {
DEPTH_RGBA = 0,
@@ -61,40 +59,42 @@ export const DefaultPipelineOptions: PipelineOptions = {
}
export class Pipeline {
- private _renderer: WebGLRenderer = null
- private _batcher: Batcher = null
+ private _renderer: WebGLRenderer
+ private _batcher: Batcher
private _pipelineOptions: PipelineOptions = Object.assign({}, DefaultPipelineOptions)
private _needsProgressive = false
private _resetFrame = false
- private _composer: EffectComposer = null
-
- private depthPass: DepthPass = null
- private normalsPass: NormalsPass = null
- private stencilPass: StencilPass = null
- private renderPass: ColorPass = null
- private stencilMaskPass: StencilMaskPass = null
- private dynamicAoPass: DynamicSAOPass = null
- private applySaoPass: ApplySAOPass = null
- private copyOutputPass: CopyOutputPass = null
- private staticAoPass: StaticAOPass = null
- private overlayPass: OverlayPass = null
+ private _composer: EffectComposer
+
+ private depthPass: DepthPass
+ private normalsPass: NormalsPass
+ private stencilPass: StencilPass
+ private renderPass: ColorPass
+ private stencilMaskPass: StencilMaskPass
+ private dynamicAoPass: DynamicSAOPass
+ private applySaoPass: ApplySAOPass
+ private copyOutputPass: CopyOutputPass
+ private staticAoPass: StaticAOPass
+ private overlayPass: OverlayPass
private drawingSize: Vector2 = new Vector2()
private _renderType: RenderType = RenderType.NORMAL
private accumulationFrame = 0
- private onBeforePipelineRender = null
- private onAfterPipelineRender = null
+ private onBeforePipelineRender: (() => void) | null = null
+ private onAfterPipelineRender: (() => void) | null = null
public set pipelineOptions(options: Partial) {
Object.assign(this._pipelineOptions, options)
this.dynamicAoPass.setParams(options.dynamicAoParams)
this.staticAoPass.setParams(options.staticAoParams)
this.accumulationFrame = 0
- this.depthPass.depthSide = options.depthSide
- this.applySaoPass.setAccumulationFrames(options.accumulationFrames)
- this.staticAoPass.setAccumulationFrames(options.accumulationFrames)
- this.pipelineOutput = options.pipelineOutput
+ if (options.depthSide) this.depthPass.depthSide = options.depthSide
+ if (options.accumulationFrames) {
+ this.applySaoPass.setAccumulationFrames(options.accumulationFrames)
+ this.staticAoPass.setAccumulationFrames(options.accumulationFrames)
+ }
+ if (options.pipelineOutput) this.pipelineOutput = options.pipelineOutput
}
public get pipelineOptions(): PipelineOptions {
@@ -102,7 +102,7 @@ export class Pipeline {
}
public set pipelineOutput(outputType: PipelineOutputType) {
- let pipeline = []
+ let pipeline: Array = []
this.clearPipeline()
switch (outputType) {
case PipelineOutputType.FINAL:
@@ -237,8 +237,11 @@ export class Pipeline {
this._renderer = renderer
this._batcher = batcher
this._composer = new EffectComposer(renderer)
- this._composer.readBuffer = null
- this._composer.writeBuffer = null
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ ;(this._composer as any).readBuffer = null
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ ;(this._composer as any).writeBuffer = null
}
public configure() {
@@ -270,7 +273,10 @@ export class Pipeline {
])
this.stencilMaskPass.setLayers([ObjectLayers.STREAM_CONTENT_MESH])
this.overlayPass.setLayers([ObjectLayers.OVERLAY, ObjectLayers.MEASUREMENTS])
- let restoreVisibility, opaque, stencil, depth
+ let restoreVisibility: Record,
+ opaque: Record,
+ stencil: Record,
+ depth: Record
this.onBeforePipelineRender = () => {
restoreVisibility = this._batcher.saveVisiblity()
@@ -381,7 +387,7 @@ export class Pipeline {
private setPipeline(pipeline: Array) {
for (let k = 0; k < pipeline.length; k++) {
- this._composer.addPass(pipeline[k] as unknown as Pass)
+ this._composer.addPass(pipeline[k] as BaseSpecklePass)
}
}
@@ -397,6 +403,8 @@ export class Pipeline {
}
public update(renderer: SpeckleRenderer) {
+ if (!renderer.scene || !renderer.renderingCamera) return
+
this.stencilPass.update(renderer.scene, renderer.renderingCamera)
this.renderPass.update(renderer.scene, renderer.renderingCamera)
this.stencilMaskPass.update(renderer.scene, renderer.renderingCamera)
@@ -413,7 +421,7 @@ export class Pipeline {
public render(): boolean {
this._renderer.getDrawingBufferSize(this.drawingSize)
- if (this.drawingSize.length() === 0) return
+ if (this.drawingSize.length() === 0) return false
if (this.onBeforePipelineRender) this.onBeforePipelineRender()
diff --git a/packages/viewer/src/modules/pipeline/ShadowcatcherPass.ts b/packages/viewer/src/modules/pipeline/ShadowcatcherPass.ts
index 5e9738b065..be1fd89ebb 100644
--- a/packages/viewer/src/modules/pipeline/ShadowcatcherPass.ts
+++ b/packages/viewer/src/modules/pipeline/ShadowcatcherPass.ts
@@ -26,9 +26,13 @@ import {
} from 'three/examples/jsm/shaders/DepthLimitedBlurShader.js'
import SpeckleDepthMaterial from '../materials/SpeckleDepthMaterial'
import SpeckleShadowcatcherMaterial from '../materials/SpeckleShadowcatcherMaterial'
-import { BaseSpecklePass, SpecklePass } from './SpecklePass'
+import { BaseSpecklePass, type SpecklePass } from './SpecklePass'
import { ObjectLayers } from '../../IViewer'
-import { DefaultShadowcatcherConfig, ShadowcatcherConfig } from '../ShadowcatcherConfig'
+import {
+ DefaultShadowcatcherConfig,
+ type ShadowcatcherConfig
+} from '../ShadowcatcherConfig'
+import type { SpeckleWebGLRenderer } from '../objects/SpeckleWebGLRenderer'
export class ShadowcatcherPass extends BaseSpecklePass implements SpecklePass {
private readonly levels: number = 4
@@ -36,23 +40,23 @@ export class ShadowcatcherPass extends BaseSpecklePass implements SpecklePass {
private renderTargets: WebGLRenderTarget[] = []
private tempTargets: WebGLRenderTarget[] = []
private outputTarget: WebGLRenderTarget
- private camera: OrthographicCamera = null
- private scene: Scene = null
+ private camera: OrthographicCamera
+ private scene!: Scene
private _needsUpdate = false
- private fsQuad: FullScreenQuad = null
- private blendMaterial: SpeckleShadowcatcherMaterial = null
- private depthMaterial: SpeckleDepthMaterial = null
- private vBlurMaterial: ShaderMaterial = null
- private hBlurMaterial: ShaderMaterial = null
+ private fsQuad: FullScreenQuad
+ private blendMaterial: SpeckleShadowcatcherMaterial
+ private depthMaterial: SpeckleDepthMaterial
+ private vBlurMaterial: ShaderMaterial
+ private hBlurMaterial: ShaderMaterial
private blurStdDev = DefaultShadowcatcherConfig.stdDeviation
private blurRadius = DefaultShadowcatcherConfig.blurRadius
private prevBlurStdDev = 0
private prevBlurRadius = 0
- private cameraHelper = null
+ private cameraHelper!: CameraHelper
- public onBeforeRender: () => void = null
- public onAfterRender: () => void = null
+ public onBeforeRender: (() => void) | undefined = undefined
+ public onAfterRender: (() => void) | undefined = undefined
get displayName(): string {
return 'Shadowcatcher'
@@ -124,7 +128,7 @@ export class ShadowcatcherPass extends BaseSpecklePass implements SpecklePass {
public update(scene: Scene) {
this.scene = scene
if (this._needsUpdate) {
- if (this.cameraHelper === null && this.debugCamera) {
+ if (!this.cameraHelper && this.debugCamera) {
this.cameraHelper = new CameraHelper(this.camera)
this.cameraHelper.layers.set(ObjectLayers.PROPS)
this.scene.add(this.cameraHelper)
@@ -180,9 +184,7 @@ export class ShadowcatcherPass extends BaseSpecklePass implements SpecklePass {
}
}
- public render(renderer, writeBuffer, readBuffer) {
- writeBuffer
- readBuffer
+ public render(renderer: SpeckleWebGLRenderer) {
if (this._needsUpdate) {
renderer.RTEBuffers.push()
renderer.updateRTEViewModel(this.camera)
diff --git a/packages/viewer/src/modules/pipeline/SpecklePass.ts b/packages/viewer/src/modules/pipeline/SpecklePass.ts
index feb2ce99ec..cc1430eca7 100644
--- a/packages/viewer/src/modules/pipeline/SpecklePass.ts
+++ b/packages/viewer/src/modules/pipeline/SpecklePass.ts
@@ -1,4 +1,11 @@
-import { Camera, Plane, Scene, Texture } from 'three'
+import {
+ Camera,
+ OrthographicCamera,
+ PerspectiveCamera,
+ Plane,
+ Scene,
+ Texture
+} from 'three'
import { Pass } from 'three/examples/jsm/postprocessing/Pass.js'
import { ObjectLayers } from '../../IViewer'
@@ -17,23 +24,26 @@ export interface SpecklePass {
onAferRender?: () => void
get displayName(): string
- get outputTexture(): Texture
+ get outputTexture(): Texture | null
- update?(scene: Scene, camera: Camera)
- setTexture?(uName: string, texture: Texture)
- setParams?(params: unknown)
- setClippingPlanes?(planes: Plane[])
- setLayers?(layers: ObjectLayers[])
+ update?(
+ scene: Scene | null,
+ camera: PerspectiveCamera | OrthographicCamera | null
+ ): void
+ setTexture?(uName: string, texture: Texture): void
+ setParams?(params: unknown): void
+ setClippingPlanes?(planes: Plane[]): void
+ setLayers?(layers: ObjectLayers[]): void
}
export interface SpeckleProgressivePass extends SpecklePass {
- setFrameIndex(index: number)
- setAccumulationFrames(frames: number)
- setRenderType?(type: RenderType)
+ setFrameIndex(index: number): void
+ setAccumulationFrames(frames: number): void
+ setRenderType?(type: RenderType): void
}
export abstract class BaseSpecklePass extends Pass implements SpecklePass {
- protected layers: ObjectLayers[] = null
+ protected layers: ObjectLayers[] | null = null
protected _enabledLayers: ObjectLayers[] = []
public get enabledLayers(): ObjectLayers[] {
@@ -47,7 +57,7 @@ export abstract class BaseSpecklePass extends Pass implements SpecklePass {
get displayName(): string {
return 'BASE'
}
- get outputTexture(): Texture {
+ get outputTexture(): Texture | null {
return null
}
diff --git a/packages/viewer/src/modules/pipeline/StaticAOPass.ts b/packages/viewer/src/modules/pipeline/StaticAOPass.ts
index d3471b729d..68ac1a87e2 100644
--- a/packages/viewer/src/modules/pipeline/StaticAOPass.ts
+++ b/packages/viewer/src/modules/pipeline/StaticAOPass.ts
@@ -1,6 +1,5 @@
import {
AddEquation,
- Camera,
Color,
CustomBlending,
DataTexture,
@@ -29,7 +28,7 @@ import { speckleStaticAoGenerateFrag } from '../materials/shaders/speckle-static
import { speckleStaticAoAccumulateVert } from '../materials/shaders/speckle-static-ao-accumulate-vert'
import { speckleStaticAoAccumulateFrag } from '../materials/shaders/speckle-static-ao-accumulate-frag'
import { SimplexNoise } from 'three/examples/jsm//math/SimplexNoise.js'
-import {
+import type {
InputDepthTextureUniform,
InputNormalsTextureUniform,
SpeckleProgressivePass
@@ -57,8 +56,8 @@ export const DefaultStaticAoPassParams = {
}
export class StaticAOPass extends Pass implements SpeckleProgressivePass {
- public aoMaterial: ShaderMaterial = null
- private accumulateMaterial: ShaderMaterial = null
+ public aoMaterial: ShaderMaterial
+ private accumulateMaterial: ShaderMaterial
private _generationBuffer: WebGLRenderTarget
private _accumulationBuffer: WebGLRenderTarget
private params: StaticAoPassParams = DefaultStaticAoPassParams
@@ -81,7 +80,7 @@ export class StaticAOPass extends Pass implements SpeckleProgressivePass {
this.aoMaterial.needsUpdate = true
}
- public get outputTexture() {
+ public get outputTexture(): Texture {
return this._accumulationBuffer.texture
}
@@ -164,7 +163,7 @@ export class StaticAOPass extends Pass implements SpeckleProgressivePass {
this.accumulationFrames = frames
}
- public update(scene: Scene, camera: Camera) {
+ public update(_scene: Scene, camera: PerspectiveCamera | OrthographicCamera) {
/** DEFINES */
this.aoMaterial.defines['PERSPECTIVE_CAMERA'] = (camera as PerspectiveCamera)
.isPerspectiveCamera
@@ -209,9 +208,7 @@ export class StaticAOPass extends Pass implements SpeckleProgressivePass {
this.accumulateMaterial.needsUpdate = true
}
- public render(renderer, writeBuffer, readBuffer) {
- writeBuffer
- readBuffer
+ public render(renderer: WebGLRenderer) {
// save original state
const originalClearColor = new Color()
renderer.getClearColor(originalClearColor)
@@ -254,7 +251,7 @@ export class StaticAOPass extends Pass implements SpeckleProgressivePass {
}
private generateSampleKernel(frameIndex: number) {
- const kernelSize = this.params.kernelSize
+ const kernelSize = this.params.kernelSize || 0
this.kernels[frameIndex] = []
for (let i = 0; i < kernelSize; i++) {
diff --git a/packages/viewer/src/modules/pipeline/StencilMaskPass.ts b/packages/viewer/src/modules/pipeline/StencilMaskPass.ts
index 1d13642e0e..3195395717 100644
--- a/packages/viewer/src/modules/pipeline/StencilMaskPass.ts
+++ b/packages/viewer/src/modules/pipeline/StencilMaskPass.ts
@@ -4,27 +4,30 @@ import {
DoubleSide,
EqualStencilFunc,
Material,
+ OrthographicCamera,
+ PerspectiveCamera,
Plane,
Scene,
Texture,
Vector2,
+ WebGLRenderTarget,
WebGLRenderer
} from 'three'
import SpeckleDisplaceMaterial from '../materials/SpeckleDisplaceMaterial'
-import { BaseSpecklePass, SpecklePass } from './SpecklePass'
+import { BaseSpecklePass, type SpecklePass } from './SpecklePass'
export class StencilMaskPass extends BaseSpecklePass implements SpecklePass {
- private camera: Camera
- private scene: Scene
- private overrideMaterial: Material = null
+ private camera!: Camera
+ private scene!: Scene
+ private overrideMaterial: Material
private _oldClearColor: Color = new Color()
- private clearColor: Color = null
+ private clearColor!: Color
private clearAlpha = 0
private clearDepth = true
private drawBufferSize: Vector2 = new Vector2()
- public onBeforeRender: () => void = null
- public onAfterRender: () => void = null
+ public onBeforeRender: (() => void) | undefined = undefined
+ public onAfterRender: (() => void) | undefined = undefined
public constructor() {
super()
@@ -42,7 +45,7 @@ export class StencilMaskPass extends BaseSpecklePass implements SpecklePass {
public get displayName(): string {
return 'STENCIL'
}
- public get outputTexture(): Texture {
+ public get outputTexture(): Texture | null {
return null
}
@@ -50,7 +53,7 @@ export class StencilMaskPass extends BaseSpecklePass implements SpecklePass {
return this.overrideMaterial
}
- public update(scene: Scene, camera: Camera) {
+ public update(scene: Scene, camera: PerspectiveCamera | OrthographicCamera) {
this.camera = camera
this.scene = scene
}
@@ -61,14 +64,15 @@ export class StencilMaskPass extends BaseSpecklePass implements SpecklePass {
render(
renderer: WebGLRenderer,
- writeBuffer,
- readBuffer /*, deltaTime, maskActive */
+ _writeBuffer: WebGLRenderTarget,
+ readBuffer: WebGLRenderTarget
) {
if (this.onBeforeRender) this.onBeforeRender()
const oldAutoClear = renderer.autoClear
renderer.autoClear = false
- let oldClearAlpha, oldOverrideMaterial
+ let oldClearAlpha,
+ oldOverrideMaterial = null
if (this.overrideMaterial !== undefined) {
oldOverrideMaterial = this.scene.overrideMaterial
diff --git a/packages/viewer/src/modules/pipeline/StencilPass.ts b/packages/viewer/src/modules/pipeline/StencilPass.ts
index 4232b5c73f..7bb9bb1c7f 100644
--- a/packages/viewer/src/modules/pipeline/StencilPass.ts
+++ b/packages/viewer/src/modules/pipeline/StencilPass.ts
@@ -4,27 +4,31 @@ import {
Color,
DoubleSide,
Material,
+ OrthographicCamera,
+ PerspectiveCamera,
Plane,
ReplaceStencilOp,
Scene,
Texture,
- Vector2
+ Vector2,
+ WebGLRenderTarget,
+ WebGLRenderer
} from 'three'
import SpeckleDisplaceMaterial from '../materials/SpeckleDisplaceMaterial'
-import { BaseSpecklePass, SpecklePass } from './SpecklePass'
+import { BaseSpecklePass, type SpecklePass } from './SpecklePass'
export class StencilPass extends BaseSpecklePass implements SpecklePass {
private camera: Camera
private scene: Scene
- private overrideMaterial: Material = null
+ private overrideMaterial: Material
private _oldClearColor: Color = new Color()
- private clearColor: Color = null
+ private clearColor: Color
private clearAlpha = 0
private clearDepth = true
private drawBufferSize: Vector2 = new Vector2()
- public onBeforeRender: () => void = null
- public onAfterRender: () => void = null
+ public onBeforeRender: (() => void) | undefined = undefined
+ public onAfterRender: (() => void) | undefined = undefined
public constructor() {
super()
@@ -46,7 +50,7 @@ export class StencilPass extends BaseSpecklePass implements SpecklePass {
public get displayName(): string {
return 'STENCIL'
}
- public get outputTexture(): Texture {
+ public get outputTexture(): Texture | null {
return null
}
@@ -54,7 +58,7 @@ export class StencilPass extends BaseSpecklePass implements SpecklePass {
return this.overrideMaterial
}
- public update(scene: Scene, camera: Camera) {
+ public update(scene: Scene, camera: PerspectiveCamera | OrthographicCamera) {
this.camera = camera
this.scene = scene
}
@@ -63,12 +67,17 @@ export class StencilPass extends BaseSpecklePass implements SpecklePass {
this.overrideMaterial.clippingPlanes = planes
}
- render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) {
+ render(
+ renderer: WebGLRenderer,
+ _writeBuffer: WebGLRenderTarget,
+ readBuffer: WebGLRenderTarget
+ ) {
if (this.onBeforeRender) this.onBeforeRender()
const oldAutoClear = renderer.autoClear
renderer.autoClear = false
- let oldClearAlpha, oldOverrideMaterial
+ let oldClearAlpha,
+ oldOverrideMaterial = null
if (this.overrideMaterial !== undefined) {
oldOverrideMaterial = this.scene.overrideMaterial
diff --git a/packages/viewer/src/modules/queries/IntersectionQuerySolver.ts b/packages/viewer/src/modules/queries/IntersectionQuerySolver.ts
index 83e1a9149b..48d9c19bdc 100644
--- a/packages/viewer/src/modules/queries/IntersectionQuerySolver.ts
+++ b/packages/viewer/src/modules/queries/IntersectionQuerySolver.ts
@@ -1,20 +1,20 @@
import Logger from 'js-logger'
-import { Intersection, Ray, Vector2, Vector3 } from 'three'
+import { type Intersection, Ray, Vector2, Vector3 } from 'three'
import SpeckleRenderer from '../SpeckleRenderer'
-import { IntersectionQuery, IntersectionQueryResult } from './Query'
+import type { IntersectionQuery, IntersectionQueryResult } from './Query'
import { ObjectLayers } from '../../IViewer'
export class IntersectionQuerySolver {
private vecBuff0: Vector3 = new Vector3()
private vecBuff1: Vector3 = new Vector3()
- private renderer: SpeckleRenderer
+ private renderer!: SpeckleRenderer
public setContext(renderer: SpeckleRenderer) {
this.renderer = renderer
}
- public solve(query: IntersectionQuery): IntersectionQueryResult {
+ public solve(query: IntersectionQuery): IntersectionQueryResult | null {
switch (query.operation) {
case 'Occlusion':
return this.solveOcclusion(query)
@@ -22,28 +22,31 @@ export class IntersectionQuerySolver {
return this.solvePick(query)
default:
Logger.error('Malformed query')
- break
+ return null
}
}
private solveOcclusion(query: IntersectionQuery): IntersectionQueryResult {
- const target = this.vecBuff0.set(query.point.x, query.point.y, query.point.z)
+ if (!this.renderer.renderingCamera) return { objects: null }
+
+ const target = this.vecBuff0.set(query.point.x, query.point.y, query.point.z || 0)
const dir = this.vecBuff1.copy(target).sub(this.renderer.renderingCamera.position)
dir.normalize()
const ray = new Ray(this.renderer.renderingCamera.position, dir)
- const results: Array = this.renderer.intersections.intersectRay(
- this.renderer.scene,
- this.renderer.renderingCamera,
- ray,
- true,
- this.renderer.clippingVolume,
- [ObjectLayers.STREAM_CONTENT_MESH]
- )
+ const results: Array | null =
+ this.renderer.intersections.intersectRay(
+ this.renderer.scene,
+ this.renderer.renderingCamera,
+ ray,
+ ObjectLayers.STREAM_CONTENT_MESH,
+ true,
+ this.renderer.clippingVolume
+ )
if (!results || results.length === 0) return { objects: null }
const hits = this.renderer.queryHitIds(results)
if (!hits) return { objects: null }
let targetDistance = this.renderer.renderingCamera.position.distanceTo(target)
- targetDistance -= query.tolerance
+ targetDistance -= query.tolerance !== undefined ? query.tolerance : 0
if (targetDistance < results[0].distance) {
return { objects: null }
@@ -59,16 +62,20 @@ export class IntersectionQuerySolver {
}
}
- private solvePick(query: IntersectionQuery): IntersectionQueryResult {
- const results: Array = this.renderer.intersections.intersect(
+ private solvePick(query: IntersectionQuery): IntersectionQueryResult | null {
+ if (!this.renderer.renderingCamera) return null
+
+ const results: Array | null = this.renderer.intersections.intersect(
this.renderer.scene,
this.renderer.renderingCamera,
new Vector2(query.point.x, query.point.y),
+ undefined,
true,
this.renderer.clippingVolume
)
if (!results) return null
const hits = this.renderer.queryHits(results)
+ if (!hits) return null
return {
objects: hits.map((value) => {
return {
diff --git a/packages/viewer/src/modules/queries/PointQuerySolver.ts b/packages/viewer/src/modules/queries/PointQuerySolver.ts
index 3593377115..2a246926e4 100644
--- a/packages/viewer/src/modules/queries/PointQuerySolver.ts
+++ b/packages/viewer/src/modules/queries/PointQuerySolver.ts
@@ -1,16 +1,16 @@
import Logger from 'js-logger'
import { Vector3 } from 'three'
import SpeckleRenderer from '../SpeckleRenderer'
-import { PointQuery, PointQueryResult } from './Query'
+import type { PointQuery, PointQueryResult } from './Query'
export class PointQuerySolver {
- private renderer: SpeckleRenderer
+ private renderer!: SpeckleRenderer
public setContext(renderer: SpeckleRenderer) {
this.renderer = renderer
}
- public solve(query: PointQuery): PointQueryResult {
+ public solve(query: PointQuery): PointQueryResult | null {
switch (query.operation) {
case 'Project':
return this.solveProjection(query)
@@ -18,14 +18,15 @@ export class PointQuerySolver {
return this.solveUnprojection(query)
default:
Logger.error('Malformed query')
- break
+ return null
}
}
private solveProjection(query: PointQuery): PointQueryResult {
// WORLD
const projected = new Vector3(query.point.x, query.point.y, query.point.z)
- projected.project(this.renderer.renderingCamera)
+ if (this.renderer.renderingCamera) projected.project(this.renderer.renderingCamera)
+ else Logger.error('Could not run query. Camera is null')
return {
// NDC
@@ -38,8 +39,9 @@ export class PointQuerySolver {
private solveUnprojection(query: PointQuery): PointQueryResult {
// NDC
const unprojected = new Vector3(query.point.x, query.point.y, query.point.z)
- unprojected.unproject(this.renderer.renderingCamera)
-
+ if (this.renderer.renderingCamera)
+ unprojected.unproject(this.renderer.renderingCamera)
+ else Logger.error('Could not run query. Camera is null')
return {
// WORLD
x: unprojected.x,
diff --git a/packages/viewer/src/modules/queries/Queries.ts b/packages/viewer/src/modules/queries/Queries.ts
index 4f8ad5d101..bab8f0f966 100644
--- a/packages/viewer/src/modules/queries/Queries.ts
+++ b/packages/viewer/src/modules/queries/Queries.ts
@@ -1,6 +1,6 @@
import { IntersectionQuerySolver } from './IntersectionQuerySolver'
import { PointQuerySolver } from './PointQuerySolver'
-import { IntersectionQuery, PointQuery, Query } from './Query'
+import type { IntersectionQuery, PointQuery, Query } from './Query'
export class Queries {
public static DefaultPointQuerySolver: PointQuerySolver = new PointQuerySolver()
diff --git a/packages/viewer/src/modules/tree/DataTree.ts b/packages/viewer/src/modules/tree/DataTree.ts
deleted file mode 100644
index e8d1c114eb..0000000000
--- a/packages/viewer/src/modules/tree/DataTree.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import TreeModel from 'tree-model'
-import { TreeNode, WorldTree } from './WorldTree'
-
-export type SpeckleObject = Record
-export type ObjectPredicate = (guid: string, obj: SpeckleObject) => boolean
-
-export interface DataTree {
- findFirst(predicate: ObjectPredicate): SpeckleObject
- findAll(predicate: ObjectPredicate): SpeckleObject[]
- walk(predicate: ObjectPredicate): void
-}
-
-class DataTreeInternal implements DataTree {
- tree: TreeModel
- root: TreeNode
-
- public constructor() {
- this.tree = new TreeModel()
- this.root = this.tree.parse({ guid: WorldTree.ROOT_ID })
- }
- public findAll(predicate: ObjectPredicate): SpeckleObject[] {
- return this.root
- .all((node: TreeNode) => {
- if (!node.model.data) return false
- return predicate(node.model.guid, node.model.data)
- })
- .map((value: TreeNode) => value.model.data)
- }
-
- public findFirst(predicate: ObjectPredicate): SpeckleObject {
- return this.root.first((node: TreeNode) => {
- if (!node.model.data) return false
- return predicate(node.model.guid, node.model.data)
- }).model.data
- }
-
- public walk(predicate: ObjectPredicate) {
- this.root.walk((node: TreeNode) => {
- if (!node.model.data) return true
- return predicate(node.model.guid, node.model.data)
- })
- }
-}
-
-export class DataTreeBuilder {
- public static build(tree: WorldTree): DataTree {
- const dataTree = new DataTreeInternal()
- let parent = null
- tree.root.walk((node: TreeNode) => {
- if (!node.parent) {
- parent = dataTree.root
- return true
- }
-
- parent = dataTree.root.first((localNode) => {
- return localNode.model.guid === node.parent.model.id
- })
-
- const _node: TreeNode = tree.parse({
- guid: node.model.id,
- data: node.model.raw,
- atomic: node.model.atomic,
- children: []
- })
- parent.addChild(_node)
-
- return true
- }, tree.root)
- return dataTree as DataTree
- }
-}
diff --git a/packages/viewer/src/modules/tree/NodeMap.ts b/packages/viewer/src/modules/tree/NodeMap.ts
index 0579a7e04a..683b19d085 100644
--- a/packages/viewer/src/modules/tree/NodeMap.ts
+++ b/packages/viewer/src/modules/tree/NodeMap.ts
@@ -1,10 +1,9 @@
import Logger from 'js-logger'
-import { TreeNode } from './WorldTree'
+import { type TreeNode } from './WorldTree'
export class NodeMap {
public static readonly COMPOUND_ID_CHAR = '~'
- private subtreeRoot: TreeNode
private all: { [id: string]: TreeNode } = {}
public instances: { [id: string]: { [id: string]: TreeNode } } = {}
@@ -13,7 +12,6 @@ export class NodeMap {
}
public constructor(subtreeRoot: TreeNode) {
- this.subtreeRoot = subtreeRoot
this.registerNode(subtreeRoot)
}
@@ -43,7 +41,7 @@ export class NodeMap {
return true
}
- public getNodeById(id: string): TreeNode[] {
+ public getNodeById(id: string): TreeNode[] | null {
if (id.includes(NodeMap.COMPOUND_ID_CHAR)) {
const baseId = id.substring(0, id.indexOf(NodeMap.COMPOUND_ID_CHAR))
if (this.instances[baseId]) {
@@ -68,7 +66,7 @@ export class NodeMap {
return this.all[id]
}
- public hasId(id: string) {
+ public hasId(id: string): boolean {
if (id.includes(NodeMap.COMPOUND_ID_CHAR)) {
const baseId = id.substring(0, id.indexOf(NodeMap.COMPOUND_ID_CHAR))
if (this.instances[baseId]) {
@@ -83,9 +81,10 @@ export class NodeMap {
if (this.instances[id]) {
return true
}
+ return false
}
- private registerInstance(node: TreeNode) {
+ private registerInstance(node: TreeNode): void {
const baseId = node.model.id.substring(
0,
node.model.id.indexOf(NodeMap.COMPOUND_ID_CHAR)
@@ -101,7 +100,7 @@ export class NodeMap {
}
public purge() {
- this.all = null
- this.instances = null
+ this.all = {}
+ this.instances = {}
}
}
diff --git a/packages/viewer/src/modules/tree/NodeRenderView.ts b/packages/viewer/src/modules/tree/NodeRenderView.ts
index f5a176e12a..7a4cdb6afc 100644
--- a/packages/viewer/src/modules/tree/NodeRenderView.ts
+++ b/packages/viewer/src/modules/tree/NodeRenderView.ts
@@ -1,7 +1,10 @@
import { Box3 } from 'three'
import { GeometryType } from '../batching/Batch'
-import { GeometryData } from '../converter/Geometry'
-import Materials, { DisplayStyle, RenderMaterial } from '../materials/Materials'
+import { GeometryAttributes, type GeometryData } from '../converter/Geometry'
+import Materials, {
+ type DisplayStyle,
+ type RenderMaterial
+} from '../materials/Materials'
import { SpeckleType } from '../loaders/GeometryConverter'
export interface NodeRenderData {
@@ -9,8 +12,8 @@ export interface NodeRenderData {
subtreeId: number
speckleType: SpeckleType
geometry: GeometryData
- renderMaterial: RenderMaterial
- displayStyle: DisplayStyle
+ renderMaterial: RenderMaterial | null
+ displayStyle: DisplayStyle | null
}
export class NodeRenderView {
@@ -23,22 +26,23 @@ export class NodeRenderView {
private readonly _renderData: NodeRenderData
private _materialHash: number
private _geometryType: GeometryType
- private _guid: string = null
+ private _guid: string | null = null
- private _aabb: Box3 = null
+ private _aabb: Box3
- public get guid() {
+ /** TO DO: Not sure if we should store it */
+ public get guid(): string {
if (!this._guid) {
this._guid = this._renderData.subtreeId + this._renderData.id
}
return this._guid
}
- public get renderData() {
+ public get renderData(): NodeRenderData {
return this._renderData
}
- public get renderMaterialHash() {
+ public get renderMaterialHash(): number {
return this._materialHash
}
@@ -50,15 +54,15 @@ export class NodeRenderView {
return this._renderData.geometry && this._renderData.geometry.metaData
}
- public get speckleType() {
+ public get speckleType(): SpeckleType {
return this._renderData.speckleType
}
- public get geometryType() {
+ public get geometryType(): GeometryType {
return this._geometryType
}
- public get batchStart() {
+ public get batchStart(): number {
return this._batchIndexStart
}
@@ -66,33 +70,35 @@ export class NodeRenderView {
return this._batchIndexStart + this._batchIndexCount
}
- public get batchCount() {
+ public get batchCount(): number {
return this._batchIndexCount
}
- public get batchId() {
+ public get batchId(): string {
return this._batchId
}
- public get aabb() {
+ public get aabb(): Box3 {
return this._aabb
}
- public get transparent() {
+ public get transparent(): boolean {
return (
- this._renderData.renderMaterial && this._renderData.renderMaterial.opacity < 1
+ (this._renderData.renderMaterial &&
+ this._renderData.renderMaterial.opacity < 1) ||
+ false
)
}
- public get vertStart() {
+ public get vertStart(): number {
return this._batchVertexStart
}
- public get vertEnd() {
+ public get vertEnd(): number {
return this._batchVertexEnd
}
- public get needsSegmentConversion() {
+ public get needsSegmentConversion(): boolean {
return (
this._renderData.speckleType === SpeckleType.Curve ||
this._renderData.speckleType === SpeckleType.Polyline ||
@@ -103,15 +109,16 @@ export class NodeRenderView {
)
}
- public get validGeometry() {
+ public get validGeometry(): boolean {
return (
- this._renderData.geometry.attributes &&
- this._renderData.geometry.attributes.POSITION &&
- this._renderData.geometry.attributes.POSITION.length > 0 &&
- (this._geometryType === GeometryType.MESH
- ? this._renderData.geometry.attributes.INDEX &&
- this._renderData.geometry.attributes.INDEX.length > 0
- : true)
+ (this._renderData.geometry.attributes &&
+ this._renderData.geometry.attributes.POSITION &&
+ this._renderData.geometry.attributes.POSITION.length > 0 &&
+ (this._geometryType === GeometryType.MESH
+ ? this._renderData.geometry.attributes.INDEX &&
+ this._renderData.geometry.attributes.INDEX.length > 0
+ : true)) ||
+ false
)
}
@@ -120,11 +127,11 @@ export class NodeRenderView {
this._geometryType = this.getGeometryType()
this._materialHash = Materials.getMaterialHash(this)
- this._batchId
- this._batchIndexCount
- this._batchIndexStart
- this._batchVertexStart
- this._batchVertexEnd
+ this._batchId = ''
+ this._batchIndexCount = 0
+ this._batchIndexStart = -1
+ this._batchVertexStart = -1
+ this._batchVertexEnd = -1
}
public setBatchData(
@@ -142,10 +149,12 @@ export class NodeRenderView {
}
public computeAABB() {
- this._aabb = new Box3().setFromArray(this._renderData.geometry.attributes.POSITION)
+ this._aabb = new Box3()
+ if (this._renderData.geometry.attributes)
+ this._aabb.setFromArray(this._renderData.geometry.attributes.POSITION)
}
- public getGeometryType(): GeometryType {
+ private getGeometryType(): GeometryType {
switch (this._renderData.speckleType) {
case SpeckleType.Mesh:
return GeometryType.MESH
@@ -165,7 +174,7 @@ export class NodeRenderView {
public disposeGeometry() {
for (const attr in this._renderData.geometry.attributes) {
- this._renderData.geometry.attributes[attr] = []
+ this._renderData.geometry.attributes[attr as GeometryAttributes] = []
}
}
}
diff --git a/packages/viewer/src/modules/tree/RenderTree.ts b/packages/viewer/src/modules/tree/RenderTree.ts
index 0d5f0b79b8..7e9ae365d2 100644
--- a/packages/viewer/src/modules/tree/RenderTree.ts
+++ b/packages/viewer/src/modules/tree/RenderTree.ts
@@ -1,7 +1,7 @@
-import { Box3, Matrix4 } from 'three'
-import { TreeNode, WorldTree } from './WorldTree'
+import { Matrix4 } from 'three'
+import { type TreeNode, WorldTree } from './WorldTree'
import Materials from '../materials/Materials'
-import { NodeRenderData, NodeRenderView } from './NodeRenderView'
+import { type NodeRenderData, NodeRenderView } from './NodeRenderView'
import Logger from 'js-logger'
import { GeometryConverter, SpeckleType } from '../loaders/GeometryConverter'
import { Geometry } from '../converter/Geometry'
@@ -9,13 +9,8 @@ import { Geometry } from '../converter/Geometry'
export class RenderTree {
private tree: WorldTree
private root: TreeNode
- private _treeBounds: Box3 = new Box3()
private cancel = false
- public get treeBounds(): Box3 {
- return this._treeBounds
- }
-
public get id(): string {
return this.root.model.id
}
@@ -55,7 +50,6 @@ export class RenderTree {
)
}
node.model.renderView.computeAABB()
- this._treeBounds.union(node.model.renderView.aabb)
} else if (node.model.renderView.hasMetadata) {
node.model.renderView.renderData.geometry.bakeTransform.premultiply(transform)
}
@@ -65,8 +59,8 @@ export class RenderTree {
private buildRenderNode(
node: TreeNode,
geometryConverter: GeometryConverter
- ): NodeRenderData {
- let ret: NodeRenderData = null
+ ): NodeRenderData | null {
+ let ret: NodeRenderData | null = null
const geometryData = geometryConverter.convertNodeToGeometryData(node.model)
if (geometryData) {
const renderMaterialNode = this.getRenderMaterialNode(node)
@@ -89,7 +83,7 @@ export class RenderTree {
return ret
}
- private getRenderMaterialNode(node: TreeNode): TreeNode {
+ private getRenderMaterialNode(node: TreeNode): TreeNode | null {
if (node.model.raw.renderMaterial) {
return node
}
@@ -99,9 +93,10 @@ export class RenderTree {
return ancestors[k]
}
}
+ return null
}
- private getDisplayStyleNode(node: TreeNode): TreeNode {
+ private getDisplayStyleNode(node: TreeNode): TreeNode | null {
if (node.model.raw.displayStyle) {
return node
}
@@ -111,6 +106,7 @@ export class RenderTree {
return ancestors[k]
}
}
+ return null
}
public computeTransform(node: TreeNode): Matrix4 {
@@ -123,7 +119,10 @@ export class RenderTree {
for (let k = 0; k < ancestors.length; k++) {
if (ancestors[k].model.renderView) {
const renderNode: NodeRenderData = ancestors[k].model.renderView.renderData
- if (renderNode.speckleType === SpeckleType.Transform) {
+ if (
+ renderNode.speckleType === SpeckleType.Transform &&
+ renderNode.geometry.transform
+ ) {
transform.premultiply(renderNode.geometry.transform)
}
}
@@ -151,41 +150,26 @@ export class RenderTree {
})
}
- /** This gets the render views for a particular node/id.
- * Currently it doesn't treat Blocks in a special way, but
- * we might want to.
- */
- public getRenderViewsForNode(node: TreeNode, parent?: TreeNode): NodeRenderView[] {
- if (
- node.model.atomic &&
- node.model.renderView &&
- node.model.renderView.renderData.speckleType !== SpeckleType.RevitInstance &&
- node.model.renderView.renderData.speckleType !== SpeckleType.BlockInstance
- ) {
- return [node.model.renderView]
- }
-
- return (parent ? parent : node.parent)
- .all((_node: TreeNode): boolean => {
- return (
- _node.model.renderView &&
- (_node.model.renderView.hasGeometry || _node.model.renderView.hasMetadata)
- )
- })
- .map((val: TreeNode) => val.model.renderView)
+ public getRenderViewsForNode(node: TreeNode): NodeRenderView[] {
+ return this.getRenderViewNodesForNode(node).map(
+ (val: TreeNode) => val.model.renderView
+ )
}
- public getRenderViewNodesForNode(node: TreeNode, parent?: TreeNode): TreeNode[] {
+ public getRenderViewNodesForNode(node: TreeNode): TreeNode[] {
if (
node.model.atomic &&
- node.model.renderView &&
+ node.model.renderView
+ /** This should not be needed anymore. */
+ /*&&
node.model.renderView.renderData.speckleType !== SpeckleType.RevitInstance &&
node.model.renderView.renderData.speckleType !== SpeckleType.BlockInstance
+ */
) {
return [node]
}
- return (parent ? parent : node.parent).all((_node: TreeNode): boolean => {
+ return node.all((_node: TreeNode): boolean => {
return (
_node.model.renderView &&
(_node.model.renderView.hasGeometry || _node.model.renderView.hasMetadata)
@@ -193,45 +177,33 @@ export class RenderTree {
})
}
- public getAtomicParent(node: TreeNode) {
- if (node.model.atomic) {
- return node
- }
- return this.tree.getAncestors(node).find((node) => node.model.atomic)
- }
-
- public getRenderViewsForNodeId(id: string): NodeRenderView[] {
+ public getRenderViewsForNodeId(id: string): NodeRenderView[] | null {
const nodes = this.tree.findId(id)
if (!nodes) {
Logger.warn(`Id ${id} does not exist`)
return null
}
- const ret = []
+ const ret: Array = []
nodes.forEach((node: TreeNode) => {
- ret.push(...this.getRenderViewsForNode(node, node))
+ ret.push(...this.getRenderViewsForNode(node))
})
return ret
}
- public getRenderViewForNodeId(id: string): NodeRenderView {
- const nodes = this.tree.findId(id)
- if (!nodes) {
- Logger.warn(`Id ${id} does not exist`)
- return null
- }
- if (nodes.length > 1) {
- Logger.warn(`Multiple nodes with ${id} found. Returning first only`)
+ public getAtomicParent(node: TreeNode): TreeNode {
+ if (node.model.atomic) {
+ return node
}
- return nodes[0].model.renderView
+ /** There will always the root of the tree as the atomic parent for all nodes */
+ return this.tree.getAncestors(node).find((node) => node.model.atomic) as TreeNode
}
- public purge() {
- this.tree = null
- }
+ public purge() {}
- public cancelBuild(subtreeId: string) {
+ /** TO DO: Need to purge only if currently building */
+ public cancelBuild(): void {
this.cancel = true
- this.tree.purge(subtreeId)
+ this.tree.purge(this.id)
this.purge()
}
}
diff --git a/packages/viewer/src/modules/tree/WorldTree.ts b/packages/viewer/src/modules/tree/WorldTree.ts
index 7c0bd2a990..54b3f0be0a 100644
--- a/packages/viewer/src/modules/tree/WorldTree.ts
+++ b/packages/viewer/src/modules/tree/WorldTree.ts
@@ -1,4 +1,4 @@
-import TreeModel from 'tree-model'
+import TreeModel, { type Model } from 'tree-model'
import { NodeRenderView } from './NodeRenderView'
import { RenderTree } from './RenderTree'
import Logger from 'js-logger'
@@ -7,22 +7,22 @@ import { NodeMap } from './NodeMap'
export type TreeNode = TreeModel.Node
export type SearchPredicate = (node: TreeNode) => boolean
-export type AsyncSearchPredicate = (node: TreeNode) => Promise
export interface NodeData {
+ id: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
raw: { [prop: string]: any }
children: TreeNode[]
- nestedNodes: TreeNode[]
atomic: boolean
+ nestedNodes?: TreeNode[]
subtreeId?: number
- renderView?: NodeRenderView
+ renderView?: NodeRenderView | null
instanced?: boolean
}
export class WorldTree {
private renderTreeInstances: { [id: string]: RenderTree } = {}
- public nodeMaps: { [id: string]: NodeMap } = {}
+ private nodeMaps: { [id: string]: NodeMap } = {}
private readonly supressWarnings = true
public static readonly ROOT_ID = 'ROOT'
private subtreeId: number = 0
@@ -38,7 +38,10 @@ export class WorldTree {
})
}
- public getRenderTree(subtreeId?: string): RenderTree {
+ /** The root render tree will always be non-null because it will always contain the root */
+ public getRenderTree(): RenderTree
+ public getRenderTree(subtreeId: string): RenderTree | null
+ public getRenderTree(subtreeId?: string): RenderTree | null {
if (!this._root) {
console.error(`WorldTree not initialised`)
return null
@@ -63,17 +66,17 @@ export class WorldTree {
return this._root
}
- public get nextSubtreeId(): number {
+ private get nextSubtreeId(): number {
return ++this.subtreeId
}
- public get nodeCount() {
+ public get nodeCount(): number {
let nodeCount = 0
for (const k in this.nodeMaps) nodeCount += this.nodeMaps[k].nodeCount
return nodeCount
}
- public isRoot(node: TreeNode) {
+ public isRoot(node: TreeNode): boolean {
return node === this._root
}
@@ -81,7 +84,7 @@ export class WorldTree {
return node.parent === this._root
}
- public parse(model) {
+ public parse(model: Model): TreeNode {
return this.tree.parse(model)
}
@@ -96,7 +99,7 @@ export class WorldTree {
this._root.addChild(node)
}
- public addNode(node: TreeNode, parent: TreeNode) {
+ public addNode(node: TreeNode, parent: TreeNode | null) {
if (parent === null || parent.model.subtreeId === undefined) {
Logger.error(`Invalid parent node!`)
return
@@ -105,7 +108,7 @@ export class WorldTree {
if (this.nodeMaps[parent.model.subtreeId]?.addNode(node)) parent.addChild(node)
}
- public removeNode(node: TreeNode) {
+ public removeNode(node: TreeNode): void {
node.drop()
}
@@ -116,7 +119,7 @@ export class WorldTree {
return (node ? node : this.root).all(predicate)
}
- public findId(id: string, subtreeId?: number) {
+ public findId(id: string, subtreeId?: number): TreeNode[] | null {
let idNode = null
if (subtreeId) {
idNode = this.nodeMaps[subtreeId].getNodeById(id)
@@ -129,6 +132,7 @@ export class WorldTree {
return idNode
}
+ /** TODO: Would rather not have this */
public findSubtree(id: string) {
let idNode = null
for (const k in this.nodeMaps) {
@@ -141,10 +145,11 @@ export class WorldTree {
return node.getPath().reverse().slice(1) // We skip the node itself
}
- public getInstances(subtree: string): { [id: string]: Record } {
- return this.nodeMaps[subtree].instances
+ public getInstances(subtreeId: string): { [id: string]: Record } {
+ return this.nodeMaps[subtreeId].instances
}
+ /** TO DO: We might want to add boolean as return type here too */
public walk(predicate: SearchPredicate, node?: TreeNode): void {
if (!node && !this.supressWarnings) {
Logger.warn(`Root will be used for searching. You might not want that`)
@@ -162,7 +167,10 @@ export class WorldTree {
const pause = new AsyncPause()
let success = true
- async function depthFirstPreOrderAsync(callback, context) {
+ async function depthFirstPreOrderAsync(
+ callback: SearchPredicate,
+ context: TreeNode
+ ) {
let i, childCount
pause.tick(100)
if (pause.needsWait) {
@@ -183,10 +191,12 @@ export class WorldTree {
public purge(subtreeId?: string) {
if (subtreeId) {
delete this.renderTreeInstances[subtreeId]
- const subtreeNode = this.findId(subtreeId)[0]
- this.nodeMaps[subtreeNode.model.subtreeId].purge()
- delete this.nodeMaps[subtreeNode.model.subtreeId]
- this.removeNode(subtreeNode)
+ const subtreeNode = this.findId(subtreeId)
+ if (subtreeNode) {
+ this.nodeMaps[subtreeNode[0].model.subtreeId].purge()
+ delete this.nodeMaps[subtreeNode[0].model.subtreeId]
+ this.removeNode(subtreeNode[0])
+ }
return
}
diff --git a/packages/viewer/tsconfig.json b/packages/viewer/tsconfig.json
index a1cd799893..74f2229df7 100644
--- a/packages/viewer/tsconfig.json
+++ b/packages/viewer/tsconfig.json
@@ -4,13 +4,16 @@
"lib": ["DOM"],
"module": "es2020",
"moduleResolution": "Bundler",
- "strict": false,
+ "strict": true,
"sourceMap": true,
"isolatedModules": true,
"esModuleInterop": true,
- "noUnusedLocals": false,
- "noUnusedParameters": false,
- "noImplicitReturns": false,
+ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
+ "verbatimModuleSyntax": false,
+ "strictPropertyInitialization": false /* We are not building a ToDO list app. This option is ridiculous */,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitReturns": true,
"skipLibCheck": true,
"outDir": "./dist",
"allowJs": true,
diff --git a/utils/docker-compose-ingress/Dockerfile b/utils/docker-compose-ingress/Dockerfile
index 4db671d571..65941a9087 100644
--- a/utils/docker-compose-ingress/Dockerfile
+++ b/utils/docker-compose-ingress/Dockerfile
@@ -5,4 +5,4 @@ RUN mkdir -p /var/nginx
COPY utils/docker-compose-ingress/nginx/templates /etc/nginx/templates
COPY utils/docker-compose-ingress/nginx/conf/mime.types /etc/nginx/mime.types
-EXPOSE 8080
+EXPOSE 8080
\ No newline at end of file
diff --git a/utils/docker-compose-ingress/nginx/default.conf b/utils/docker-compose-ingress/nginx/default.conf
new file mode 100644
index 0000000000..17b498bf43
--- /dev/null
+++ b/utils/docker-compose-ingress/nginx/default.conf
@@ -0,0 +1,27 @@
+server {
+ listen 8080;
+ location / {
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_pass http://127.0.0.1:8081;
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+ location ~* ^/(graphql|explorer|(auth/.*)|(objects/.*)|(preview/.*)|(api/.*)|(static/.*)) {
+ resolver 127.0.0.11 valid=30s;
+ set $upstream_speckle_server speckle-server;
+ client_max_body_size 300m;
+ proxy_pass http://127.0.0.1:3000;
+
+ proxy_buffering off;
+ proxy_request_buffering off;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+}
diff --git a/utils/helm/speckle-server/templates/frontend_2/deployment.yml b/utils/helm/speckle-server/templates/frontend_2/deployment.yml
index 0ad3b98fcb..e961dc7bdc 100644
--- a/utils/helm/speckle-server/templates/frontend_2/deployment.yml
+++ b/utils/helm/speckle-server/templates/frontend_2/deployment.yml
@@ -125,6 +125,8 @@ spec:
- name: NUXT_PUBLIC_DATADOG_ENV
value: {{ .Values.analytics.datadog_env | quote }}
{{- end }}
+ - name: FF_AUTOMATE_MODULE_ENABLED
+ value: {{ .Values.featureFlags.automateModuleEnabled | quote }}
{{- if .Values.analytics.survicate_workspace_key }}
- name: NUXT_PUBLIC_SURVICATE_WORKSPACE_KEY
value: {{ .Values.analytics.survicate_workspace_key | quote }}
diff --git a/utils/helm/speckle-server/templates/server/deployment.yml b/utils/helm/speckle-server/templates/server/deployment.yml
index 3d1e02a33d..80d10b5752 100644
--- a/utils/helm/speckle-server/templates/server/deployment.yml
+++ b/utils/helm/speckle-server/templates/server/deployment.yml
@@ -111,6 +111,9 @@ spec:
- name: ENABLE_FE2_MESSAGING
value: {{ .Values.server.enableFe2Messaging | quote }}
+ - name: FF_AUTOMATE_MODULE_ENABLED
+ value: {{ .Values.featureFlags.automateModuleEnabled | quote }}
+
- name: ONBOARDING_STREAM_URL
value: {{ .Values.server.onboarding.stream_url }}
- name: ONBOARDING_STREAM_CACHE_BUST_NUMBER
diff --git a/utils/helm/speckle-server/values.schema.json b/utils/helm/speckle-server/values.schema.json
index bd1672c952..db1dfef4e5 100644
--- a/utils/helm/speckle-server/values.schema.json
+++ b/utils/helm/speckle-server/values.schema.json
@@ -32,6 +32,16 @@
"description": "The name of the ClusterIssuer kubernetes resource that provides the SSL Certificate",
"default": "letsencrypt-staging"
},
+ "featureFlags": {
+ "type": "object",
+ "properties": {
+ "automateModuleEnabled": {
+ "type": "boolean",
+ "description": "High level flag fully toggles the integrated automate module",
+ "default": false
+ }
+ }
+ },
"analytics": {
"type": "object",
"properties": {
diff --git a/utils/helm/speckle-server/values.yaml b/utils/helm/speckle-server/values.yaml
index cefb616211..648ad40c94 100644
--- a/utils/helm/speckle-server/values.yaml
+++ b/utils/helm/speckle-server/values.yaml
@@ -30,6 +30,14 @@ tlsRejectUnauthorized: '1'
##
cert_manager_issuer: letsencrypt-staging
+## @section Feature flags
+## @descriptionStart
+## This object is a central location to define feature flags for the whole chart.
+## @descriptionEnd
+featureFlags:
+ ## @param featureFlags.automateModuleEnabled High level flag fully toggles the integrated automate module
+ automateModuleEnabled: false
+
analytics:
## @param analytics.enabled Enable or disable analytics
enabled: true
diff --git a/workspace.code-workspace b/workspace.code-workspace
index f59af2f5ab..ae9f223619 100644
--- a/workspace.code-workspace
+++ b/workspace.code-workspace
@@ -113,7 +113,7 @@
},
"vue.complete.casing.props": "kebab",
"vue.inlayHints.missingProps": true,
- "circleci.persistedProjectSelection": []
+ "circleci.persistedProjectSelection": ["gh/specklesystems/speckle-server"]
},
"extensions": {
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
@@ -128,7 +128,9 @@
"Vue.volar",
"bradlc.vscode-tailwindcss",
"stylelint.vscode-stylelint",
- "cpylua.language-postcss"
+ "cpylua.language-postcss",
+ "graphql.vscode-graphql",
+ "graphql.vscode-graphql-syntax"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": ["octref.vetur", "vscode.typescript-language-features"]
diff --git a/yarn.lock b/yarn.lock
index 0db3c0a553..d2c474f22a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -10474,7 +10474,14 @@ __metadata:
languageName: node
linkType: hard
-"@ioredis/commands@npm:^1.1.1":
+"@ioredis/as-callback@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "@ioredis/as-callback@npm:3.0.0"
+ checksum: 2835e39631497fe4f8b07d95576abea165c9f7efef81e9e55c733588051ff4edcb31eeb59f36127923dae0cb1a8e21b4e27ee3ab79a065a0baeecf861c3bc0b1
+ languageName: node
+ linkType: hard
+
+"@ioredis/commands@npm:^1.1.1, @ioredis/commands@npm:^1.2.0":
version: 1.2.0
resolution: "@ioredis/commands@npm:1.2.0"
checksum: 9b20225ba36ef3e5caf69b3c0720597c3016cc9b1e157f519ea388f621dd9037177f84cfe7e25c4c32dad7dd90c70ff9123cd411f747e053cf292193c9c461e2
@@ -12349,305 +12356,6 @@ __metadata:
languageName: node
linkType: hard
-"@octokit/app@npm:^14.0.2":
- version: 14.1.0
- resolution: "@octokit/app@npm:14.1.0"
- dependencies:
- "@octokit/auth-app": ^6.0.0
- "@octokit/auth-unauthenticated": ^5.0.0
- "@octokit/core": ^5.0.0
- "@octokit/oauth-app": ^6.0.0
- "@octokit/plugin-paginate-rest": ^9.0.0
- "@octokit/types": ^12.0.0
- "@octokit/webhooks": ^12.0.4
- checksum: 2a27ea831d0367b07f3c4109bbc840c7ae7d5a52d3129593cd867364794eb51b16b0fc308b116a89af9a2f19553c72346e03dd07b952e82c222ed1e7880dfcac
- languageName: node
- linkType: hard
-
-"@octokit/auth-app@npm:^6.0.0":
- version: 6.1.1
- resolution: "@octokit/auth-app@npm:6.1.1"
- dependencies:
- "@octokit/auth-oauth-app": ^7.1.0
- "@octokit/auth-oauth-user": ^4.1.0
- "@octokit/request": ^8.3.1
- "@octokit/request-error": ^5.1.0
- "@octokit/types": ^13.1.0
- deprecation: ^2.3.1
- lru-cache: ^10.0.0
- universal-github-app-jwt: ^1.1.2
- universal-user-agent: ^6.0.0
- checksum: 6b3b299865f4a612cf308b6c01ba38101930d1e3eb3444c4eaa5365bec9d62538d45b471e1ee3677244e26b899316bd4ad30ade821564f7f48ff9f51bb74c423
- languageName: node
- linkType: hard
-
-"@octokit/auth-oauth-app@npm:^7.0.0, @octokit/auth-oauth-app@npm:^7.1.0":
- version: 7.1.0
- resolution: "@octokit/auth-oauth-app@npm:7.1.0"
- dependencies:
- "@octokit/auth-oauth-device": ^6.1.0
- "@octokit/auth-oauth-user": ^4.1.0
- "@octokit/request": ^8.3.1
- "@octokit/types": ^13.0.0
- "@types/btoa-lite": ^1.0.0
- btoa-lite: ^1.0.0
- universal-user-agent: ^6.0.0
- checksum: 021e13c138279e9edd7d6dcdc484a2658ae07b834ec3f5f41158e3870b3413deb09024408d1615731c960243ba710ca638a868dcd2583f7eb80fa6204b70657b
- languageName: node
- linkType: hard
-
-"@octokit/auth-oauth-device@npm:^6.1.0":
- version: 6.1.0
- resolution: "@octokit/auth-oauth-device@npm:6.1.0"
- dependencies:
- "@octokit/oauth-methods": ^4.1.0
- "@octokit/request": ^8.3.1
- "@octokit/types": ^13.0.0
- universal-user-agent: ^6.0.0
- checksum: 2824f74ea5eca3d8da9793f463ebca725c8a13a241085015f96f037771ef3e5fa82d5842f538353c683b709d8d32ccd481bfc0ba8cbcde708916ea95a78dd0d2
- languageName: node
- linkType: hard
-
-"@octokit/auth-oauth-user@npm:^4.0.0, @octokit/auth-oauth-user@npm:^4.1.0":
- version: 4.1.0
- resolution: "@octokit/auth-oauth-user@npm:4.1.0"
- dependencies:
- "@octokit/auth-oauth-device": ^6.1.0
- "@octokit/oauth-methods": ^4.1.0
- "@octokit/request": ^8.3.1
- "@octokit/types": ^13.0.0
- btoa-lite: ^1.0.0
- universal-user-agent: ^6.0.0
- checksum: 581197a427c1ef153350e46de7315c9da1a98904b67e5e13aed88d36e334d95d869f8f12a35ed70d7232c6afd6d3912200988e41959e30c83f880d072ee8b8ba
- languageName: node
- linkType: hard
-
-"@octokit/auth-token@npm:^4.0.0":
- version: 4.0.0
- resolution: "@octokit/auth-token@npm:4.0.0"
- checksum: d78f4dc48b214d374aeb39caec4fdbf5c1e4fd8b9fcb18f630b1fe2cbd5a880fca05445f32b4561f41262cb551746aeb0b49e89c95c6dd99299706684d0cae2f
- languageName: node
- linkType: hard
-
-"@octokit/auth-unauthenticated@npm:^5.0.0":
- version: 5.0.1
- resolution: "@octokit/auth-unauthenticated@npm:5.0.1"
- dependencies:
- "@octokit/request-error": ^5.0.0
- "@octokit/types": ^12.0.0
- checksum: b6eed1fc15d47f45411c0229dd6613dd8fd4b79afbac23b8c47818da692a35d54f57e088294d9b71ce4dcc0f58ce0c77d12cd2700370d87770059248b9a8fbba
- languageName: node
- linkType: hard
-
-"@octokit/core@npm:^5.0.0":
- version: 5.2.0
- resolution: "@octokit/core@npm:5.2.0"
- dependencies:
- "@octokit/auth-token": ^4.0.0
- "@octokit/graphql": ^7.1.0
- "@octokit/request": ^8.3.1
- "@octokit/request-error": ^5.1.0
- "@octokit/types": ^13.0.0
- before-after-hook: ^2.2.0
- universal-user-agent: ^6.0.0
- checksum: 57d5f02b759b569323dcb76cc72bf94ea7d0de58638c118ee14ec3e37d303c505893137dd72918328794844f35c74b3cd16999319c4b40d410a310d44a9b7566
- languageName: node
- linkType: hard
-
-"@octokit/endpoint@npm:^9.0.1":
- version: 9.0.5
- resolution: "@octokit/endpoint@npm:9.0.5"
- dependencies:
- "@octokit/types": ^13.1.0
- universal-user-agent: ^6.0.0
- checksum: d5cc2df9bd4603844c163eea05eec89c677cfe699c6f065fe86b83123e34554ec16d429e8142dec1e2b4cf56591ef0ce5b1763f250c87bc8e7bf6c74ba59ae82
- languageName: node
- linkType: hard
-
-"@octokit/graphql@npm:^7.1.0":
- version: 7.1.0
- resolution: "@octokit/graphql@npm:7.1.0"
- dependencies:
- "@octokit/request": ^8.3.0
- "@octokit/types": ^13.0.0
- universal-user-agent: ^6.0.0
- checksum: 7b2706796e0269fc033ed149ea211117bcacf53115fd142c1eeafc06ebc5b6290e4e48c03d6276c210d72e3695e8598f83caac556cd00714fc1f8e4707d77448
- languageName: node
- linkType: hard
-
-"@octokit/oauth-app@npm:^6.0.0":
- version: 6.1.0
- resolution: "@octokit/oauth-app@npm:6.1.0"
- dependencies:
- "@octokit/auth-oauth-app": ^7.0.0
- "@octokit/auth-oauth-user": ^4.0.0
- "@octokit/auth-unauthenticated": ^5.0.0
- "@octokit/core": ^5.0.0
- "@octokit/oauth-authorization-url": ^6.0.2
- "@octokit/oauth-methods": ^4.0.0
- "@types/aws-lambda": ^8.10.83
- universal-user-agent: ^6.0.0
- checksum: 4759ef41624928efee484802e3a6280d7a92205f435e0d299bc4b1e39661427d7f9ec33ef0d752dd6ee665e37d4afa81c8a6aea10ba53b8eb7da66167b0c52d4
- languageName: node
- linkType: hard
-
-"@octokit/oauth-authorization-url@npm:^6.0.2":
- version: 6.0.2
- resolution: "@octokit/oauth-authorization-url@npm:6.0.2"
- checksum: 0f11169a3eeb782cc08312c923de1a702b25ae033b972ba40380b6d72cb3f684543c8b6a5cf6f05936fdc6b8892070d4f7581138d8efc1b4c4a55ae6d7762327
- languageName: node
- linkType: hard
-
-"@octokit/oauth-methods@npm:^4.0.0, @octokit/oauth-methods@npm:^4.1.0":
- version: 4.1.0
- resolution: "@octokit/oauth-methods@npm:4.1.0"
- dependencies:
- "@octokit/oauth-authorization-url": ^6.0.2
- "@octokit/request": ^8.3.1
- "@octokit/request-error": ^5.1.0
- "@octokit/types": ^13.0.0
- btoa-lite: ^1.0.0
- checksum: 2ca42f054a3b92f6f3fa9a984df7d75cc8c1f19aba5f6fc9636499dde3a8031e33148cbc936cace103b1eb7fe79d978aee7077aa6f69e0dd996ee345a10f2aa4
- languageName: node
- linkType: hard
-
-"@octokit/openapi-types@npm:^20.0.0":
- version: 20.0.0
- resolution: "@octokit/openapi-types@npm:20.0.0"
- checksum: 23ff7613750f8b5790a0cbed5a2048728a7909e50d726932831044908357a932c7fc0613fb7b86430a49d31b3d03a180632ea5dd936535bfbc1176391a199e96
- languageName: node
- linkType: hard
-
-"@octokit/openapi-types@npm:^22.0.1":
- version: 22.0.1
- resolution: "@octokit/openapi-types@npm:22.0.1"
- checksum: f361764bf965081bb94facc33a171a98c4d94285e5c218ca6355a5aea35d1ec732ab7fa7ac941034ca249601d768cfa5205bcbef0980c0c6faa2b842efeed2ec
- languageName: node
- linkType: hard
-
-"@octokit/plugin-paginate-graphql@npm:^4.0.0":
- version: 4.0.1
- resolution: "@octokit/plugin-paginate-graphql@npm:4.0.1"
- peerDependencies:
- "@octokit/core": ">=5"
- checksum: 109d895303d39c1ba362a260c71202f3c92798faa4f4e05638023685b5ac9191cee61759ea0eee43b9ce945cf8c52aebf2dbd54c392165e86448d6421e97b0f5
- languageName: node
- linkType: hard
-
-"@octokit/plugin-paginate-rest@npm:^9.0.0":
- version: 9.2.1
- resolution: "@octokit/plugin-paginate-rest@npm:9.2.1"
- dependencies:
- "@octokit/types": ^12.6.0
- peerDependencies:
- "@octokit/core": 5
- checksum: 554ad17a7dcfd7028e321ffcae233f8ae7975569084f19d9b6217b47fb182e2604145108de7a9029777e6dc976b27b2dd7387e2e47a77532a72e6c195880576d
- languageName: node
- linkType: hard
-
-"@octokit/plugin-rest-endpoint-methods@npm:^10.0.0":
- version: 10.4.1
- resolution: "@octokit/plugin-rest-endpoint-methods@npm:10.4.1"
- dependencies:
- "@octokit/types": ^12.6.0
- peerDependencies:
- "@octokit/core": 5
- checksum: 3e0e95515ccb7fdd5e5cff32a5e34a688fd275c6703caf786f7c49820e2bf2a66e7d845ba4eae4d03c307c1950ea417e34a17055b25b46e2019123b75b394c56
- languageName: node
- linkType: hard
-
-"@octokit/plugin-retry@npm:^6.0.0":
- version: 6.0.1
- resolution: "@octokit/plugin-retry@npm:6.0.1"
- dependencies:
- "@octokit/request-error": ^5.0.0
- "@octokit/types": ^12.0.0
- bottleneck: ^2.15.3
- peerDependencies:
- "@octokit/core": ">=5"
- checksum: 9c8663b5257cf4fa04cc737c064e9557501719d6d3af7cf8f46434a2117e1cf4b8d25d9eb4294ed255ad17a0ede853542649870612733f4b8ece97e24e391d22
- languageName: node
- linkType: hard
-
-"@octokit/plugin-throttling@npm:^8.0.0":
- version: 8.2.0
- resolution: "@octokit/plugin-throttling@npm:8.2.0"
- dependencies:
- "@octokit/types": ^12.2.0
- bottleneck: ^2.15.3
- peerDependencies:
- "@octokit/core": ^5.0.0
- checksum: 12c357175783bcd0feea454ece57f033928948a0555dc97c79675b56d2cc79043d2a5e28a7554d3531f1de13583634df3b48fb9609f79e8bb3adad92820bd807
- languageName: node
- linkType: hard
-
-"@octokit/request-error@npm:^5.0.0, @octokit/request-error@npm:^5.1.0":
- version: 5.1.0
- resolution: "@octokit/request-error@npm:5.1.0"
- dependencies:
- "@octokit/types": ^13.1.0
- deprecation: ^2.0.0
- once: ^1.4.0
- checksum: 2cdbb8e44072323b5e1c8c385727af6700e3e492d55bc1e8d0549c4a3d9026914f915866323d371b1f1772326d6e902341c872679cc05c417ffc15cadf5f4a4e
- languageName: node
- linkType: hard
-
-"@octokit/request@npm:^8.3.0, @octokit/request@npm:^8.3.1":
- version: 8.4.0
- resolution: "@octokit/request@npm:8.4.0"
- dependencies:
- "@octokit/endpoint": ^9.0.1
- "@octokit/request-error": ^5.1.0
- "@octokit/types": ^13.1.0
- universal-user-agent: ^6.0.0
- checksum: 3d937e817a85c0adf447ab46b428ccd702c31b2091e47adec90583ec2242bd64666306fe8188628fb139aa4752e19400eb7652b0f5ca33cd9e77bbb2c60b202a
- languageName: node
- linkType: hard
-
-"@octokit/types@npm:^12.0.0, @octokit/types@npm:^12.2.0, @octokit/types@npm:^12.6.0":
- version: 12.6.0
- resolution: "@octokit/types@npm:12.6.0"
- dependencies:
- "@octokit/openapi-types": ^20.0.0
- checksum: 850235f425584499a2266d5c585c1c2462ae11e25c650567142f3342cb9ce589c8c8fed87705811ca93271fd28c68e1fa77b88b67b97015d7b63d269fa46ed05
- languageName: node
- linkType: hard
-
-"@octokit/types@npm:^13.0.0, @octokit/types@npm:^13.1.0":
- version: 13.4.0
- resolution: "@octokit/types@npm:13.4.0"
- dependencies:
- "@octokit/openapi-types": ^22.0.1
- checksum: 71d1e61e82ca10cb7f9e79d15158b7a9e84a6fe815fa865e7adbafee225963a18919316d7f0d7c96bc9e6751cf0f1663da056da468e8d5cebcbdaf44316cafba
- languageName: node
- linkType: hard
-
-"@octokit/webhooks-methods@npm:^4.1.0":
- version: 4.1.0
- resolution: "@octokit/webhooks-methods@npm:4.1.0"
- checksum: 0ce67220156d554ae4bc6a7230ae62c0389b9bbee1f6d1077947e64645ee864f0702778e86427d59ae970176620753f54edb44665cedbeb9bc22b9348a074427
- languageName: node
- linkType: hard
-
-"@octokit/webhooks-types@npm:7.4.0":
- version: 7.4.0
- resolution: "@octokit/webhooks-types@npm:7.4.0"
- checksum: bedb819a6ad944ea95cab56da69a0c158d5f689d7f24a45e9a45bcbc4a34550858b1ef0d80a5f4c2fe02a6fc8d14302ca07123fc16a7cce93bb175c11f6a68dc
- languageName: node
- linkType: hard
-
-"@octokit/webhooks@npm:^12.0.4":
- version: 12.2.0
- resolution: "@octokit/webhooks@npm:12.2.0"
- dependencies:
- "@octokit/request-error": ^5.0.0
- "@octokit/webhooks-methods": ^4.1.0
- "@octokit/webhooks-types": 7.4.0
- aggregate-error: ^3.1.0
- checksum: 69d32fd24ea00f632d1ba3edb84c8e15852b47ad120fe7db938bc8fd1f2823dd7e61707b3280a29818925871b51e472c5f892f76eee0c6d0cee8d0e51c7b5f5d
- languageName: node
- linkType: hard
-
"@open-draft/until@npm:^1.0.3":
version: 1.0.3
resolution: "@open-draft/until@npm:1.0.3"
@@ -14100,6 +13808,8 @@ __metadata:
stylelint-config-standard: ^26.0.0
subscriptions-transport-ws: ^0.11.0
tailwindcss: ^3.4.1
+ tweetnacl-sealedbox-js: ^1.2.0
+ tweetnacl-util: ^0.15.1
type-fest: ^3.5.1
typescript: ^4.8.3
vee-validate: ^4.7.0
@@ -14299,12 +14009,14 @@ __metadata:
"@tiptap/core": ^2.0.0-beta.176
"@types/bcrypt": ^5.0.0
"@types/bull": ^3.15.9
+ "@types/chai-as-promised": ^7.1.8
"@types/compression": ^1.7.2
"@types/cookie-parser": ^1.4.7
"@types/debug": ^4.1.7
"@types/deep-equal-in-any-order": ^1.0.1
"@types/ejs": ^3.1.1
"@types/express": ^4.17.13
+ "@types/ioredis-mock": ^8.2.5
"@types/libsodium-wrappers": ^0
"@types/lodash": ^4.14.180
"@types/mailchimp__mailchimp_marketing": ^3.0.9
@@ -14325,12 +14037,14 @@ __metadata:
"@types/zxcvbn": ^4.4.1
"@typescript-eslint/eslint-plugin": ^5.39.0
"@typescript-eslint/parser": ^5.39.0
+ ajv: ^8.12.0
apollo-server-express: ^3.10.2
axios: ^1.6.0
bcrypt: ^5.0.0
bull: ^4.8.5
busboy: ^1.4.0
chai: ^4.2.0
+ chai-as-promised: ^7.1.2
chai-http: ^4.3.0
compression: ^1.7.4
concurrently: ^7.0.0
@@ -14358,6 +14072,7 @@ __metadata:
graphql-subscriptions: ^2.0.0
http-proxy-middleware: v3.0.0-beta.0
ioredis: ^5.2.2
+ ioredis-mock: ^8.9.0
knex: ^2.4.1
libsodium-wrappers: ^0.7.13
lodash: ^4.17.21
@@ -14375,7 +14090,6 @@ __metadata:
nodemailer: ^6.5.0
nodemon: ^2.0.20
nyc: ^15.0.1
- octokit: ^3.1.2
openid-client: ^5.1.7
passport: ^0.6.0
passport-azure-ad: ^4.3.4
@@ -14447,6 +14161,8 @@ __metadata:
rollup-plugin-typescript2: ^0.34.1
type-fest: ^3.11.1
typescript: ^4.5.4
+ znv: ^0.4.0
+ zod: ^3.22.4
peerDependencies:
"@tiptap/core": ^2.0.0-beta.176
pino: ^8.7.0
@@ -14606,6 +14322,7 @@ __metadata:
"@speckle/shared": "workspace:^"
"@types/babel__core": ^7.20.1
"@types/flat": ^5.0.2
+ "@types/lodash-es": 4.17.12
"@types/three": ^0.136.0
"@typescript-eslint/eslint-plugin": ^5.39.0
"@typescript-eslint/parser": ^5.39.0
@@ -14614,13 +14331,11 @@ __metadata:
core-js: ^3.21.1
eslint: ^8.11.0
eslint-config-prettier: ^8.5.0
- 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
regenerator-runtime: ^0.13.7
rollup: ^2.70.1
rollup-plugin-delete: ^2.0.0
@@ -14633,7 +14348,6 @@ __metadata:
troika-three-text: 0.47.2
type-fest: ^4.15.0
typescript: ^4.5.4
- underscore: 1.13.6
vitest: ^1.4.0
languageName: unknown
linkType: soft
@@ -16445,13 +16159,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/aws-lambda@npm:^8.10.83":
- version: 8.10.137
- resolution: "@types/aws-lambda@npm:8.10.137"
- checksum: 172238b8a5d1e4002d11517f4e6739836806b59844da336ce44e72cd544c97453071ffdf6bedd736858e96569123988dd451055bf41ea3876e7201255d5c7713
- languageName: node
- linkType: hard
-
"@types/babel__core@npm:^7.0.0, @types/babel__core@npm:^7.1.14":
version: 7.1.19
resolution: "@types/babel__core@npm:7.1.19"
@@ -16534,13 +16241,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/btoa-lite@npm:^1.0.0":
- version: 1.0.2
- resolution: "@types/btoa-lite@npm:1.0.2"
- checksum: 4c46b163c881a75522c7556dd7a7df8a0d4c680a45e8bac34e50864e1c2d9df8dc90b99f75199154c60ef2faff90896b7e5f11df6936c94167a3e5e1c6f4d935
- languageName: node
- linkType: hard
-
"@types/bull@npm:^3.15.9":
version: 3.15.9
resolution: "@types/bull@npm:3.15.9"
@@ -16563,6 +16263,22 @@ __metadata:
languageName: node
linkType: hard
+"@types/chai-as-promised@npm:^7.1.8":
+ version: 7.1.8
+ resolution: "@types/chai-as-promised@npm:7.1.8"
+ dependencies:
+ "@types/chai": "*"
+ checksum: f0e5eab451b91bc1e289ed89519faf6591932e8a28d2ec9bbe95826eb73d28fe43713633e0c18706f3baa560a7d97e7c7c20dc53ce639e5d75bac46b2a50bf21
+ languageName: node
+ linkType: hard
+
+"@types/chai@npm:*":
+ version: 4.3.16
+ resolution: "@types/chai@npm:4.3.16"
+ checksum: bb5f52d1b70534ed8b4bf74bd248add003ffe1156303802ea367331607c06b494da885ffbc2b674a66b4f90c9ee88759790a5f243879f6759f124f22328f5e95
+ languageName: node
+ linkType: hard
+
"@types/chai@npm:4":
version: 4.3.1
resolution: "@types/chai@npm:4.3.1"
@@ -16917,6 +16633,16 @@ __metadata:
languageName: node
linkType: hard
+"@types/ioredis-mock@npm:^8.2.5":
+ version: 8.2.5
+ resolution: "@types/ioredis-mock@npm:8.2.5"
+ dependencies:
+ "@types/node": "*"
+ ioredis: ">=5"
+ checksum: c32a20e02f8c777780b8663ba83bc7be4a5098fe44c390dd28cb69c9b60fc6dcfa1cbc7e5f8dca3579e85b3be1a08b5b8201a5a2d6d1195e5e5840b13538caab
+ languageName: node
+ linkType: hard
+
"@types/ioredis@npm:*":
version: 4.28.10
resolution: "@types/ioredis@npm:4.28.10"
@@ -17045,6 +16771,15 @@ __metadata:
languageName: node
linkType: hard
+"@types/lodash-es@npm:4.17.12":
+ version: 4.17.12
+ resolution: "@types/lodash-es@npm:4.17.12"
+ dependencies:
+ "@types/lodash": "*"
+ checksum: 990a99e2243bebe9505cb5ad19fbc172beb4a8e00f9075c99fc06c46c2801ffdb40bc2867271cf580d5f48994fc9fb076ec92cd60a20e621603bf22114e5b077
+ languageName: node
+ linkType: hard
+
"@types/lodash-es@npm:^4.17.6":
version: 4.17.6
resolution: "@types/lodash-es@npm:4.17.6"
@@ -20297,7 +20032,7 @@ __metadata:
languageName: node
linkType: hard
-"aggregate-error@npm:^3.0.0, aggregate-error@npm:^3.1.0":
+"aggregate-error@npm:^3.0.0":
version: 3.1.0
resolution: "aggregate-error@npm:3.1.0"
dependencies:
@@ -20365,6 +20100,18 @@ __metadata:
languageName: node
linkType: hard
+"ajv@npm:^8.12.0":
+ version: 8.13.0
+ resolution: "ajv@npm:8.13.0"
+ dependencies:
+ fast-deep-equal: ^3.1.3
+ json-schema-traverse: ^1.0.0
+ require-from-string: ^2.0.2
+ uri-js: ^4.4.1
+ checksum: 6de82d0b2073e645ca3300561356ddda0234f39b35d2125a8700b650509b296f41c00ab69f53178bbe25ad688bd6ac3747ab44101f2f4bd245952e8fd6ccc3c1
+ languageName: node
+ linkType: hard
+
"ajv@npm:^8.6.1":
version: 8.12.0
resolution: "ajv@npm:8.12.0"
@@ -21637,13 +21384,6 @@ __metadata:
languageName: node
linkType: hard
-"before-after-hook@npm:^2.2.0":
- version: 2.2.3
- resolution: "before-after-hook@npm:2.2.3"
- checksum: a1a2430976d9bdab4cd89cb50d27fa86b19e2b41812bf1315923b0cba03371ebca99449809226425dd3bcef20e010db61abdaff549278e111d6480034bebae87
- languageName: node
- linkType: hard
-
"better-opn@npm:^3.0.2":
version: 3.0.2
resolution: "better-opn@npm:3.0.2"
@@ -21749,6 +21489,13 @@ __metadata:
languageName: node
linkType: hard
+"blakejs@npm:^1.1.0":
+ version: 1.2.1
+ resolution: "blakejs@npm:1.2.1"
+ checksum: d699ba116cfa21d0b01d12014a03e484dd76d483133e6dc9eb415aa70a119f08beb3bcefb8c71840106a00b542cba77383f8be60cd1f0d4589cb8afb922eefbe
+ languageName: node
+ linkType: hard
+
"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.9":
version: 4.12.0
resolution: "bn.js@npm:4.12.0"
@@ -21822,13 +21569,6 @@ __metadata:
languageName: node
linkType: hard
-"bottleneck@npm:^2.15.3":
- version: 2.19.5
- resolution: "bottleneck@npm:2.19.5"
- checksum: c5eef1bbea12cef1f1405e7306e7d24860568b0f7ac5eeab706a86762b3fc65ef6d1c641c8a166e4db90f412fc5c948fc5ce8008a8cd3d28c7212ef9c3482bda
- languageName: node
- linkType: hard
-
"bowser@npm:^2.11.0":
version: 2.11.0
resolution: "bowser@npm:2.11.0"
@@ -22074,13 +21814,6 @@ __metadata:
languageName: node
linkType: hard
-"btoa-lite@npm:^1.0.0":
- version: 1.0.0
- resolution: "btoa-lite@npm:1.0.0"
- checksum: c2d61993b801f8e35a96f20692a45459c753d9baa29d86d1343e714f8d6bbe7069f1a20a5ae868488f3fb137d5bd0c560f6fbbc90b5a71050919d2d2c97c0475
- languageName: node
- linkType: hard
-
"buffer-crc32@npm:^0.2.1, buffer-crc32@npm:^0.2.13, buffer-crc32@npm:~0.2.3":
version: 0.2.13
resolution: "buffer-crc32@npm:0.2.13"
@@ -22640,6 +22373,17 @@ __metadata:
languageName: node
linkType: hard
+"chai-as-promised@npm:^7.1.2":
+ version: 7.1.2
+ resolution: "chai-as-promised@npm:7.1.2"
+ dependencies:
+ check-error: ^1.0.2
+ peerDependencies:
+ chai: ">= 2.1.2 < 6"
+ checksum: 671ee980054eb23a523875c1d22929a2ac05d89b5428e1fd12800f54fc69baf41014667b87e2368e2355ee2a3140d3e3d7d5a1f8638b07cfefd7fe38a149e3f6
+ languageName: node
+ linkType: hard
+
"chai-http@npm:^4.3.0":
version: 4.3.0
resolution: "chai-http@npm:4.3.0"
@@ -25329,13 +25073,6 @@ __metadata:
languageName: node
linkType: hard
-"deprecation@npm:^2.0.0, deprecation@npm:^2.3.1":
- version: 2.3.1
- resolution: "deprecation@npm:2.3.1"
- checksum: f56a05e182c2c195071385455956b0c4106fe14e36245b00c689ceef8e8ab639235176a96977ba7c74afb173317fac2e0ec6ec7a1c6d1e6eaa401c586c714132
- languageName: node
- linkType: hard
-
"dequal@npm:^2.0.2":
version: 2.0.3
resolution: "dequal@npm:2.0.3"
@@ -27733,6 +27470,26 @@ __metadata:
languageName: node
linkType: hard
+"fengari-interop@npm:^0.1.3":
+ version: 0.1.3
+ resolution: "fengari-interop@npm:0.1.3"
+ peerDependencies:
+ fengari: ^0.1.0
+ checksum: f483e0aedec3a0b49911ffd3207a55f73c861f95ef84fb7582deb45bc65afa2e7bf8f9fc3734563f6c106a438909f94059b5d08f5a7872ffcc3d45d260a3ee15
+ languageName: node
+ linkType: hard
+
+"fengari@npm:^0.1.4":
+ version: 0.1.4
+ resolution: "fengari@npm:0.1.4"
+ dependencies:
+ readline-sync: ^1.4.9
+ sprintf-js: ^1.1.1
+ tmp: ^0.0.33
+ checksum: bd6b04f9738f9cbb58e3c80684a72bf6f7e74c902d26e4e812b1cfbd69c06da5ffb2a8a67a62ecd75603fc565b6b183c1b72be60f2e60c18aa487aad65b62196
+ languageName: node
+ linkType: hard
+
"fetch-blob@npm:^3.1.2, fetch-blob@npm:^3.1.4":
version: 3.2.0
resolution: "fetch-blob@npm:3.2.0"
@@ -30401,6 +30158,39 @@ __metadata:
languageName: node
linkType: hard
+"ioredis-mock@npm:^8.9.0":
+ version: 8.9.0
+ resolution: "ioredis-mock@npm:8.9.0"
+ dependencies:
+ "@ioredis/as-callback": ^3.0.0
+ "@ioredis/commands": ^1.2.0
+ fengari: ^0.1.4
+ fengari-interop: ^0.1.3
+ semver: ^7.5.4
+ peerDependencies:
+ "@types/ioredis-mock": ^8
+ ioredis: ^5
+ checksum: 9d7480f153a5904f2bbb1555ebf2246f80bfd8c89e10442b6db58984809a1a652050c61fc8ad8a1fa86629ee0f6d2bac2929fd66f34e143263d0449bcdf657aa
+ languageName: node
+ linkType: hard
+
+"ioredis@npm:>=5":
+ version: 5.4.1
+ resolution: "ioredis@npm:5.4.1"
+ dependencies:
+ "@ioredis/commands": ^1.1.1
+ cluster-key-slot: ^1.1.0
+ debug: ^4.3.4
+ denque: ^2.1.0
+ lodash.defaults: ^4.2.0
+ lodash.isarguments: ^3.1.0
+ redis-errors: ^1.2.0
+ redis-parser: ^3.0.0
+ standard-as-callback: ^2.1.0
+ checksum: 92210294f75800febe7544c27b07e4892480172363b11971aa575be5b68f023bfed4bc858abc9792230c153aa80409047a358f174062c14d17536aa4499fe10b
+ languageName: node
+ linkType: hard
+
"ioredis@npm:^4.17.3, ioredis@npm:^4.28.5":
version: 4.28.5
resolution: "ioredis@npm:4.28.5"
@@ -33070,24 +32860,6 @@ __metadata:
languageName: node
linkType: hard
-"jsonwebtoken@npm:^9.0.2":
- version: 9.0.2
- resolution: "jsonwebtoken@npm:9.0.2"
- dependencies:
- jws: ^3.2.2
- lodash.includes: ^4.3.0
- lodash.isboolean: ^3.0.3
- lodash.isinteger: ^4.0.4
- lodash.isnumber: ^3.0.3
- lodash.isplainobject: ^4.0.6
- lodash.isstring: ^4.0.1
- lodash.once: ^4.0.0
- ms: ^2.1.1
- semver: ^7.5.4
- checksum: fc739a6a8b33f1974f9772dca7f8493ca8df4cc31c5a09dcfdb7cff77447dcf22f4236fb2774ef3fe50df0abeb8e1c6f4c41eba82f500a804ab101e2fbc9d61a
- languageName: node
- linkType: hard
-
"jsprim@npm:^1.2.2":
version: 1.4.2
resolution: "jsprim@npm:1.4.2"
@@ -33868,13 +33640,6 @@ __metadata:
languageName: node
linkType: hard
-"lodash.includes@npm:^4.3.0":
- version: 4.3.0
- resolution: "lodash.includes@npm:4.3.0"
- checksum: 71092c130515a67ab3bd928f57f6018434797c94def7f46aafa417771e455ce3a4834889f4267b17887d7f75297dfabd96231bf704fd2b8c5096dc4a913568b6
- languageName: node
- linkType: hard
-
"lodash.isarguments@npm:^3.1.0":
version: 3.1.0
resolution: "lodash.isarguments@npm:3.1.0"
@@ -33882,13 +33647,6 @@ __metadata:
languageName: node
linkType: hard
-"lodash.isboolean@npm:^3.0.3":
- version: 3.0.3
- resolution: "lodash.isboolean@npm:3.0.3"
- checksum: b70068b4a8b8837912b54052557b21fc4774174e3512ed3c5b94621e5aff5eb6c68089d0a386b7e801d679cd105d2e35417978a5e99071750aa2ed90bffd0250
- languageName: node
- linkType: hard
-
"lodash.isempty@npm:^4.4.0":
version: 4.4.0
resolution: "lodash.isempty@npm:4.4.0"
@@ -33910,20 +33668,6 @@ __metadata:
languageName: node
linkType: hard
-"lodash.isinteger@npm:^4.0.4":
- version: 4.0.4
- resolution: "lodash.isinteger@npm:4.0.4"
- checksum: 6034821b3fc61a2ffc34e7d5644bb50c5fd8f1c0121c554c21ac271911ee0c0502274852845005f8651d51e199ee2e0cfebfe40aaa49c7fe617f603a8a0b1691
- languageName: node
- linkType: hard
-
-"lodash.isnumber@npm:^3.0.3":
- version: 3.0.3
- resolution: "lodash.isnumber@npm:3.0.3"
- checksum: 913784275b565346255e6ae6a6e30b760a0da70abc29f3e1f409081585875105138cda4a429ff02577e1bc0a7ae2a90e0a3079a37f3a04c3d6c5aaa532f4cab2
- languageName: node
- linkType: hard
-
"lodash.isplainobject@npm:^4.0.6":
version: 4.0.6
resolution: "lodash.isplainobject@npm:4.0.6"
@@ -33931,13 +33675,6 @@ __metadata:
languageName: node
linkType: hard
-"lodash.isstring@npm:^4.0.1":
- version: 4.0.1
- resolution: "lodash.isstring@npm:4.0.1"
- checksum: eaac87ae9636848af08021083d796e2eea3d02e80082ab8a9955309569cb3a463ce97fd281d7dc119e402b2e7d8c54a23914b15d2fc7fff56461511dc8937ba0
- languageName: node
- linkType: hard
-
"lodash.isundefined@npm:^3.0.1":
version: 3.0.1
resolution: "lodash.isundefined@npm:3.0.1"
@@ -33980,13 +33717,6 @@ __metadata:
languageName: node
linkType: hard
-"lodash.once@npm:^4.0.0":
- version: 4.1.1
- resolution: "lodash.once@npm:4.1.1"
- checksum: d768fa9f9b4e1dc6453be99b753906f58990e0c45e7b2ca5a3b40a33111e5d17f6edf2f768786e2716af90a8e78f8f91431ab8435f761fef00f9b0c256f6d245
- languageName: node
- linkType: hard
-
"lodash.padend@npm:^4.6.1":
version: 4.6.1
resolution: "lodash.padend@npm:4.6.1"
@@ -37718,24 +37448,6 @@ __metadata:
languageName: node
linkType: hard
-"octokit@npm:^3.1.2":
- version: 3.2.0
- resolution: "octokit@npm:3.2.0"
- dependencies:
- "@octokit/app": ^14.0.2
- "@octokit/core": ^5.0.0
- "@octokit/oauth-app": ^6.0.0
- "@octokit/plugin-paginate-graphql": ^4.0.0
- "@octokit/plugin-paginate-rest": ^9.0.0
- "@octokit/plugin-rest-endpoint-methods": ^10.0.0
- "@octokit/plugin-retry": ^6.0.0
- "@octokit/plugin-throttling": ^8.0.0
- "@octokit/request-error": ^5.0.0
- "@octokit/types": ^12.0.0
- checksum: 3ec8efe02144aa6210a5c846947245cbe58d31144fa8e6a1726782001d77fcd044b7e1498a56c5e47aba44c1f5726a37c261f474cb46b816579549f7a09db1f6
- languageName: node
- linkType: hard
-
"ofetch@npm:^1.1.1":
version: 1.1.1
resolution: "ofetch@npm:1.1.1"
@@ -41598,6 +41310,13 @@ __metadata:
languageName: node
linkType: hard
+"readline-sync@npm:^1.4.9":
+ version: 1.4.10
+ resolution: "readline-sync@npm:1.4.10"
+ checksum: 4dbd8925af028dc4cb1bb813f51ca3479035199aa5224886b560eec8e768ab27d7ebf11d69a67ed93d5a130b7c994f0bdb77796326e563cf928bbfd560e3747e
+ languageName: node
+ linkType: hard
+
"real-require@npm:^0.2.0":
version: 0.2.0
resolution: "real-require@npm:0.2.0"
@@ -42664,6 +42383,7 @@ __metadata:
"@types/eslint": ^8.4.1
"@types/lockfile": ^1.0.2
commitizen: ^4.2.5
+ cross-env: ^7.0.3
cz-conventional-changelog: ^3.3.0
eslint: ^8.11.0
eslint-config-prettier: ^8.5.0
@@ -43764,7 +43484,7 @@ __metadata:
languageName: node
linkType: hard
-"sprintf-js@npm:^1.1.3":
+"sprintf-js@npm:^1.1.1, sprintf-js@npm:^1.1.3":
version: 1.1.3
resolution: "sprintf-js@npm:1.1.3"
checksum: a3fdac7b49643875b70864a9d9b469d87a40dfeaf5d34d9d0c5b1cda5fd7d065531fcb43c76357d62254c57184a7b151954156563a4d6a747015cfb41021cad0
@@ -45697,6 +45417,23 @@ __metadata:
languageName: node
linkType: hard
+"tweetnacl-sealedbox-js@npm:^1.2.0":
+ version: 1.2.0
+ resolution: "tweetnacl-sealedbox-js@npm:1.2.0"
+ dependencies:
+ blakejs: ^1.1.0
+ tweetnacl: ^1.0.1
+ checksum: ada2e3827bd0b49a23d576813fb76aa0815ccafa07c8c3dc1a86c56bc56f4f0ec605f255a2f7e4ce0be83f94906bbbb151eeb2edeb12e95420c60c0b34d89d50
+ languageName: node
+ linkType: hard
+
+"tweetnacl-util@npm:^0.15.1":
+ version: 0.15.1
+ resolution: "tweetnacl-util@npm:0.15.1"
+ checksum: ae6aa8a52cdd21a95103a4cc10657d6a2040b36c7a6da7b9d3ab811c6750a2d5db77e8c36969e75fdee11f511aa2b91c552496c6e8e989b6e490e54aca2864fc
+ languageName: node
+ linkType: hard
+
"tweetnacl@npm:^0.14.3, tweetnacl@npm:~0.14.0":
version: 0.14.5
resolution: "tweetnacl@npm:0.14.5"
@@ -45704,6 +45441,13 @@ __metadata:
languageName: node
linkType: hard
+"tweetnacl@npm:^1.0.1":
+ version: 1.0.3
+ resolution: "tweetnacl@npm:1.0.3"
+ checksum: e4a57cac188f0c53f24c7a33279e223618a2bfb5fea426231991652a13247bea06b081fd745d71291fcae0f4428d29beba1b984b1f1ce6f66b06a6d1ab90645c
+ languageName: node
+ linkType: hard
+
"type-check@npm:^0.4.0, type-check@npm:~0.4.0":
version: 0.4.0
resolution: "type-check@npm:0.4.0"
@@ -46370,23 +46114,6 @@ __metadata:
languageName: node
linkType: hard
-"universal-github-app-jwt@npm:^1.1.2":
- version: 1.1.2
- resolution: "universal-github-app-jwt@npm:1.1.2"
- dependencies:
- "@types/jsonwebtoken": ^9.0.0
- jsonwebtoken: ^9.0.2
- checksum: 1bc069c57d319607d4b52143ba89de18cdff2b6afb63107e6972dff9574c7fc453f1a6bb1714817c72898a55c37fa38783be965ebd1c61de661231ca061440d1
- languageName: node
- linkType: hard
-
-"universal-user-agent@npm:^6.0.0":
- version: 6.0.1
- resolution: "universal-user-agent@npm:6.0.1"
- checksum: fdc8e1ae48a05decfc7ded09b62071f571c7fe0bd793d700704c80cea316101d4eac15cc27ed2bb64f4ce166d2684777c3198b9ab16034f547abea0d3aa1c93c
- languageName: node
- linkType: hard
-
"universalify@npm:^0.1.0":
version: 0.1.2
resolution: "universalify@npm:0.1.2"
@@ -46938,7 +46665,7 @@ __metadata:
languageName: node
linkType: hard
-"uri-js@npm:^4.2.2":
+"uri-js@npm:^4.2.2, uri-js@npm:^4.4.1":
version: 4.4.1
resolution: "uri-js@npm:4.4.1"
dependencies: