From fcea53ddd2edc898c7a9fc170739c229dd982fbf Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Fri, 3 Jun 2022 16:37:06 +0300 Subject: [PATCH] Implemented the concept of RenderTree which just extends the functionality of the general purpose WorldTree. Moved all geometry conversions to a seaprate source where no async, no fetches, no special things happen, with the intent of possibly moving this to a webworker in the future --- packages/viewer-sandbox/src/main.ts | 4 +- packages/viewer/src/modules/NodeRenderView.ts | 22 + packages/viewer/src/modules/RenderTree.ts | 159 +++++ .../viewer/src/modules/ViewerObjectLoader.js | 13 +- .../viewer/src/modules/converter/Converter.ts | 16 +- .../modules/converter/GeometryConverter.ts | 545 ++++++++++++++++++ .../viewer/src/modules/converter/WorldTree.ts | 33 +- 7 files changed, 768 insertions(+), 24 deletions(-) create mode 100644 packages/viewer/src/modules/NodeRenderView.ts create mode 100644 packages/viewer/src/modules/RenderTree.ts create mode 100644 packages/viewer/src/modules/converter/GeometryConverter.ts diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 64eefba1b..6fd97d074 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -14,8 +14,8 @@ if (!container) { // Viewer setup const params = DefaultViewerParams -params.environmentSrc = - 'https://speckle-xyz-assets.ams3.digitaloceanspaces.com/studio010.hdr' +// params.environmentSrc = +// 'https://speckle-xyz-assets.ams3.digitaloceanspaces.com/studio010.hdr' // 'http://localhost:3033/sample-hdri.exr' // TODO: Remove this, just a test of image bundling capabilities! diff --git a/packages/viewer/src/modules/NodeRenderView.ts b/packages/viewer/src/modules/NodeRenderView.ts new file mode 100644 index 000000000..16d5519b9 --- /dev/null +++ b/packages/viewer/src/modules/NodeRenderView.ts @@ -0,0 +1,22 @@ +import { GeometryData } from './converter/Geometry' +import { SpeckleType } from './converter/GeometryConverter' + +export interface NodeRenderData { + speckleType: SpeckleType + geometry: GeometryData + batchId: string + batchIndexStart: number + batchIndexCount: number +} + +export class NodeRenderView { + private readonly _renderData: { [id: string]: NodeRenderData } = {} + + public get renderData() { + return this._renderData + } + + public setData(id: string, data: NodeRenderData) { + this._renderData[id] = data + } +} diff --git a/packages/viewer/src/modules/RenderTree.ts b/packages/viewer/src/modules/RenderTree.ts new file mode 100644 index 000000000..0a1414d04 --- /dev/null +++ b/packages/viewer/src/modules/RenderTree.ts @@ -0,0 +1,159 @@ +import { Geometry } from './converter/Geometry' +import { GeometryConverter, SpeckleType } from './converter/GeometryConverter' +import ObjectWrapper from './converter/ObjectWrapper' +import { TreeNode } from './converter/WorldTree' +import { NodeRenderData, NodeRenderView } from './NodeRenderView' + +export class RenderTree { + private root: TreeNode + public constructor(root: TreeNode) { + this.root = root + } + + public buildRenderTree() { + this.root.walk((node: TreeNode): boolean => { + let renderView = null + const geometryData = GeometryConverter.convertNodeToGeometryData(node.model) + if (geometryData) { + const renderData: NodeRenderData = { + speckleType: GeometryConverter.getSpeckleType(node.model), + geometry: geometryData, + batchId: 'n/a', + batchIndexStart: 0, + batchIndexCount: 0 + } + renderView = new NodeRenderView() + renderView.setData(node.model.id, renderData) + } + + node.model.renderView = renderView + return true + }) + } + + /** + * TEMPORARY + */ + public getObjectWrappers() { + const objectWrappers = [] + this.root.walk((node: TreeNode): boolean => { + const renderView: NodeRenderView = node.model.renderView + if (renderView) { + const plm = Object.keys(renderView.renderData) + const renderData: NodeRenderData = renderView.renderData[plm[0]] + switch (renderData.speckleType) { + case SpeckleType.Pointcloud: + objectWrappers.push( + new ObjectWrapper( + Geometry.makePointCloudGeometry(renderData.geometry), + node.model.raw, + 'pointcloud' + ) + ) + break + case SpeckleType.Brep: + objectWrappers.push( + new ObjectWrapper( + Geometry.makeMeshGeometry(renderData.geometry), + node.model.raw + ) + ) + break + case SpeckleType.Mesh: + objectWrappers.push( + new ObjectWrapper( + Geometry.makeMeshGeometry(renderData.geometry), + node.model.raw + ) + ) + break + case SpeckleType.Point: + objectWrappers.push( + new ObjectWrapper( + Geometry.makePointGeometry(renderData.geometry), + node.model.raw, + 'point' + ) + ) + break + case SpeckleType.Line: + objectWrappers.push( + new ObjectWrapper( + Geometry.makeLineGeometry(renderData.geometry), + node.model.raw, + 'line' + ) + ) + break + case SpeckleType.Polyline: + objectWrappers.push( + new ObjectWrapper( + Geometry.makeLineGeometry(renderData.geometry), + node.model.raw, + 'line' + ) + ) + break + case SpeckleType.Box: + objectWrappers.push( + new ObjectWrapper( + Geometry.makeMeshGeometry(renderData.geometry), + node.model.raw + ) + ) + break + case SpeckleType.Polycurve: + objectWrappers.push( + new ObjectWrapper( + Geometry.makeLineGeometry(renderData.geometry), + node.model.raw, + 'line' + ) + ) + break + case SpeckleType.Curve: + objectWrappers.push( + new ObjectWrapper( + Geometry.makeLineGeometry(renderData.geometry), + node.model.raw, + 'line' + ) + ) + break + case SpeckleType.Circle: + objectWrappers.push( + new ObjectWrapper( + Geometry.makeLineGeometry(renderData.geometry), + node.model.raw, + 'line' + ) + ) + break + case SpeckleType.Arc: + objectWrappers.push( + new ObjectWrapper( + Geometry.makeLineGeometry(renderData.geometry), + node.model.raw, + 'line' + ) + ) + break + case SpeckleType.Ellipse: + objectWrappers.push( + new ObjectWrapper( + Geometry.makeLineGeometry(renderData.geometry), + node.model.raw, + 'line' + ) + ) + break + default: + console.warn(`Skipping geometry conversion for ${renderData.speckleType}`) + return null + } + } + return true + }) + return objectWrappers + } +} diff --git a/packages/viewer/src/modules/ViewerObjectLoader.js b/packages/viewer/src/modules/ViewerObjectLoader.js index bac3d2208..0aad3aac1 100644 --- a/packages/viewer/src/modules/ViewerObjectLoader.js +++ b/packages/viewer/src/modules/ViewerObjectLoader.js @@ -1,6 +1,6 @@ import ObjectLoader from '@speckle/objectloader' import Converter from './converter/Converter' -// import { WorldTree } from './converter/WorldTree' +import { WorldTree } from './converter/WorldTree' /** * Helper wrapper around the ObjectLoader class, with some built in assumptions. @@ -68,7 +68,7 @@ export default class ViewerObjectLoader { let total = 0 let viewerLoads = 0 let firstObjectPromise = null - const parsedObjects = [] // Temporary until refactor + let parsedObjects = [] // Temporary until refactor for await (const obj of this.loader.getObjectIterator()) { if (this.cancel) { this.viewer.emit('load-progress', { @@ -107,7 +107,14 @@ export default class ViewerObjectLoader { // Geometry.applyWorldTransform(parsedObjects) // Temporary until refactor - // console.log(WorldTree.getInstance().findAll()) + WorldTree.getRenderTree().buildRenderTree() + // console.log( + WorldTree.getInstance().findAll((node) => { + return node.model.renderView !== null + }) + // ) + parsedObjects = WorldTree.getRenderTree().getObjectWrappers() + for (let k = 0; k < parsedObjects.length; k++) { await this.converter.asyncPause() this.viewer.sceneManager.addObject(parsedObjects[k]) diff --git a/packages/viewer/src/modules/converter/Converter.ts b/packages/viewer/src/modules/converter/Converter.ts index cc44a0868..6b6236369 100644 --- a/packages/viewer/src/modules/converter/Converter.ts +++ b/packages/viewer/src/modules/converter/Converter.ts @@ -26,9 +26,6 @@ export type ConverterNodeDelegate = (object, node) => Promise * Warning: HIC SVNT DRACONES. */ export default class Coverter { - PolylineToNodebind(arg0: this): ConverterNodeDelegate { - throw new Error('Method not implemented.') - } private objectLoader private curveSegmentLength: number private lastAsyncPause: number @@ -169,10 +166,10 @@ export default class Coverter { }) if (node === null) { WorldTree.getInstance().addNode(childNode, node) - console.warn(`Added root node with id ${obj.id}`) + // console.warn(`Added root node with id ${obj.id}`) } else { WorldTree.getInstance().addNode(childNode, node) - console.warn(`Added child node with id ${obj.id} to parent node ${node.model.id}`) + // console.warn(`Added child node with id ${obj.id} to parent node ${node.model.id}`) } // Keep track of parents. An object is his own parent, for the simplicity of working with subtrees @@ -434,9 +431,9 @@ export default class Coverter { children: [] }) WorldTree.getInstance().addNode(childNode, node) - console.warn( - `Added child node with id ${childNode.model.id} to parent node ${node.model.id}` - ) + // console.warn( + // `Added child node with id ${childNode.model.id} to parent node ${node.model.id}` + // ) await this.convertToNode(ref, childNode) } @@ -504,6 +501,7 @@ export default class Coverter { } private async PolycurveToNode(obj, node) { + node.model.raw.segments = [] for (let i = 0; i < obj.segments.length; i++) { let element = obj.segments[i] const nestedNode: TreeNode = WorldTree.getInstance().parse({ @@ -517,6 +515,7 @@ export default class Coverter { } else if ((element = this.getDisplayValue(element)) !== undefined) { await this.convertToNode(element, nestedNode) } + node.model.raw.segments[i] = nestedNode } } @@ -529,6 +528,7 @@ export default class Coverter { geometry: null, children: [] }) + await this.convertToNode(displayValue, nestedNode) node.model.raw.displayValue = nestedNode } diff --git a/packages/viewer/src/modules/converter/GeometryConverter.ts b/packages/viewer/src/modules/converter/GeometryConverter.ts new file mode 100644 index 000000000..d8ab455a2 --- /dev/null +++ b/packages/viewer/src/modules/converter/GeometryConverter.ts @@ -0,0 +1,545 @@ +import { + BoxBufferGeometry, + EllipseCurve, + Line3, + Matrix4, + Vector2, + Vector3 +} from 'three' +import { Geometry, GeometryData } from './Geometry' +import MeshTriangulationHelper from './MeshTriangulationHelper' +import { getConversionFactor } from './Units' +import { NodeData } from './WorldTree' + +export enum SpeckleType { + View3D = 'View3D', + BlockInstance = 'BlockInstance', + Pointcloud = 'Pointcloud', + Brep = 'Brep', + Mesh = 'Mesh', + Point = 'Point', + Line = 'Line', + Polyline = 'Polyline', + Box = 'Box', + Polycurve = 'Polycurve', + Curve = 'Curve', + Circle = 'Circle', + Arc = 'Arc', + Ellipse = 'Ellipse', + Unknown = 'Unknown' +} + +export class GeometryConverter { + public static getSpeckleType(node: NodeData): SpeckleType { + let type = 'Base' + if (node.raw.data) + type = node.raw.data.speckle_type + ? node.raw.data.speckle_type.split('.').reverse()[0] + : type + else + type = node.raw.speckle_type + ? node.raw.speckle_type.split('.').reverse()[0] + : type + if (type in SpeckleType) return type as SpeckleType + else return SpeckleType.Unknown + } + + public static convertNodeToGeometryData(node: NodeData): GeometryData { + const type = GeometryConverter.getSpeckleType(node) + switch (type) { + case SpeckleType.Pointcloud: + return GeometryConverter.PointcloudToGeometryData(node) + case SpeckleType.Brep: + return GeometryConverter.BrepToGeometryData(node) + case SpeckleType.Mesh: + return GeometryConverter.MeshToGeometryData(node) + case SpeckleType.Point: + return GeometryConverter.PointToGeometryData(node) + case SpeckleType.Line: + return GeometryConverter.LineToGeometryData(node) + case SpeckleType.Polyline: + return GeometryConverter.PolylineToGeometryData(node) + case SpeckleType.Box: + return GeometryConverter.BoxToGeometryData(node) + case SpeckleType.Polycurve: + return GeometryConverter.PolycurveToGeometryData(node) + case SpeckleType.Curve: + return GeometryConverter.CurveToGeometryData(node) + case SpeckleType.Circle: + return GeometryConverter.CircleToGeometryData(node) + case SpeckleType.Arc: + return GeometryConverter.ArcToGeometryData(node) + case SpeckleType.Ellipse: + return GeometryConverter.EllipseToGeometryData(node) + default: + console.warn(`Skipping geometry conversion for ${type}`) + return null + } + } + /** + * POINT CLOUD + */ + private static PointcloudToGeometryData(node: NodeData) { + const conversionFactor = getConversionFactor(node.raw.units) + + const vertices = node.raw.points + const colorsRaw = node.raw.colors + let colors = null + + if (colorsRaw && colorsRaw.length !== 0) { + if (colorsRaw.length !== vertices.length / 3) { + console.warn( + `Mesh (id ${node.raw.id}) colours are mismatched with vertice counts. The number of colours must equal the number of vertices.` + ) + } + colors = GeometryConverter.unpackColors(colorsRaw) + } + return { + attributes: { + POSITION: vertices, + COLOR: colors + }, + bakeTransform: new Matrix4().makeScale( + conversionFactor, + conversionFactor, + conversionFactor + ), + transform: null + } as GeometryData + } + + /** + * BREP + */ + private static BrepToGeometryData(node: NodeData) { + return this.MeshToGeometryData(node.raw.displaValue) + } + + /** + * MESH + */ + private static MeshToGeometryData(node: NodeData): GeometryData { + if (!node.raw) return + + const conversionFactor = getConversionFactor(node.raw.units) + // const buffer = new BufferGeometry() + const indices = [] + + if (!node.raw.vertices) return + if (!node.raw.faces) return + + const vertices = node.raw.vertices + const faces = node.raw.faces + const colorsRaw = node.raw.colors + let colors = null + + let k = 0 + while (k < faces.length) { + let n = faces[k] + if (n <= 3) n += 3 // 0 -> 3, 1 -> 4 + + if (n === 3) { + // Triangle face + indices.push(faces[k + 1], faces[k + 2], faces[k + 3]) + } else { + // Quad or N-gon face + const triangulation = MeshTriangulationHelper.triangulateFace( + k, + faces, + vertices + ) + indices.push( + ...triangulation.filter((el) => { + return el !== undefined + }) + ) + } + + k += n + 1 + } + + if (colorsRaw && colorsRaw.length !== 0) { + if (colorsRaw.length !== vertices.length / 3) { + console.warn( + `Mesh (id ${node.raw.id}) colours are mismatched with vertice counts. The number of colours must equal the number of vertices.` + ) + } + colors = GeometryConverter.unpackColors(colorsRaw) + } + + return { + attributes: { + POSITION: vertices, + INDEX: indices, + COLOR: colors + }, + bakeTransform: new Matrix4().makeScale( + conversionFactor, + conversionFactor, + conversionFactor + ), + transform: null + } as GeometryData + } + + /** + * POINT + */ + private static PointToGeometryData(node: NodeData): GeometryData { + const conversionFactor = getConversionFactor(node.raw.units) + return { + attributes: { + POSITION: this.PointToFloatArray(node.raw) + }, + bakeTransform: new Matrix4().makeScale( + conversionFactor, + conversionFactor, + conversionFactor + ), + transform: null + } as GeometryData + } + + /** + * LINE + */ + private static LineToGeometryData(node: NodeData): GeometryData { + const conversionFactor = getConversionFactor(node.raw.units) + return { + attributes: { + POSITION: this.PointToFloatArray(node.raw.start).concat( + this.PointToFloatArray(node.raw.end) + ) + }, + bakeTransform: new Matrix4().makeScale( + conversionFactor, + conversionFactor, + conversionFactor + ), + transform: null + } as GeometryData + } + + /** + * POLYLINE + */ + private static PolylineToGeometryData(node: NodeData): GeometryData { + const conversionFactor = getConversionFactor(node.raw.units) + + if (node.raw.closed) + node.raw.value.push(node.raw.value[0], node.raw.value[1], node.raw.value[2]) + return { + attributes: { + POSITION: node.raw.value + }, + bakeTransform: new Matrix4().makeScale( + conversionFactor, + conversionFactor, + conversionFactor + ), + transform: null + } as GeometryData + } + + /** + * BOX + */ + private static BoxToGeometryData(node: NodeData) { + /** + * 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 + * but it's too late in the evenning atm... + */ + const conversionFactor = getConversionFactor(node.raw.units) + + const move = this.PointToVector3(node.raw.basePlane.origin) + const width = (node.raw.xSize.end - node.raw.xSize.start) * conversionFactor + const depth = (node.raw.ySize.end - node.raw.ySize.start) * conversionFactor + const height = (node.raw.zSize.end - node.raw.zSize.start) * conversionFactor + + const box = new BoxBufferGeometry(width, depth, height, 1, 1, 1) + return { + attributes: { + POSITION: box.attributes.position.array, + INDEX: box.index.array + }, + bakeTransform: new Matrix4().setPosition(move), + transform: null + } as GeometryData + } + + /** + * POLYCURVE + */ + private static PolycurveToGeometryData(node: NodeData): GeometryData { + const buffers = [] + for (let i = 0; i < node.raw.segments.length; i++) { + const element = node.raw.segments[i] + const conv = GeometryConverter.convertNodeToGeometryData(element.model) + buffers.push(conv) + } + return Geometry.mergeGeometryData(buffers) + } + + /** + * CURVE + */ + private static CurveToGeometryData(node: NodeData) { + return this.PolylineToGeometryData(node.raw.displayValue.model) + } + + /** + * CIRCLE + */ + private static CircleToGeometryData(node: NodeData) { + const conversionFactor = getConversionFactor(node.raw.units) + const curveSegmentLength = 0.1 + const points = this.getCircularCurvePoints( + node.raw.plane, + node.raw.radius * conversionFactor, + curveSegmentLength + ) + return { + attributes: { + POSITION: this.FlattenVector3Array(points) + }, + bakeTransform: null, + transform: null + } as GeometryData + } + + /** + * ARC + */ + private static ArcToGeometryData(node: NodeData) { + const origin = new Vector3( + node.raw.plane.origin.x, + node.raw.plane.origin.y, + node.raw.plane.origin.z + ) + const startPoint = new Vector3( + node.raw.startPoint.x, + node.raw.startPoint.y, + node.raw.startPoint.z + ) + const endPoint = new Vector3( + node.raw.endPoint.x, + node.raw.endPoint.y, + node.raw.endPoint.z + ) + const midPoint = new Vector3( + node.raw.midPoint.x, + node.raw.midPoint.y, + node.raw.midPoint.z + ) + + const chord = new Line3(startPoint, endPoint) + // This the projection of the origin on the chord + const chordCenter = chord.getCenter(new Vector3()) + // Direction from the origin to the mid point + const d0 = new Vector3().subVectors(midPoint, origin) + d0.normalize() + // Direction from the origin to it;s projection on the chord + const d1 = new Vector3().subVectors(chordCenter, origin) + d1.normalize() + // If the two above directions point in opposite directions, we need to reverse the arc's winding order + const _clockwise = d0.dot(d1) < 0 + + // Here we compute arc's orthonormal basis vectors using the origin and the two end points. + const v0 = new Vector3().subVectors(startPoint, origin) + v0.normalize() + const v1 = new Vector3().subVectors(endPoint, origin) + v1.normalize() + const v2 = new Vector3().crossVectors(v0, v1) + v2.normalize() + const v3 = new Vector3().crossVectors(v2, v0) + v3.normalize() + /** + * We clamp the dot value to [-1,1] since that's the domain acos is defined on. Normally dot won't return + * values outside that interval, but due to floating point precision, you sometimes get -1.0000000004, which + * makes acos return NaN + */ + const dot = Math.min(Math.max(v0.dot(v1), -1), 1) + // This is just the angle between the start and end points. Should be same as obj.angleRadians(or something) + const angle = Math.acos(dot) + const radius = node.raw.radius + // We draw the arc in a local un-rotated coordinate system. We rotate it later on via transformation + const curve = new EllipseCurve( + 0, + 0, // ax, aY + radius, + radius, // xRadius, yRadius + 0, + angle, // aStartAngle, aEndAngle + _clockwise, // aClockwise + 0 // aRotation + ) + // This just samples points along the arc curve + const points = curve.getPoints(50) + + const matrix = new Matrix4() + // Scale first, in order for the composition to work correctly + const conversionFactor = getConversionFactor(node.raw.plane.units) + // We determine the orientation of the plane using the three basis vectors computed above + const R = new Matrix4().makeBasis(v0, v3, v2) + // We translate it to the circle's origin (considering the origin's scaling as aswell ) + const T = new Matrix4().setPosition(origin.multiplyScalar(conversionFactor)) + + matrix.multiply(T).multiply(R) + + // if (scale) { + const S = new Matrix4().scale( + new Vector3(conversionFactor, conversionFactor, conversionFactor) + ) + matrix.multiply(S) + // } + + return { + attributes: { + POSITION: this.FlattenVector3Array(points) + }, + bakeTransform: matrix, + transform: null + } as GeometryData + } + + /** + * ELLIPSE + */ + private static EllipseToGeometryData(node: NodeData) { + const conversionFactor = getConversionFactor(node.raw.units) + + const center = new Vector3( + node.raw.plane.origin.x, + node.raw.plane.origin.y, + node.raw.plane.origin.z + ).multiplyScalar(conversionFactor) + const xAxis = new Vector3( + node.raw.plane.xdir.x, + node.raw.plane.xdir.y, + node.raw.plane.xdir.z + ).normalize() + const yAxis = new Vector3( + node.raw.plane.ydir.x, + node.raw.plane.ydir.y, + node.raw.plane.ydir.z + ).normalize() + + let resolution = 2 * Math.PI * node.raw.firstRadius * conversionFactor * 10 + resolution = parseInt(resolution.toString()) + const points = [] + + for (let index = 0; index <= resolution; index++) { + const t = (index * Math.PI * 2) / resolution + const x = Math.cos(t) * node.raw.firstRadius * conversionFactor + const y = Math.sin(t) * node.raw.secondRadius * conversionFactor + const xMove = new Vector3(xAxis.x * x, xAxis.y * x, xAxis.z * x) + const yMove = new Vector3(yAxis.x * y, yAxis.y * y, yAxis.z * y) + + const pt = new Vector3().addVectors(xMove, yMove).add(center) + points.push(pt) + } + + return { + attributes: { + POSITION: this.FlattenVector3Array(points) + }, + bakeTransform: null, + transform: null + } as GeometryData + } + + /** + * UTILS + */ + + private static getCircularCurvePoints( + plane, + radius, + startAngle = 0, + endAngle = 2 * Math.PI, + res = 0.1 + ) { + // Get alignment vectors + const center = this.PointToVector3(plane.origin) + const xAxis = this.PointToVector3(plane.xdir) + const yAxis = this.PointToVector3(plane.ydir) + + // Make sure plane axis are unit length!!!! + xAxis.normalize() + yAxis.normalize() + + // Determine resolution + let resolution = ((endAngle - startAngle) * radius) / res + resolution = parseInt(resolution.toString()) + + const points = [] + + for (let index = 0; index <= resolution; index++) { + const t = startAngle + (index * (endAngle - startAngle)) / resolution + const x = Math.cos(t) * radius + const y = Math.sin(t) * radius + const xMove = new Vector3(xAxis.x * x, xAxis.y * x, xAxis.z * x) + const yMove = new Vector3(yAxis.x * y, yAxis.y * y, yAxis.z * y) + + const pt = new Vector3().addVectors(xMove, yMove).add(center) + points.push(pt) + } + return points + } + + private static PointToVector3(obj, scale = true) { + const conversionFactor = scale ? getConversionFactor(obj.units) : 1 + let v = null + if (obj.value) { + // Old point format based on value list + v = new Vector3( + obj.value[0] * conversionFactor, + obj.value[1] * conversionFactor, + obj.value[2] * conversionFactor + ) + } else { + // New point format based on cartesian coords + v = new Vector3( + obj.x * conversionFactor, + obj.y * conversionFactor, + obj.z * conversionFactor + ) + } + return v + } + + private static PointToFloatArray(obj) { + if (obj.value) { + return [obj.value[0], obj.value[1], obj.value[2]] + } else { + return [obj.x, obj.y, obj.z] + } + } + + private static FlattenVector3Array(input: Vector3[] | Vector2[]): number[] { + const output = new Array(input.length * 3) + const vBuff = [] + for (let k = 0, l = 0; k < input.length; k++, l += 3) { + input[k].toArray(vBuff) + output[l] = vBuff[0] + output[l + 1] = vBuff[1] + output[l + 2] = vBuff[2] ? vBuff[2] : 0 + } + return output + } + + private static unpackColors(int32Colors: number[]): number[] { + const colors = new Array(int32Colors.length * 3) + for (let i = 0; i < int32Colors.length; i++) { + const color = int32Colors[i] + const r = (color >> 16) & 0xff + const g = (color >> 8) & 0xff + const b = color & 0xff + colors[i * 3] = r / 255 + colors[i * 3 + 1] = g / 255 + colors[i * 3 + 2] = b / 255 + } + return colors + } +} diff --git a/packages/viewer/src/modules/converter/WorldTree.ts b/packages/viewer/src/modules/converter/WorldTree.ts index f3ff7df8c..d67f37642 100644 --- a/packages/viewer/src/modules/converter/WorldTree.ts +++ b/packages/viewer/src/modules/converter/WorldTree.ts @@ -1,17 +1,19 @@ import TreeModel from 'tree-model' -import { GeometryData } from './Geometry' -import { Node } from 'tree-model' +import { NodeRenderView } from '../NodeRenderView' +import { RenderTree } from '../RenderTree' export type TreeNode = TreeModel.Node +export type SearchPredicate = (node: TreeNode) => boolean export interface NodeData { // eslint-disable-next-line @typescript-eslint/no-explicit-any raw: { [prop: string]: any } - geometry: GeometryData + renderView?: NodeRenderView } export class WorldTree { private static instance: WorldTree + private static renderTreeInstance: RenderTree private constructor() { this.tree = new TreeModel() @@ -25,10 +27,22 @@ export class WorldTree { return WorldTree.instance } - private tree: TreeModel - private _root: TreeModel.Node + public static getRenderTree(): RenderTree { + if (!WorldTree.getInstance()._root) { + console.error(`WorldTree not initialised`) + return null + } + if (!WorldTree.renderTreeInstance) { + WorldTree.renderTreeInstance = new RenderTree(WorldTree.getInstance()._root) + } - public get root(): TreeModel.Node { + return WorldTree.renderTreeInstance + } + + private tree: TreeModel + private _root: TreeNode + + public get root(): TreeNode { return this._root } @@ -44,10 +58,7 @@ export class WorldTree { parent.addChild(node) } - public findAll() { - return this.root.all((node: Node) => { - // const type = node.model.raw.speckle_type.split('.').reverse()[0] - return node.model.raw.displayValue !== undefined //type === 'Polyline' - }) + public findAll(predicate: SearchPredicate, node?: TreeNode): Array { + return (node ? node : this.root).all(predicate) } }