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 }