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
This commit is contained in:
@@ -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!
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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])
|
||||
|
||||
@@ -26,9 +26,6 @@ export type ConverterNodeDelegate = (object, node) => Promise<void>
|
||||
* 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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<number>(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
|
||||
}
|
||||
}
|
||||
@@ -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<NodeData>
|
||||
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<NodeData>
|
||||
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<NodeData> {
|
||||
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<NodeData>) => {
|
||||
// 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<TreeNode> {
|
||||
return (node ? node : this.root).all(predicate)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user