Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Poor Man's Mesh Colors Switch #2688

Merged
merged 9 commits into from
Aug 19, 2024
41 changes: 38 additions & 3 deletions packages/viewer-sandbox/src/Sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import { Euler, Vector3 } from 'three'
import { GeometryType } from '@speckle/viewer'
import { MeshBatch } from '@speckle/viewer'

import { Color } from 'three'

export default class Sandbox {
private viewer: Viewer
private pane: Pane
Expand Down Expand Up @@ -141,6 +143,7 @@ export default class Sandbox {
this.pane = new Pane({ title: 'Speckle Sandbox', expanded: true })
// Mad HTML/CSS skills
container.appendChild(this.pane['containerElem_'])

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
this.pane['containerElem_'].style = 'pointer-events:auto;'

Expand Down Expand Up @@ -375,6 +378,7 @@ export default class Sandbox {
input.onchange = (e) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const file = e.target?.files[0] as Blob & { name: string }

Expand Down Expand Up @@ -470,9 +474,6 @@ export default class Sandbox {
})
screenshot.on('click', async () => {
console.warn(await this.viewer.screenshot())
// this.viewer
// .getExtension(FilteringExtension)
// .hideObjects([this.viewer.getWorldTree().root.model.children[0].id])
})

const rotate = this.tabs.pages[0].addButton({
Expand All @@ -492,6 +493,40 @@ export default class Sandbox {
}
})

const colors = this.tabs.pages[0].addButton({
title: `PM's Colors`
})
colors.on('click', async () => {
const colorNodes = this.viewer.getWorldTree().findAll(
(node: TreeNode) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
node.model.renderView &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
node.model.renderView.renderData.colorMaterial &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
node.model.renderView.geometryType === GeometryType.MESH
)
const colorMap: { [color: number]: Array<string> } = {}
for (let k = 0; k < colorNodes.length; k++) {
const node = colorNodes[k]
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const color: number = node.model.renderView.renderData.colorMaterial.color
if (!colorMap[color]) colorMap[color] = []
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
colorMap[color].push(node.model.id)
}
const colorGroups = []

for (const color in colorMap) {
colorGroups.push({
objectIds: colorMap[color],
color: '#' + new Color(Number.parseInt(color)).getHexString()
})
}
console.log(colorGroups)
this.viewer.getExtension(FilteringExtension).setUserObjectColors(colorGroups)
})

this.tabs.pages[0]
.addInput({ dampening: 30 }, 'dampening', {
label: 'Dampening',
Expand Down
9 changes: 8 additions & 1 deletion packages/viewer-sandbox/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ const getStream = () => {
// prettier-ignore
// '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://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'
Expand Down Expand Up @@ -404,6 +404,13 @@ const getStream = () => {
// 'https://latest.speckle.systems/projects/126cd4b7bb/models/0a4181c73b'
// 'https://latest.speckle.systems/projects/126cd4b7bb/models/bcf086cdc4'
// 'https://latest.speckle.systems/projects/126cd4b7bb/models/6221c985c0'

// DUI3 Color Proxies
' https://app.speckle.systems/projects/93200a735d/models/d1fbf678f1'
// 'https://app.speckle.systems/projects/b53a53697a/models/93fa215ba9'
// 'https://latest.speckle.systems/projects/126cd4b7bb/models/5ec85fc2a2'
// 'https://latest.speckle.systems/projects/2af60ce1b6/models/09dbceb25f@5af6d6a3f4'
// 'https://latest.speckle.systems/projects/2af60ce1b6/models/09dbceb25f@ebb895355d'
)
}

Expand Down
14 changes: 10 additions & 4 deletions packages/viewer/src/modules/batching/Batcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { MathUtils } from 'three'
import LineBatch from './LineBatch.js'
import Materials, {
FilterMaterialType,
MinimalMaterial,
type DisplayStyle,
type RenderMaterial
} from '../materials/Materials.js'
Expand Down Expand Up @@ -323,17 +324,22 @@ export default class Batcher {

const geometryType =
batchType !== undefined ? batchType : renderViews[0].geometryType
let matRef: RenderMaterial | DisplayStyle | null =
let matRef: RenderMaterial | DisplayStyle | MinimalMaterial | null =
renderViews[0].renderData.renderMaterial

if (geometryType === GeometryType.MESH) {
matRef = renderViews[0].renderData.renderMaterial
} else if (geometryType === GeometryType.LINE) {
matRef = renderViews[0].renderData.displayStyle
matRef =
renderViews[0].renderData.colorMaterial !== undefined
? renderViews[0].renderData.colorMaterial
: renderViews[0].renderData.displayStyle
} else if (geometryType === GeometryType.POINT) {
matRef =
renderViews[0].renderData.renderMaterial ||
renderViews[0].renderData.displayStyle
renderViews[0].renderData.colorMaterial !== undefined
? renderViews[0].renderData.colorMaterial
: renderViews[0].renderData.renderMaterial ||
renderViews[0].renderData.displayStyle
} else if (geometryType === GeometryType.POINT_CLOUD) {
matRef = renderViews[0].renderData.renderMaterial
} else if (geometryType === GeometryType.TEXT) {
Expand Down
1 change: 1 addition & 0 deletions packages/viewer/src/modules/loaders/GeometryConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export enum SpeckleType {
Transform = 'Transform',
InstanceProxy = 'InstanceProxy',
RenderMaterialProxy = 'RenderMaterialProxy',
ColorProxy = 'ColorProxy',
Unknown = 'Unknown'
}

Expand Down
34 changes: 30 additions & 4 deletions packages/viewer/src/modules/loaders/Speckle/SpeckleConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export default class SpeckleConverter {
private instancedObjectsLookupTable: { [id: string]: SpeckleObject } = {}
private instanceProxies: { [id: string]: TreeNode } = {}
private renderMaterialMap: { [id: string]: SpeckleObject } = {}
private colorMap: { [id: string]: number } = {}
private instanceCounter = 0

private readonly NodeConverterMapping: {
Expand All @@ -51,6 +52,7 @@ export default class SpeckleConverter {
InstanceDefinitionProxy: this.InstanceDefinitionProxyToNode.bind(this),
InstanceProxy: this.InstanceProxyToNode.bind(this),
RenderMaterialProxy: this.RenderMaterialProxyToNode.bind(this),
ColorProxy: this.ColorProxyToNode.bind(this),
Parameter: null
}

Expand Down Expand Up @@ -563,6 +565,25 @@ export default class SpeckleConverter {
}
}

private async ColorProxyToNode(obj: SpeckleObject, _node: TreeNode) {
if (!obj.value || typeof obj.value !== 'number') {
Logger.error(`Color ${obj.id} has no value, or value is not a number!`)
return
}
if (!obj.objects || !Array.isArray(obj.objects) || obj.objects.length === 0) {
Logger.warn(`Color Proxy ${obj.id} has no target objects!`)
return
}
const colorValue = obj.value
const targetObjects = obj.objects as []
for (let k = 0; k < targetObjects.length; k++) {
if (this.colorMap[targetObjects[k]]) {
Logger.error(`Overwritting color ${targetObjects[k]}`)
}
this.colorMap[targetObjects[k]] = colorValue
}
}

private getInstanceProxyDefinitionId(obj: SpeckleObject): string {
return (obj.DefinitionId || obj.definitionId) as string
}
Expand Down Expand Up @@ -680,19 +701,24 @@ export default class SpeckleConverter {
}

public async applyMaterials() {
let count = Object.keys(this.renderMaterialMap).length
if (count === 0) return
let renderMaterialCount = Object.keys(this.renderMaterialMap).length
let colorCount = Object.keys(this.colorMap).length
if (renderMaterialCount === 0 && colorCount === 0) return

/** Do a short async walk */
await this.tree.walkAsync((node: TreeNode) => {
if (!node.model.raw.applicationId) return true
const applicationId = node.model.raw.applicationId.toString()
if (this.renderMaterialMap[applicationId] !== undefined) {
node.model.raw.renderMaterial = this.renderMaterialMap[applicationId]
count--
renderMaterialCount--
}
if (this.colorMap[applicationId] !== undefined) {
node.model.raw.color = this.colorMap[applicationId]
colorCount--
}
/** Break out when all applicationIds are accounted for*/
if (count === 0) return false
if (renderMaterialCount === 0 && colorCount === 0) return false
return true
})
}
Expand Down
59 changes: 47 additions & 12 deletions packages/viewer/src/modules/materials/Materials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,25 @@ const defaultGradient: Asset = {
type: AssetType.TEXTURE_8BPP
}

export interface RenderMaterial {
export interface RenderMaterial extends MinimalMaterial {
id: string
color: number
emissive: number
opacity: number
roughness: number
metalness: number
vertexColors: boolean
}

export interface DisplayStyle {
export interface DisplayStyle extends MinimalMaterial {
id: string
color: number
lineWeight: number
opacity?: number
}

export interface MinimalMaterial {
color: number
}

export enum FilterMaterialType {
GHOST,
GRADIENT,
Expand Down Expand Up @@ -164,6 +166,18 @@ export default class Materials {
return displayStyle
}

public static colorMaterialFromNode(node: TreeNode | null): MinimalMaterial | null {
if (!node) return null

let colorMaterial: MinimalMaterial | null = null
if (node.model.raw.color) {
colorMaterial = {
color: node.model.raw.color
}
}
return colorMaterial
}

public static fastCopy(from: Material, to: Material) {
;(to as unknown as SpeckleMaterial).fastCopy(from, to)
/** Doing it via three.js is slow as hell */
Expand Down Expand Up @@ -194,6 +208,10 @@ export default class Materials {
return plm
}

private static minimalMaterialToString(minimalMaterial: MinimalMaterial) {
return minimalMaterial.color.toString()
}

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
Expand Down Expand Up @@ -244,20 +262,34 @@ export default class Materials {
renderView: NodeRenderView,
materialData?: RenderMaterial | DisplayStyle | MaterialOptions | null
): number {
/** See if we need to get the hash from the DUI3 color material. In DUI3, Points and Lines
* always use the color material exclusively. Any other render material or display style is ignored
*/
const colorMaterialData = renderView.renderData.colorMaterial
let mat = ''
const useColorMaterialHash =
colorMaterialData &&
!materialData &&
(renderView.geometryType === GeometryType.LINE ||
renderView.geometryType === GeometryType.POINT)

if (!materialData) {
materialData =
renderView.renderData.renderMaterial || renderView.renderData.displayStyle
}
let mat = ''
if (materialData) {
/** DUI3 rules which apply only if the technical material exist (color proxies) in absence of a render material or display style from DUI2
* The technical material will contribute to the material hash
*/
if (useColorMaterialHash) {
mat += Materials.minimalMaterialToString(colorMaterialData)
} else if (materialData) {
mat =
Materials.isRendeMaterial(materialData) &&
(renderView.geometryType === GeometryType.MESH ||
renderView.geometryType === GeometryType.POINT ||
renderView.geometryType === GeometryType.POINT || // Maybe even include GeometryType.POINT_CLOUD actually?
renderView.geometryType === GeometryType.TEXT)
? Materials.renderMaterialToString(materialData)
: Materials.isDisplayStyle(materialData) &&
// && renderView.geometryType !== GeometryType.POINT // Allow Points to use displayStyle
renderView.geometryType !== GeometryType.MESH
? Materials.displayStyleToString(materialData)
: ''
Expand All @@ -268,6 +300,7 @@ export default class Materials {
mat += '/' + (materialData as MaterialOptions).pointSize
}
}

let geometry = ''
if (renderView.renderData.geometry.attributes)
geometry = renderView.renderData.geometry.attributes.COLOR ? 'vertexColors' : ''
Expand Down Expand Up @@ -724,14 +757,16 @@ export default class Materials {
const mat = new SpecklePointMaterial(
{
color: safeColor,
opacity: materialData.opacity,
vertexColors: materialData.vertexColors,
...(materialData.opacity !== undefined && { opacity: materialData.opacity }),
...(materialData.vertexColors !== undefined && {
vertexColors: materialData.vertexColors
}),
size: 2,
sizeAttenuation: false
},
['USE_RTE']
)
mat.transparent = mat.opacity < 1 ? true : false
if (mat.opacity !== undefined) mat.transparent = mat.opacity < 1 ? true : false
mat.depthWrite = mat.transparent ? false : true
mat.toneMapped = false
mat.color.convertSRGBToLinear()
Expand All @@ -757,7 +792,7 @@ export default class Materials {

public getMaterial(
hash: number,
material: RenderMaterial | DisplayStyle | null,
material: RenderMaterial | DisplayStyle | MinimalMaterial | null,
type: GeometryType
): Material {
let mat
Expand Down
2 changes: 2 additions & 0 deletions packages/viewer/src/modules/tree/NodeRenderView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Box3 } from 'three'
import { GeometryType } from '../batching/Batch.js'
import { GeometryAttributes, type GeometryData } from '../converter/Geometry.js'
import Materials, {
MinimalMaterial,
type DisplayStyle,
type RenderMaterial
} from '../materials/Materials.js'
Expand All @@ -14,6 +15,7 @@ export interface NodeRenderData {
geometry: GeometryData
renderMaterial: RenderMaterial | null
displayStyle: DisplayStyle | null
colorMaterial: MinimalMaterial | null
}

export class NodeRenderView {
Expand Down
Loading