From 92009018fc87dba5feda3929eaa6d7dff4d8d174 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Fri, 22 Jul 2022 01:03:01 +0300 Subject: [PATCH] #827. Generalizes all gradient based filtering. We can now filter by any property which is numerical from the sandbox. Implemented ramp texture generation. We will need this to generate the ramp texture for discreet colored filters like filterByType, filterByFamily ,etc --- packages/viewer-sandbox/src/Sandbox.ts | 108 +++++++++--------- packages/viewer/src/modules/Assets.ts | 42 ++++++- .../viewer/src/modules/FilteringManager.ts | 9 +- packages/viewer/src/modules/Viewer.ts | 22 ++-- .../viewer/src/modules/materials/Materials.ts | 4 +- 5 files changed, 115 insertions(+), 70 deletions(-) diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts index 6b406edaa..4367c5183 100644 --- a/packages/viewer-sandbox/src/Sandbox.ts +++ b/packages/viewer-sandbox/src/Sandbox.ts @@ -3,6 +3,7 @@ import { GeometryConverter } from '@speckle/viewer' import { Viewer, IViewer, WorldTree } from '@speckle/viewer' import SpeckleLineMaterial from '@speckle/viewer/dist/modules/materials/SpeckleLineMaterial' import { Object3D } from '@speckle/viewer/node_modules/@types/three' +import { Pane } from 'tweakpane' import UrlHelper from './UrlHelper' export default class Sandbox { private viewer: IViewer @@ -29,9 +30,10 @@ export default class Sandbox { } public static filterParams = { - volumeData: {}, - minVolume: 0, - maxVolume: 10000 + filterBy: 'Volume', + data: {}, + minValue: 0, + maxValue: 10000 } public constructor(viewer: Viewer) { @@ -233,68 +235,60 @@ export default class Sandbox { title: 'Filtering', expanded: true }) - // filteringFolder - // .addButton({ - // title: 'Select Random' - // }) - // .on('click', () => { - // this.viewer.speckleRenderer.clearFilter() - // this.viewer.speckleRenderer.beginFilter() - // this.viewer.speckleRenderer.applyFilter( - // this.getRandomNodeIds(0.25), - // FilterMaterial.GRADIENT - // ) - // this.viewer.speckleRenderer.endFilter() - // }) + filteringFolder.addInput(Sandbox.filterParams, 'filterBy', { + options: { + Volume: 'Volume', + Area: 'Area', + SpeckleType: 'speckle_type' + } + }) filteringFolder .addButton({ - title: 'Filter By Volume' + title: 'Apply Filter' }) .on('click', () => { - Sandbox.filterParams.volumeData = this.viewer.debugGetVolumeNodes() - Sandbox.filterParams.minVolume = Sandbox.filterParams.volumeData.min - Sandbox.filterParams.maxVolume = Sandbox.filterParams.volumeData.max + Sandbox.filterParams.data = this.viewer.debugGetFilterByPropetyNodes( + Sandbox.filterParams.filterBy + ) + Sandbox.filterParams.minValue = Sandbox.filterParams.data.min + Sandbox.filterParams.maxValue = Sandbox.filterParams.data.max - this.viewer.debugApplyVolumeFilter(Sandbox.filterParams.volumeData) - this.minVolumeControl.controller_.valueController.value.constraint_.constraints[0].minValue = - Sandbox.filterParams.minVolume - this.minVolumeControl.controller_.valueController.value.constraint_.constraints[0].maxValue = - Sandbox.filterParams.maxVolume - this.maxVolumeControl.controller_.valueController.value.constraint_.constraints[0].minValue = - Sandbox.filterParams.minVolume - this.maxVolumeControl.controller_.valueController.value.constraint_.constraints[0].maxValue = - Sandbox.filterParams.maxVolume - this.maxVolumeControl.disabled = false - this.minVolumeControl.disabled = false + this.viewer.debugApplyByPropetyFilter( + Sandbox.filterParams.data, + Sandbox.filterParams.filterBy + ) + if (this.maxVolumeControl) this.maxVolumeControl.dispose() + if (this.minVolumeControl) this.minVolumeControl.dispose() + + this.minVolumeControl = filteringFolder + .addInput(Sandbox.filterParams, 'minValue', { + min: Sandbox.filterParams.minValue, + max: Sandbox.filterParams.maxValue + }) + .on('change', () => { + this.viewer.debugApplyByPropetyFilter( + Sandbox.filterParams.data, + Sandbox.filterParams.filterBy, + Sandbox.filterParams.minValue, + Sandbox.filterParams.maxValue + ) + }) + this.maxVolumeControl = filteringFolder + .addInput(Sandbox.filterParams, 'maxValue', { + min: Sandbox.filterParams.minValue, + max: Sandbox.filterParams.maxValue + }) + .on('change', () => { + this.viewer.debugApplyByPropetyFilter( + Sandbox.filterParams.data, + Sandbox.filterParams.filterBy, + Sandbox.filterParams.minValue, + Sandbox.filterParams.maxValue + ) + }) this.pane.refresh() }) - this.minVolumeControl = filteringFolder - .addInput(Sandbox.filterParams, 'minVolume', { - min: 0, - max: 100, - disabled: true - }) - .on('change', () => { - this.viewer.debugApplyVolumeFilter( - Sandbox.filterParams.volumeData, - Sandbox.filterParams.minVolume, - Sandbox.filterParams.maxVolume - ) - }) - this.maxVolumeControl = filteringFolder - .addInput(Sandbox.filterParams, 'maxVolume', { - min: 0, - max: 100, - disabled: true - }) - .on('change', () => { - this.viewer.debugApplyVolumeFilter( - Sandbox.filterParams.volumeData, - Sandbox.filterParams.minVolume, - Sandbox.filterParams.maxVolume - ) - }) } public async loadUrl(url: string) { diff --git a/packages/viewer/src/modules/Assets.ts b/packages/viewer/src/modules/Assets.ts index 18dd9c2eb..1cae882b1 100644 --- a/packages/viewer/src/modules/Assets.ts +++ b/packages/viewer/src/modules/Assets.ts @@ -1,4 +1,11 @@ -import { Texture, PMREMGenerator, WebGLRenderer, TextureLoader } from 'three' +import { + Texture, + PMREMGenerator, + WebGLRenderer, + TextureLoader, + Color, + DataTexture +} from 'three' import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js' import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js' import { Asset, AssetType } from '../IViewer' @@ -96,4 +103,37 @@ export class Assets { } }) } + + public static generateGradientRampTexture( + fromColor: string, + toColor: string, + steps: number + ) { + fromColor + toColor + steps + // NOT NECESSARY AT THE MOMENT. USING STATIC GRADIENT RAMP + } + + public static generateDiscreetRampTexture(hexColors: string[]): Texture { + const width = hexColors.length + const height = 1 + + const size = width * height + const data = new Uint8Array(4 * size) + + for (let k = 0; k < hexColors.length; k++) { + const stride = k * 4 + const color = new Color(hexColors[k]) + color.convertSRGBToLinear() + data[stride] = Math.floor(color.r * 255) + data[stride + 1] = Math.floor(color.g * 255) + data[stride + 2] = Math.floor(color.b * 255) + data[stride + 3] = 255 + } + + const texture = new DataTexture(data, width, height) + texture.needsUpdate = true + return texture + } } diff --git a/packages/viewer/src/modules/FilteringManager.ts b/packages/viewer/src/modules/FilteringManager.ts index 303879617..c81a45bca 100644 --- a/packages/viewer/src/modules/FilteringManager.ts +++ b/packages/viewer/src/modules/FilteringManager.ts @@ -1,11 +1,16 @@ +import { Texture } from 'three' + export enum FilterMaterialType { SELECT, GHOST, - GRADIENT + GRADIENT, + COLORED } + export interface FilterMaterial { filterType: FilterMaterialType - gradientIndex?: number + rampIndex?: number + rampTexture?: Texture } export class FilteringManager { diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index c9a83d4a4..65c9cfedf 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -322,7 +322,11 @@ export class Viewer extends EventEmitter implements IViewer { } } - public debugGetVolumeNodes(): { min: number; max: number; nodes: TreeNode[] } { + public debugGetFilterByPropetyNodes(propertyName: string): { + min: number + max: number + nodes: TreeNode[] + } { const volumeNodes = [] let minVolume = Infinity let maxVolume = 0 @@ -331,7 +335,7 @@ export class Viewer extends EventEmitter implements IViewer { if (params) { for (const k in params) { if (!(params[k] instanceof Object)) continue - if (params[k].name === 'Volume') { + if (params[k].name === propertyName) { minVolume = Math.min(minVolume, params[k].value) maxVolume = Math.max(maxVolume, params[k].value) volumeNodes.push(node) @@ -348,21 +352,23 @@ export class Viewer extends EventEmitter implements IViewer { } } - public debugApplyVolumeFilter( + public debugApplyByPropetyFilter( data: { min: number; max: number; nodes: TreeNode[] }, + propertyName: string, min?: number, max?: number ) { const nodesGradient = [] const nodesGhost = [] - const volumeValues = [] + const values = [] + /** This is the lazy approach */ WorldTree.getInstance().walk((node: TreeNode) => { const params = node.model.raw.parameters if (params) { for (const k in params) { if (!(params[k] instanceof Object)) continue - if (params[k].name === 'Volume') { + if (params[k].name === propertyName) { const volumeValue = params[k].value const pasMin = min !== undefined ? volumeValue >= min : true const pasMax = max !== undefined ? volumeValue <= max : true @@ -373,7 +379,7 @@ export class Viewer extends EventEmitter implements IViewer { !nodesGradient.includes(node) ) { nodesGradient.push(node) - volumeValues.push(volumeValue) + values.push(volumeValue) } } else { if (!nodesGhost.includes(node)) nodesGhost.push(node) @@ -397,10 +403,10 @@ export class Viewer extends EventEmitter implements IViewer { const ids = WorldTree.getRenderTree() .getRenderViewsForNode(nodesGradient[k], nodesGradient[k]) .map((value) => value.renderData.id) - const t = (volumeValues[k] - data.min) / (data.max - data.min) + const t = (values[k] - data.min) / (data.max - data.min) this.speckleRenderer.applyFilter(ids, { filterType: FilterMaterialType.GRADIENT, - gradientIndex: t + rampIndex: t }) } diff --git a/packages/viewer/src/modules/materials/Materials.ts b/packages/viewer/src/modules/materials/Materials.ts index e68df8439..0bee16d0c 100644 --- a/packages/viewer/src/modules/materials/Materials.ts +++ b/packages/viewer/src/modules/materials/Materials.ts @@ -385,9 +385,9 @@ export default class Materials { } public getFilterMaterialOptions(filterMaterial: FilterMaterial) { - return filterMaterial.gradientIndex + return filterMaterial.rampIndex ? { - gradientIndex: filterMaterial.gradientIndex + gradientIndex: filterMaterial.rampIndex } : null }