4aef572a44
* Fixed an issue with HDRIs not generating proper PMREMs over multiple viewer instances * WIP on makign the world tree multiple instanced * The WorldTree is no longer static. Each viewer has it's own instance and it hands it over to whoever needs it. * Fixed an issue with filtering and the new non-static tree. Also removed the 'root' key from the NodeData structure since it's not needed * Added an guard when building batches for situations where all render views have invalid geometries. This generally means there is somethign wrong with the stream itself * multi-viewer css fixes --------- Co-authored-by: Dimitrie Stefanescu <didimitrie@gmail.com>
217 lines
6.8 KiB
TypeScript
217 lines
6.8 KiB
TypeScript
import { Box3, Matrix4 } from 'three'
|
|
import { GeometryConverter, SpeckleType } from '../converter/GeometryConverter'
|
|
import { TreeNode, WorldTree } from './WorldTree'
|
|
import Materials from '../materials/Materials'
|
|
import { NodeRenderData, NodeRenderView } from './NodeRenderView'
|
|
import { Geometry } from '../converter/Geometry'
|
|
import Logger from 'js-logger'
|
|
|
|
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
|
|
}
|
|
|
|
public constructor(tree: WorldTree, subtreeRoot: TreeNode) {
|
|
this.tree = tree
|
|
this.root = subtreeRoot
|
|
}
|
|
|
|
public buildRenderTree() {
|
|
this.tree.walk((node: TreeNode): boolean => {
|
|
const rendeNode = this.buildRenderNode(node)
|
|
node.model.renderView = rendeNode ? new NodeRenderView(rendeNode) : null
|
|
if (node.model.renderView && node.model.renderView.hasGeometry) {
|
|
const transform = this.computeTransform(node)
|
|
if (rendeNode.geometry.bakeTransform) {
|
|
transform.multiply(rendeNode.geometry.bakeTransform)
|
|
}
|
|
Geometry.transformGeometryData(rendeNode.geometry, transform)
|
|
node.model.renderView.computeAABB()
|
|
this._treeBounds.union(node.model.renderView.aabb)
|
|
|
|
if (!GeometryConverter.keepGeometryData) {
|
|
GeometryConverter.disposeNodeGeometryData(node.model)
|
|
}
|
|
}
|
|
|
|
return true
|
|
})
|
|
}
|
|
|
|
public buildRenderTreeAsync(priority: number): Promise<boolean> {
|
|
const p = this.tree.walkAsync(
|
|
(node: TreeNode): boolean => {
|
|
const rendeNode = this.buildRenderNode(node)
|
|
node.model.renderView = rendeNode ? new NodeRenderView(rendeNode) : null
|
|
if (node.model.renderView && node.model.renderView.hasGeometry) {
|
|
const transform = this.computeTransform(node)
|
|
if (rendeNode.geometry.bakeTransform) {
|
|
transform.multiply(rendeNode.geometry.bakeTransform)
|
|
}
|
|
Geometry.transformGeometryData(rendeNode.geometry, transform)
|
|
node.model.renderView.computeAABB()
|
|
this._treeBounds.union(node.model.renderView.aabb)
|
|
|
|
if (!GeometryConverter.keepGeometryData) {
|
|
GeometryConverter.disposeNodeGeometryData(node.model)
|
|
}
|
|
}
|
|
return !this.cancel
|
|
},
|
|
this.root,
|
|
priority
|
|
)
|
|
return p
|
|
}
|
|
|
|
private buildRenderNode(node: TreeNode): NodeRenderData {
|
|
let ret: NodeRenderData = null
|
|
const geometryData = GeometryConverter.convertNodeToGeometryData(node.model)
|
|
if (geometryData) {
|
|
const renderMaterialNode = this.getRenderMaterialNode(node)
|
|
const displayStyleNode = this.getDisplayStyleNode(node)
|
|
ret = {
|
|
id: node.model.id,
|
|
speckleType: GeometryConverter.getSpeckleType(node.model),
|
|
geometry: geometryData,
|
|
renderMaterial: Materials.renderMaterialFromNode(
|
|
renderMaterialNode || displayStyleNode
|
|
),
|
|
/** Line-type geometry can also use a renderMaterial*/
|
|
displayStyle: Materials.displayStyleFromNode(
|
|
displayStyleNode || renderMaterialNode
|
|
)
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
private getRenderMaterialNode(node: TreeNode): TreeNode {
|
|
if (node.model.raw.renderMaterial) {
|
|
return node
|
|
}
|
|
const ancestors = this.tree.getAncestors(node)
|
|
for (let k = 0; k < ancestors.length; k++) {
|
|
if (ancestors[k].model.raw.renderMaterial) {
|
|
return ancestors[k]
|
|
}
|
|
}
|
|
}
|
|
|
|
private getDisplayStyleNode(node: TreeNode): TreeNode {
|
|
if (node.model.raw.displayStyle) {
|
|
return node
|
|
}
|
|
const ancestors = this.tree.getAncestors(node)
|
|
for (let k = 0; k < ancestors.length; k++) {
|
|
if (ancestors[k].model.raw.displayStyle) {
|
|
return ancestors[k]
|
|
}
|
|
}
|
|
}
|
|
|
|
public computeTransform(node: TreeNode): Matrix4 {
|
|
const transform = new Matrix4()
|
|
const ancestors = this.tree.getAncestors(node)
|
|
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.BlockInstance) {
|
|
transform.premultiply(renderNode.geometry.transform)
|
|
} else if (renderNode.speckleType === SpeckleType.RevitInstance) {
|
|
/** Revit Instances *hosted* on other instances do not stack the host's transform */
|
|
if (k > 0) {
|
|
const curentAncestorId = ancestors[k].model.raw.id
|
|
if (ancestors[k - 1].model.raw.host === curentAncestorId) continue
|
|
}
|
|
transform.premultiply(renderNode.geometry.transform)
|
|
}
|
|
}
|
|
}
|
|
return transform
|
|
}
|
|
|
|
public getAtomicRenderViews(...types: SpeckleType[]): NodeRenderView[] {
|
|
return this.root
|
|
.all((node: TreeNode): boolean => {
|
|
return (
|
|
node.model.renderView !== null &&
|
|
types.includes(node.model.renderView.renderData.speckleType) &&
|
|
(node.model.atomic ||
|
|
(node.parent.model.atomic && !node.parent.model.renderView?.hasGeometry))
|
|
)
|
|
})
|
|
.map((val: TreeNode) => val.model.renderView)
|
|
}
|
|
|
|
/** 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) {
|
|
return [node.model.renderView]
|
|
}
|
|
|
|
return (parent ? parent : node.parent)
|
|
.all((_node: TreeNode): boolean => {
|
|
return _node.model.renderView && _node.model.renderView.hasGeometry
|
|
})
|
|
.map((val: TreeNode) => val.model.renderView)
|
|
}
|
|
|
|
public getRenderViewNodesForNode(node: TreeNode, parent?: TreeNode): TreeNode[] {
|
|
if (node.model.atomic && node.model.renderView) {
|
|
return [node]
|
|
}
|
|
|
|
return (parent ? parent : node.parent).all((_node: TreeNode): boolean => {
|
|
return _node.model.renderView && _node.model.renderView.hasGeometry
|
|
})
|
|
}
|
|
|
|
public getAtomicParent(node: TreeNode) {
|
|
if (node.model.atomic) {
|
|
return node.model.renderView
|
|
}
|
|
return this.tree.getAncestors(node).find((node) => node.model.atomic)
|
|
}
|
|
|
|
public getRenderViewsForNodeId(id: string): NodeRenderView[] {
|
|
const node = this.tree.findId(id)
|
|
if (!node) {
|
|
Logger.warn(`Id ${id} does not exist`)
|
|
return null
|
|
}
|
|
return this.getRenderViewsForNode(node)
|
|
}
|
|
|
|
public getRenderViewForNodeId(id: string): NodeRenderView {
|
|
const node = this.tree.findId(id)
|
|
if (!node) {
|
|
Logger.warn(`Id ${id} does not exist`)
|
|
return null
|
|
}
|
|
return node.model.renderView
|
|
}
|
|
|
|
public purge() {
|
|
this.tree = null
|
|
}
|
|
|
|
public cancelBuild(subtreeId: string) {
|
|
this.cancel = true
|
|
this.tree.purge(subtreeId)
|
|
this.purge()
|
|
}
|
|
}
|