Text support and limited Dimensions support (#1604)
* Font loading (hidden under a .png for now). Simple experiments with rendering text * Added some text rendering tests. WIP on billboarding * Added tests using troika-text-three library which uses sdf font textures instead of generating text geometry * WIP on supporting speckle text objects * Some more work on speckle text object integration. Worked on text materials, text batch and text object. We're now rendering basic text * We're now rendering the text for various dimension types. Changed text to use display style first, since that's the one that contains it's color. * Implemented text selection. Text can now be selected and works the same way every other speckle objects works when selecting * Implemented the rest of the filtering features for text. * Batch building can now be async. Building text batches needs to be async because that's how the troika libary works. Fixed an issue with the section box not clippin text properly * Removed dummy text geometry data from it's RenderData. Enabled render views with metadata to count as valid. getRenderableRenderViews now returns nodes with metadata as well. Text now supports proper unit conversion. Text now is being displayed correctly(-ish) when inside blocks. Autocad text is now being displayed
This commit is contained in:
committed by
GitHub
parent
5361d64e99
commit
b3d8c5d28c
@@ -377,7 +377,8 @@ export default class Sandbox {
|
||||
title: 'Screenshot'
|
||||
})
|
||||
screenshot.on('click', async () => {
|
||||
console.warn(await this.viewer.screenshot())
|
||||
// console.warn(await this.viewer.screenshot())
|
||||
this.viewer.getRenderer().adnotate()
|
||||
})
|
||||
|
||||
const rotate = this.tabs.pages[0].addButton({
|
||||
@@ -882,7 +883,7 @@ export default class Sandbox {
|
||||
filteringFolder.addInput(this.filterParams, 'filterBy', {
|
||||
options: {
|
||||
Volume: 'parameters.HOST_VOLUME_COMPUTED.value',
|
||||
Area: 'parameters.HOST_AREA_COMPUTED.value',
|
||||
Area: 'area',
|
||||
Elevation: 'Elevation',
|
||||
SpeckleType: 'speckle_type',
|
||||
DisplayName: 'DisplayName',
|
||||
|
||||
@@ -109,7 +109,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/6c6e43e5f3'
|
||||
// 'https://latest.speckle.dev/streams/58b5648c4d/commits/60371ecb2d'
|
||||
// 'Super' heavy revit shit
|
||||
@@ -263,10 +263,15 @@ const getStream = () => {
|
||||
// 'https://speckle.xyz/streams/be0f962efb/objects/37639741c363a123100eda8044f2fe3f'
|
||||
// 'https://latest.speckle.dev/streams/92b620fb17/objects/a4e2fad01e69cd886ecbfedf221f5301'
|
||||
// 'https://latest.speckle.dev/streams/3f895e614f/commits/7e16d2ab71'
|
||||
// 'https://latest.speckle.dev/streams/55cc1cbf0a/commits/aa72674507'
|
||||
// 'https://latest.speckle.dev/streams/55cc1cbf0a/objects/44aa4bad23591f90484a9a63814b9dc9'
|
||||
// 'https://latest.speckle.dev/streams/55cc1cbf0a/objects/3a21694b533826cf551d4e2ff9963397'
|
||||
// 'https://latest.speckle.dev/streams/55cc1cbf0a/commits/a7f74b6524'
|
||||
// 'https://latest.speckle.dev/streams/c1faab5c62/objects/d3466547df9df86397eb4dff7ac9713f'
|
||||
// 'https://latest.speckle.dev/streams/c1faab5c62/commits/140c443886'
|
||||
// 'https://latest.speckle.dev/streams/e258b0e8db/commits/108971810d'
|
||||
'https://latest.speckle.dev/streams/e258b0e8db/objects/3fcd63d80cf791c3f554a795846e62f6'
|
||||
// 'https://latest.speckle.dev/streams/55cc1cbf0a/objects/d7ae178fb6a7b1f599a177486e14f9a6'
|
||||
// 'https://latest.speckle.dev/streams/e258b0e8db/objects/3fcd63d80cf791c3f554a795846e62f6'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@
|
||||
"string-to-color": "^2.2.2",
|
||||
"three": "^0.140.0",
|
||||
"three-mesh-bvh": "0.5.17",
|
||||
"tree-model": "1.0.7"
|
||||
"tree-model": "1.0.7",
|
||||
"troika-three-text": "0.47.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.2",
|
||||
|
||||
@@ -19,7 +19,8 @@ export interface ViewerParams {
|
||||
export enum AssetType {
|
||||
TEXTURE_8BPP = 'png', // For now
|
||||
TEXTURE_HDR = 'hdr',
|
||||
TEXTURE_EXR = 'exr'
|
||||
TEXTURE_EXR = 'exr',
|
||||
FONT_JSON = 'font-json'
|
||||
}
|
||||
|
||||
export interface Asset {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,11 +8,12 @@ import {
|
||||
} from 'three'
|
||||
import { EXRLoader } from 'three/examples/jsm/loaders/EXRLoader.js'
|
||||
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js'
|
||||
import { FontLoader, Font } from 'three/examples/jsm/loaders/FontLoader.js'
|
||||
import { Asset, AssetType } from '../IViewer'
|
||||
import Logger from 'js-logger'
|
||||
|
||||
export class Assets {
|
||||
private static _cache: { [name: string]: Texture } = {}
|
||||
private static _cache: { [name: string]: Texture | Font } = {}
|
||||
|
||||
private static getLoader(src: string, assetType: AssetType): TextureLoader {
|
||||
if (assetType === undefined) assetType = src.split('.').pop() as AssetType
|
||||
@@ -43,7 +44,7 @@ export class Assets {
|
||||
srcUrl = asset as string
|
||||
}
|
||||
if (this._cache[srcUrl]) {
|
||||
return Promise.resolve(this._cache[srcUrl])
|
||||
return Promise.resolve(this._cache[srcUrl] as Texture)
|
||||
}
|
||||
|
||||
return new Promise<Texture>((resolve, reject) => {
|
||||
@@ -58,7 +59,7 @@ export class Assets {
|
||||
this._cache[srcUrl] = pmremRT.texture
|
||||
texture.dispose()
|
||||
generator.dispose()
|
||||
resolve(this._cache[srcUrl])
|
||||
resolve(this._cache[srcUrl] as Texture)
|
||||
},
|
||||
undefined,
|
||||
(error: ErrorEvent) => {
|
||||
@@ -83,7 +84,7 @@ export class Assets {
|
||||
}
|
||||
|
||||
if (this._cache[srcUrl]) {
|
||||
return Promise.resolve(this._cache[srcUrl])
|
||||
return Promise.resolve(this._cache[srcUrl] as Texture)
|
||||
}
|
||||
return new Promise<Texture>((resolve, reject) => {
|
||||
// Hack to load 'data:image's - for some reason, the frontend receives the default
|
||||
@@ -107,7 +108,7 @@ export class Assets {
|
||||
srcUrl,
|
||||
(texture) => {
|
||||
this._cache[srcUrl] = texture
|
||||
resolve(this._cache[srcUrl])
|
||||
resolve(this._cache[srcUrl] as Texture)
|
||||
},
|
||||
undefined,
|
||||
(error: ErrorEvent) => {
|
||||
@@ -121,6 +122,32 @@ export class Assets {
|
||||
})
|
||||
}
|
||||
|
||||
public static getFont(asset: Asset | string): Promise<Font> {
|
||||
let srcUrl: string = null
|
||||
if ((<Asset>asset).src) {
|
||||
srcUrl = (asset as Asset).src
|
||||
} else {
|
||||
srcUrl = asset as string
|
||||
}
|
||||
|
||||
if (this._cache[srcUrl]) {
|
||||
return Promise.resolve(this._cache[srcUrl] as Font)
|
||||
}
|
||||
|
||||
return new Promise<Font>((resolve, reject) => {
|
||||
new FontLoader().load(
|
||||
srcUrl,
|
||||
(font: Font) => {
|
||||
resolve(font)
|
||||
},
|
||||
undefined,
|
||||
(error: ErrorEvent) => {
|
||||
reject(`Loading asset ${srcUrl} failed ${error.message}`)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/** To be used wisely */
|
||||
public static async getTextureData(asset: Asset | string): Promise<ImageData> {
|
||||
const texture = await Assets.getTexture(asset)
|
||||
|
||||
@@ -65,6 +65,7 @@ export enum ObjectLayers {
|
||||
STREAM_CONTENT_MESH = 10,
|
||||
STREAM_CONTENT_LINE = 11,
|
||||
STREAM_CONTENT_POINT = 12,
|
||||
STREAM_CONTENT_TEXT = 13,
|
||||
|
||||
STREAM_CONTENT = 1,
|
||||
PROPS = 2,
|
||||
@@ -1420,4 +1421,34 @@ export default class SpeckleRenderer {
|
||||
public markTransformsDirty(batchId: string) {
|
||||
;(this.batcher.batches[batchId] as MeshBatch).mesh.transformsDirty = true
|
||||
}
|
||||
|
||||
public async adnotate() {
|
||||
// const batches: Batch[] = Object.values(this.batcher.batches)
|
||||
// let accumulator = 0
|
||||
// let tris = 0
|
||||
// let count = 0
|
||||
// for (let k = 0; k < batches.length; k++) {
|
||||
// const rvs = batches[k].renderViews
|
||||
// for (let i = 0; i < rvs.length; i++) {
|
||||
// const speckleObject = this.viewer.getWorldTree().findId(rvs[i].renderData.id)
|
||||
// .model.raw
|
||||
// const text = new SpeckleText()
|
||||
// const start = performance.now()
|
||||
// await text.setText(speckleObject.speckle_type.split('.').reverse()[0], 1)
|
||||
// tris += text.triCount
|
||||
// accumulator += performance.now() - start
|
||||
// const objSize = rvs[i].aabb.getSize(new Vector3())
|
||||
// const objCenter = rvs[i].aabb.getCenter(new Vector3())
|
||||
// text.setPosition(
|
||||
// new Vector3(objCenter.x, objCenter.y + objSize.y * 0.5, objCenter.z)
|
||||
// )
|
||||
// this.rootGroup.add(text.text)
|
||||
// this.rootGroup.add(text.background)
|
||||
// count++
|
||||
// }
|
||||
// }
|
||||
// console.warn(accumulator)
|
||||
// console.warn(tris / 3)
|
||||
// console.warn(count)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ export enum GeometryType {
|
||||
MESH,
|
||||
LINE,
|
||||
POINT,
|
||||
POINT_CLOUD
|
||||
POINT_CLOUD,
|
||||
TEXT
|
||||
}
|
||||
|
||||
export interface Batch {
|
||||
|
||||
@@ -19,6 +19,7 @@ import Logger from 'js-logger'
|
||||
import { World } from '../World'
|
||||
import { RenderTree } from '../tree/RenderTree'
|
||||
import SpeckleMesh from '../objects/SpeckleMesh'
|
||||
import TextBatch from './TextBatch'
|
||||
|
||||
export enum TransformStorage {
|
||||
VERTEX_TEXTURE = 0,
|
||||
@@ -43,7 +44,7 @@ export default class Batcher {
|
||||
this.materials.createDefaultMaterials()
|
||||
}
|
||||
|
||||
public makeBatches(
|
||||
public async makeBatches(
|
||||
renderTree: RenderTree,
|
||||
speckleType: SpeckleType[],
|
||||
batchType?: GeometryType
|
||||
@@ -72,12 +73,12 @@ export default class Batcher {
|
||||
if (valid) {
|
||||
vertCount += value.renderData.geometry.attributes.POSITION.length / 3
|
||||
}
|
||||
return valid
|
||||
return valid || value.hasMetadata
|
||||
})
|
||||
const batches = this.splitBatch(renderViewsBatch, vertCount)
|
||||
for (let k = 0; k < batches.length; k++) {
|
||||
const restrictedRvs = batches[k]
|
||||
const batch = this.buildBatch(
|
||||
const batch = await this.buildBatch(
|
||||
renderTree,
|
||||
restrictedRvs,
|
||||
materialHashes[i],
|
||||
@@ -123,12 +124,12 @@ export default class Batcher {
|
||||
if (valid) {
|
||||
vertCount += value.renderData.geometry.attributes.POSITION.length / 3
|
||||
}
|
||||
return valid
|
||||
return valid || value.hasMetadata
|
||||
})
|
||||
const batches = this.splitBatch(renderViewsBatch, vertCount)
|
||||
for (let k = 0; k < batches.length; k++) {
|
||||
const restrictedRvs = batches[k]
|
||||
const batch = this.buildBatch(
|
||||
const batch = await this.buildBatch(
|
||||
renderTree,
|
||||
restrictedRvs,
|
||||
materialHashes[i],
|
||||
@@ -202,15 +203,15 @@ export default class Batcher {
|
||||
return vSplit
|
||||
}
|
||||
|
||||
private buildBatch(
|
||||
private async buildBatch(
|
||||
renderTree: RenderTree,
|
||||
renderViews: NodeRenderView[],
|
||||
materialHash: number,
|
||||
batchType?: GeometryType
|
||||
): Batch {
|
||||
let batch = renderViews.filter((value) => value.renderMaterialHash === materialHash)
|
||||
/** Prune any meshes with no geometry data */
|
||||
batch = batch.filter((value) => value.validGeometry)
|
||||
): Promise<Batch> {
|
||||
const batch = renderViews.filter(
|
||||
(value) => value.renderMaterialHash === materialHash
|
||||
)
|
||||
|
||||
if (!batch.length) {
|
||||
/** This is for the case when all renderviews have invalid geometries, and it generally
|
||||
@@ -234,6 +235,8 @@ export default class Batcher {
|
||||
matRef = renderViews[0].renderData.renderMaterial
|
||||
} else if (geometryType === GeometryType.POINT_CLOUD) {
|
||||
matRef = renderViews[0].renderData.renderMaterial
|
||||
} else if (geometryType === GeometryType.TEXT) {
|
||||
matRef = renderViews[0].renderData.displayStyle
|
||||
}
|
||||
|
||||
const material = this.materials.getMaterial(materialHash, matRef, geometryType)
|
||||
@@ -260,10 +263,13 @@ export default class Batcher {
|
||||
case GeometryType.POINT_CLOUD:
|
||||
geometryBatch = new PointBatch(batchID, renderTree.id, batch)
|
||||
break
|
||||
case GeometryType.TEXT:
|
||||
geometryBatch = new TextBatch(batchID, renderTree.id, batch)
|
||||
break
|
||||
}
|
||||
|
||||
geometryBatch.setBatchMaterial(material)
|
||||
geometryBatch.buildBatch()
|
||||
await geometryBatch.buildBatch()
|
||||
|
||||
return geometryBatch
|
||||
}
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { Box3, Material, Object3D, WebGLRenderer } from 'three'
|
||||
|
||||
import { NodeRenderView } from '../tree/NodeRenderView'
|
||||
import { AllBatchUpdateRange, Batch, BatchUpdateRange, GeometryType } from './Batch'
|
||||
|
||||
import { SpeckleText } from '../objects/SpeckleText'
|
||||
import { GlyphGeometry } from 'troika-three-text'
|
||||
import { ObjectLayers } from '../SpeckleRenderer'
|
||||
|
||||
export default class TextBatch implements Batch {
|
||||
public id: string
|
||||
public subtreeId: string
|
||||
public renderViews: NodeRenderView[]
|
||||
private geometry: GlyphGeometry
|
||||
public batchMaterial: Material
|
||||
public mesh: SpeckleText
|
||||
|
||||
public get bounds(): Box3 {
|
||||
return new Box3() //this.mesh.BVH.getBoundingBox(new Box3())
|
||||
}
|
||||
|
||||
public constructor(id: string, subtreeId: string, renderViews: NodeRenderView[]) {
|
||||
this.id = id
|
||||
this.subtreeId = subtreeId
|
||||
this.renderViews = renderViews
|
||||
}
|
||||
|
||||
public get geometryType(): GeometryType {
|
||||
return GeometryType.TEXT
|
||||
}
|
||||
|
||||
public get renderObject(): Object3D {
|
||||
return this.mesh
|
||||
}
|
||||
|
||||
public getCount(): number {
|
||||
return this.geometry.index.count
|
||||
}
|
||||
|
||||
public setBatchMaterial(material: Material) {
|
||||
this.batchMaterial = material
|
||||
}
|
||||
|
||||
public onUpdate(deltaTime: number) {
|
||||
deltaTime
|
||||
}
|
||||
|
||||
public onRender(renderer: WebGLRenderer) {
|
||||
renderer
|
||||
}
|
||||
|
||||
public setVisibleRange(...ranges: BatchUpdateRange[]) {}
|
||||
|
||||
public getVisibleRange(): BatchUpdateRange {
|
||||
return AllBatchUpdateRange
|
||||
}
|
||||
|
||||
public setDrawRanges(...ranges: BatchUpdateRange[]) {
|
||||
this.mesh.textMesh.material = ranges[0].material
|
||||
if (ranges[0].materialOptions && ranges[0].materialOptions.rampIndexColor) {
|
||||
this.mesh.textMesh.material.color.copy(ranges[0].materialOptions.rampIndexColor)
|
||||
}
|
||||
}
|
||||
|
||||
public autoFillDrawRanges() {}
|
||||
|
||||
public resetDrawRanges() {
|
||||
this.mesh.textMesh.material = this.batchMaterial
|
||||
this.mesh.textMesh.visible = true
|
||||
// this.geometry.clearGroups()
|
||||
// this.geometry.setDrawRange(0, Infinity)
|
||||
}
|
||||
|
||||
public async buildBatch() {
|
||||
this.mesh = new SpeckleText(this.id)
|
||||
this.mesh.matrixAutoUpdate = false
|
||||
await this.mesh.update(
|
||||
SpeckleText.SpeckleTextParamsFromMetadata(
|
||||
this.renderViews[0].renderData.geometry.metaData
|
||||
)
|
||||
)
|
||||
this.mesh.matrix.copy(this.renderViews[0].renderData.geometry.bakeTransform)
|
||||
this.renderViews[0].setBatchData(
|
||||
this.id,
|
||||
0,
|
||||
this.mesh.textMesh.geometry.index.length / 3
|
||||
)
|
||||
this.mesh.textMesh.material = this.batchMaterial
|
||||
this.mesh.layers.set(ObjectLayers.STREAM_CONTENT_TEXT)
|
||||
this.mesh.textMesh.layers.set(ObjectLayers.STREAM_CONTENT_TEXT)
|
||||
}
|
||||
|
||||
public getRenderView(index: number): NodeRenderView {
|
||||
return this.renderViews[0]
|
||||
}
|
||||
|
||||
public getMaterialAtIndex(index: number): Material {
|
||||
index
|
||||
console.warn('Deprecated! Do not call this anymore')
|
||||
return null
|
||||
}
|
||||
|
||||
public purge() {
|
||||
this.renderViews.length = 0
|
||||
this.geometry.dispose()
|
||||
this.batchMaterial.dispose()
|
||||
this.mesh = null
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,9 @@ export default class Coverter {
|
||||
Circle: this.CircleToNode.bind(this),
|
||||
Arc: this.ArcToNode.bind(this),
|
||||
Ellipse: this.EllipseToNode.bind(this),
|
||||
RevitInstance: this.RevitInstanceToNode.bind(this)
|
||||
RevitInstance: this.RevitInstanceToNode.bind(this),
|
||||
Text: this.TextToNode.bind(this),
|
||||
Dimension: this.DimensionToNode.bind(this)
|
||||
}
|
||||
|
||||
constructor(objectLoader: unknown, tree: WorldTree) {
|
||||
@@ -487,6 +489,53 @@ export default class Coverter {
|
||||
node.model.raw.colors = await this.dechunk(obj.colors)
|
||||
}
|
||||
|
||||
private async TextToNode(obj, node) {
|
||||
return
|
||||
}
|
||||
|
||||
private async DimensionToNode(obj, node) {
|
||||
const displayValues = [...this.getDisplayValue(obj)]
|
||||
for (const displayValue of displayValues) {
|
||||
const childNode: TreeNode = this.tree.parse({
|
||||
id: this.getNodeId(displayValue),
|
||||
raw: Object.assign({}, displayValue),
|
||||
atomic: false,
|
||||
children: []
|
||||
})
|
||||
this.tree.addNode(childNode, node)
|
||||
await this.convertToNode(displayValue, childNode)
|
||||
}
|
||||
/**
|
||||
* YOLO
|
||||
* - Dimensions of all types do not have information about text size
|
||||
* - Positioning of the text is not consistent across dimension types
|
||||
* - Angular Dimensions are broken
|
||||
*/
|
||||
const textObj = JSON.parse(JSON.stringify(obj))
|
||||
textObj.plane = textObj.RhinoProps.plane
|
||||
const derivedType = this.getSpeckleTypeChain(textObj)[0]
|
||||
switch (derivedType) {
|
||||
case 'LengthDimension':
|
||||
textObj.plane.origin = textObj.position
|
||||
break
|
||||
case 'DistanceDimension':
|
||||
textObj.plane.origin = textObj.textPosition
|
||||
break
|
||||
case 'AngleDimension':
|
||||
textObj.plane.origin = textObj.textPosition
|
||||
break
|
||||
}
|
||||
textObj['speckle_type'] = 'Objects.Other.Text'
|
||||
const textNode: TreeNode = this.tree.parse({
|
||||
id: this.getNodeId(textObj),
|
||||
raw: textObj,
|
||||
atomic: false,
|
||||
children: []
|
||||
})
|
||||
this.tree.addNode(textNode, node)
|
||||
await this.convertToNode(textObj, textNode)
|
||||
}
|
||||
|
||||
private async PointToNode(obj, node) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Matrix4,
|
||||
Vector3
|
||||
} from 'three'
|
||||
import { SpeckleObject } from '../tree/DataTree'
|
||||
|
||||
export enum GeometryAttributes {
|
||||
POSITION = 'POSITION',
|
||||
@@ -22,6 +23,7 @@ export interface GeometryData {
|
||||
attributes: Partial<Record<GeometryAttributes, number[]>>
|
||||
bakeTransform: Matrix4
|
||||
transform: Matrix4
|
||||
metaData?: SpeckleObject
|
||||
}
|
||||
|
||||
export class Geometry {
|
||||
|
||||
@@ -21,6 +21,7 @@ export enum SpeckleType {
|
||||
Arc = 'Arc',
|
||||
Ellipse = 'Ellipse',
|
||||
RevitInstance = 'RevitInstance',
|
||||
Text = 'Text',
|
||||
Unknown = 'Unknown'
|
||||
}
|
||||
|
||||
@@ -36,7 +37,8 @@ export const SpeckleTypeAllRenderables: SpeckleType[] = [
|
||||
SpeckleType.Curve,
|
||||
SpeckleType.Circle,
|
||||
SpeckleType.Arc,
|
||||
SpeckleType.Ellipse
|
||||
SpeckleType.Ellipse,
|
||||
SpeckleType.Text
|
||||
]
|
||||
|
||||
export class GeometryConverter {
|
||||
@@ -94,7 +96,9 @@ export class GeometryConverter {
|
||||
return GeometryConverter.View3DToGeometryData(node)
|
||||
case SpeckleType.RevitInstance:
|
||||
return GeometryConverter.RevitInstanceToGeometryData(node)
|
||||
default:
|
||||
case SpeckleType.Text:
|
||||
return GeometryConverter.TextToGeometryData(node)
|
||||
case SpeckleType.Unknown:
|
||||
// console.warn(`Skipping geometry conversion for ${type}`)
|
||||
return null
|
||||
}
|
||||
@@ -337,6 +341,29 @@ export class GeometryConverter {
|
||||
} as GeometryData
|
||||
}
|
||||
|
||||
/**
|
||||
* TEXT
|
||||
*/
|
||||
private static TextToGeometryData(node: NodeData): GeometryData {
|
||||
const conversionFactor = getConversionFactor(node.raw.units)
|
||||
const plane = node.raw.plane
|
||||
const position = new Vector3(plane.origin.x, plane.origin.y, plane.origin.z)
|
||||
const scale = new Matrix4().makeScale(
|
||||
conversionFactor,
|
||||
conversionFactor,
|
||||
conversionFactor
|
||||
)
|
||||
const mat = new Matrix4().makeBasis(plane.xdir, plane.ydir, plane.normal)
|
||||
mat.setPosition(position)
|
||||
mat.premultiply(scale)
|
||||
return {
|
||||
attributes: null,
|
||||
bakeTransform: mat,
|
||||
transform: null,
|
||||
metaData: node.raw
|
||||
} as GeometryData
|
||||
}
|
||||
|
||||
/**
|
||||
* POINT
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Assets } from '../Assets'
|
||||
import { getConversionFactor } from '../converter/Units'
|
||||
import SpeckleGhostMaterial from './SpeckleGhostMaterial'
|
||||
import Logger from 'js-logger'
|
||||
import SpeckleTextMaterial from './SpeckleTextMaterial'
|
||||
|
||||
export interface MaterialOptions {
|
||||
rampIndex?: number
|
||||
@@ -56,6 +57,12 @@ export default class Materials {
|
||||
private pointOverlayMaterial: Material = null
|
||||
private pointCloudOverlayMaterial: Material = null
|
||||
|
||||
private textHighlightMaterial: Material = null
|
||||
private textGhostMaterial: Material = null
|
||||
private textColoredMaterial: Material = null
|
||||
private textOverlayMaterial: Material = null
|
||||
private textHiddenMaterial: Material = null
|
||||
|
||||
private defaultGradientTextureData: ImageData = null
|
||||
|
||||
public static renderMaterialFromNode(node: TreeNode): RenderMaterial {
|
||||
@@ -430,6 +437,104 @@ export default class Materials {
|
||||
;(this.pointGhostMaterial as SpecklePointMaterial).toneMapped = false
|
||||
}
|
||||
|
||||
private async createDefaultTextMaterials() {
|
||||
this.textHighlightMaterial = new SpeckleTextMaterial(
|
||||
{
|
||||
color: 0x047efb,
|
||||
opacity: 1,
|
||||
side: DoubleSide
|
||||
},
|
||||
['USE_RTE']
|
||||
)
|
||||
this.textHighlightMaterial.transparent =
|
||||
this.textHighlightMaterial.opacity < 1 ? true : false
|
||||
this.textHighlightMaterial.depthWrite = this.textHighlightMaterial.transparent
|
||||
? false
|
||||
: true
|
||||
this.textHighlightMaterial.toneMapped = false
|
||||
;(this.textHighlightMaterial as SpeckleTextMaterial).color.convertSRGBToLinear()
|
||||
|
||||
this.textHighlightMaterial = (
|
||||
this.textHighlightMaterial as SpeckleTextMaterial
|
||||
).getDerivedMaterial()
|
||||
|
||||
this.textGhostMaterial = new SpeckleTextMaterial(
|
||||
{
|
||||
color: 0xffffff,
|
||||
opacity: 0.1,
|
||||
side: DoubleSide
|
||||
},
|
||||
['USE_RTE']
|
||||
)
|
||||
this.textGhostMaterial.transparent =
|
||||
this.textGhostMaterial.opacity < 1 ? true : false
|
||||
this.textGhostMaterial.depthWrite = this.textGhostMaterial.transparent
|
||||
? false
|
||||
: true
|
||||
this.textGhostMaterial.toneMapped = false
|
||||
;(this.textGhostMaterial as SpeckleTextMaterial).color.convertSRGBToLinear()
|
||||
|
||||
this.textGhostMaterial = (
|
||||
this.textGhostMaterial as SpeckleTextMaterial
|
||||
).getDerivedMaterial()
|
||||
|
||||
this.textColoredMaterial = new SpeckleTextMaterial(
|
||||
{
|
||||
color: 0xffffff,
|
||||
opacity: 1,
|
||||
side: DoubleSide
|
||||
},
|
||||
['USE_RTE']
|
||||
)
|
||||
this.textColoredMaterial.transparent =
|
||||
this.textColoredMaterial.opacity < 1 ? true : false
|
||||
this.textColoredMaterial.depthWrite = this.textColoredMaterial.transparent
|
||||
? false
|
||||
: true
|
||||
this.textColoredMaterial.toneMapped = false
|
||||
;(this.textColoredMaterial as SpeckleTextMaterial).color.convertSRGBToLinear()
|
||||
|
||||
this.textColoredMaterial = (
|
||||
this.textColoredMaterial as SpeckleTextMaterial
|
||||
).getDerivedMaterial()
|
||||
|
||||
this.textOverlayMaterial = new SpeckleTextMaterial(
|
||||
{
|
||||
color: 0x04cbfb,
|
||||
opacity: 1,
|
||||
side: DoubleSide
|
||||
},
|
||||
['USE_RTE']
|
||||
)
|
||||
this.textOverlayMaterial.transparent =
|
||||
this.textOverlayMaterial.opacity < 1 ? true : false
|
||||
this.textOverlayMaterial.depthWrite = this.textOverlayMaterial.transparent
|
||||
? false
|
||||
: true
|
||||
this.textOverlayMaterial.toneMapped = false
|
||||
;(this.textOverlayMaterial as SpeckleTextMaterial).color.convertSRGBToLinear()
|
||||
|
||||
this.textOverlayMaterial = (
|
||||
this.textOverlayMaterial as SpeckleTextMaterial
|
||||
).getDerivedMaterial()
|
||||
|
||||
this.textHiddenMaterial = new SpeckleTextMaterial(
|
||||
{
|
||||
color: 0xffffff,
|
||||
opacity: 1,
|
||||
side: DoubleSide
|
||||
},
|
||||
['USE_RTE']
|
||||
)
|
||||
this.textHiddenMaterial.visible = false
|
||||
this.textHiddenMaterial.toneMapped = false
|
||||
;(this.textHiddenMaterial as SpeckleTextMaterial).color.convertSRGBToLinear()
|
||||
|
||||
this.textHiddenMaterial = (
|
||||
this.textHiddenMaterial as SpeckleTextMaterial
|
||||
).getDerivedMaterial()
|
||||
}
|
||||
|
||||
private async createDefaultNullMaterials() {
|
||||
this.materialMap[NodeRenderView.NullRenderMaterialHash] =
|
||||
new SpeckleStandardMaterial(
|
||||
@@ -523,6 +628,7 @@ export default class Materials {
|
||||
await this.createDefaultMeshMaterials()
|
||||
await this.createLineDefaultMaterials()
|
||||
await this.createDefaultPointMaterials()
|
||||
await this.createDefaultTextMaterials()
|
||||
await this.createDefaultNullMaterials()
|
||||
this.defaultGradientTextureData = await Assets.getTextureData(defaultGradient)
|
||||
}
|
||||
@@ -600,6 +706,23 @@ export default class Materials {
|
||||
return mat
|
||||
}
|
||||
|
||||
private makeTextMaterial(materialData: DisplayStyle): Material {
|
||||
const mat = new SpeckleTextMaterial(
|
||||
{
|
||||
color: materialData.color,
|
||||
opacity: 1,
|
||||
side: DoubleSide
|
||||
},
|
||||
['USE_RTE']
|
||||
)
|
||||
mat.transparent = mat.opacity < 1 ? true : false
|
||||
mat.depthWrite = mat.transparent ? false : true
|
||||
mat.toneMapped = false
|
||||
mat.color.convertSRGBToLinear()
|
||||
|
||||
return mat.getDerivedMaterial()
|
||||
}
|
||||
|
||||
public getMaterial(
|
||||
hash: number,
|
||||
material: RenderMaterial | DisplayStyle,
|
||||
@@ -619,6 +742,9 @@ export default class Materials {
|
||||
case GeometryType.POINT_CLOUD:
|
||||
mat = this.getPointCloudMaterial(hash, material as RenderMaterial)
|
||||
break
|
||||
case GeometryType.TEXT:
|
||||
mat = this.getTextMaterial(hash, material)
|
||||
break
|
||||
}
|
||||
// }
|
||||
/** There's a bug in three.js where it checks for the length of the planes without checking if they exist first
|
||||
@@ -659,6 +785,10 @@ export default class Materials {
|
||||
return this.getPointMaterial(hash, material)
|
||||
}
|
||||
|
||||
private getTextMaterial(hash: number, material: RenderMaterial | DisplayStyle) {
|
||||
return this.makeTextMaterial(material as DisplayStyle)
|
||||
}
|
||||
|
||||
public getHighlightMaterial(renderView: NodeRenderView): Material {
|
||||
switch (renderView.geometryType) {
|
||||
case GeometryType.MESH:
|
||||
@@ -671,6 +801,8 @@ export default class Materials {
|
||||
return this.pointHighlightMaterial
|
||||
case GeometryType.POINT_CLOUD:
|
||||
return this.pointCloudHighlightMaterial
|
||||
case GeometryType.TEXT:
|
||||
return this.textHighlightMaterial
|
||||
}
|
||||
}
|
||||
|
||||
@@ -684,6 +816,8 @@ export default class Materials {
|
||||
return this.pointGhostMaterial
|
||||
case GeometryType.POINT_CLOUD:
|
||||
return this.pointGhostMaterial
|
||||
case GeometryType.TEXT:
|
||||
return this.textGhostMaterial
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,6 +833,8 @@ export default class Materials {
|
||||
return this.pointGhostMaterial
|
||||
case GeometryType.POINT_CLOUD:
|
||||
return this.pointGhostMaterial
|
||||
case GeometryType.TEXT:
|
||||
return this.textColoredMaterial
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,6 +850,8 @@ export default class Materials {
|
||||
return this.pointGhostMaterial
|
||||
case GeometryType.POINT_CLOUD:
|
||||
return this.pointGhostMaterial
|
||||
case GeometryType.TEXT:
|
||||
return this.textColoredMaterial
|
||||
}
|
||||
}
|
||||
|
||||
@@ -729,6 +867,8 @@ export default class Materials {
|
||||
return this.pointOverlayMaterial
|
||||
case GeometryType.POINT_CLOUD:
|
||||
return this.pointCloudOverlayMaterial
|
||||
case GeometryType.TEXT:
|
||||
return this.textOverlayMaterial
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,6 +882,8 @@ export default class Materials {
|
||||
return this.meshHiddenMaterial
|
||||
case GeometryType.POINT_CLOUD:
|
||||
return this.meshHiddenMaterial
|
||||
case GeometryType.TEXT:
|
||||
return this.textHiddenMaterial
|
||||
}
|
||||
}
|
||||
|
||||
@@ -799,6 +941,20 @@ export default class Materials {
|
||||
},
|
||||
['USE_RTE']
|
||||
)
|
||||
case GeometryType.TEXT: {
|
||||
const mat = new SpeckleTextMaterial(
|
||||
{
|
||||
color,
|
||||
opacity: 1,
|
||||
side: DoubleSide
|
||||
},
|
||||
['USE_RTE']
|
||||
)
|
||||
mat.toneMapped = false
|
||||
mat.color.convertSRGBToLinear()
|
||||
|
||||
return mat.getDerivedMaterial()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@ class SpeckleBasicMaterial extends ExtendedMeshBasicMaterial {
|
||||
uViewer_low: new Vector3(),
|
||||
uTransforms: [new Matrix4()],
|
||||
tTransforms: null,
|
||||
objCount: 1
|
||||
objCount: 1,
|
||||
billboardPos: new Vector3()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable camelcase */
|
||||
import { speckleTextVert } from './shaders/speckle-text-vert'
|
||||
import { speckleTextFrag } from './shaders/speckle-text-frag'
|
||||
import {
|
||||
UniformsUtils,
|
||||
ShaderLib,
|
||||
Vector3,
|
||||
MeshBasicMaterial,
|
||||
Material,
|
||||
IUniform
|
||||
} from 'three'
|
||||
import { Matrix4 } from 'three'
|
||||
import { Geometry } from '../converter/Geometry'
|
||||
import SpeckleMesh from '../objects/SpeckleMesh'
|
||||
|
||||
import { Uniforms } from './SpeckleStandardMaterial'
|
||||
import { ExtendedMeshBasicMaterial } from './SpeckleMaterial'
|
||||
import { createTextDerivedMaterial } from 'troika-three-text'
|
||||
|
||||
class SpeckleTextMaterial extends ExtendedMeshBasicMaterial {
|
||||
protected static readonly matBuff: Matrix4 = new Matrix4()
|
||||
protected static readonly vecBuff0: Vector3 = new Vector3()
|
||||
protected static readonly vecBuff1: Vector3 = new Vector3()
|
||||
protected static readonly vecBuff2: Vector3 = new Vector3()
|
||||
|
||||
protected get vertexShader(): string {
|
||||
return speckleTextVert
|
||||
}
|
||||
|
||||
protected get fragmentShader(): string {
|
||||
return speckleTextFrag
|
||||
}
|
||||
|
||||
protected get baseUniforms(): { [uniform: string]: IUniform } {
|
||||
return ShaderLib.basic.uniforms
|
||||
}
|
||||
|
||||
protected get uniformsDef(): Uniforms {
|
||||
return {
|
||||
uViewer_high: new Vector3(),
|
||||
uViewer_low: new Vector3(),
|
||||
uTransforms: [new Matrix4()],
|
||||
tTransforms: null,
|
||||
objCount: 1,
|
||||
billboardPos: new Vector3()
|
||||
}
|
||||
}
|
||||
|
||||
constructor(parameters, defines = []) {
|
||||
super(parameters)
|
||||
this.init(defines)
|
||||
}
|
||||
|
||||
/** We need a unique key per program */
|
||||
public customProgramCacheKey() {
|
||||
return this.constructor.name
|
||||
}
|
||||
|
||||
public copy(source) {
|
||||
super.copy(source)
|
||||
this.copyFrom(source)
|
||||
return this
|
||||
}
|
||||
|
||||
public getDerivedMaterial() {
|
||||
const derived = createTextDerivedMaterial(this)
|
||||
/** We rebind the uniforms */
|
||||
for (const k in this.userData) {
|
||||
derived.uniforms[k] = this.userData[k]
|
||||
}
|
||||
|
||||
return derived
|
||||
}
|
||||
|
||||
/** Called by three.js render loop */
|
||||
public onBeforeRender(_this, scene, camera, geometry, object, group) {
|
||||
/** TO ENABLE */
|
||||
// SpeckleTextMaterial.matBuff.copy(camera.matrixWorldInverse)
|
||||
// SpeckleTextMaterial.matBuff.elements[12] = 0
|
||||
// SpeckleTextMaterial.matBuff.elements[13] = 0
|
||||
// SpeckleTextMaterial.matBuff.elements[14] = 0
|
||||
// object.modelViewMatrix.copy(SpeckleTextMaterial.matBuff)
|
||||
// SpeckleTextMaterial.vecBuff0.set(
|
||||
// camera.matrixWorld.elements[12],
|
||||
// camera.matrixWorld.elements[13],
|
||||
// camera.matrixWorld.elements[14]
|
||||
// )
|
||||
// Geometry.DoubleToHighLowVector(
|
||||
// SpeckleTextMaterial.vecBuff0,
|
||||
// SpeckleTextMaterial.vecBuff1,
|
||||
// SpeckleTextMaterial.vecBuff2
|
||||
// )
|
||||
// this.userData.uViewer_low.value.copy(SpeckleTextMaterial.vecBuff1)
|
||||
// this.userData.uViewer_high.value.copy(SpeckleTextMaterial.vecBuff2)
|
||||
// this.needsUpdate = true
|
||||
}
|
||||
}
|
||||
|
||||
export default SpeckleTextMaterial
|
||||
@@ -0,0 +1,53 @@
|
||||
export const speckleTextFrag = /* glsl */ `
|
||||
uniform vec3 diffuse;
|
||||
uniform float opacity;
|
||||
#ifndef FLAT_SHADED
|
||||
varying vec3 vNormal;
|
||||
#endif
|
||||
#include <common>
|
||||
#include <dithering_pars_fragment>
|
||||
#include <color_pars_fragment>
|
||||
#include <uv_pars_fragment>
|
||||
#include <uv2_pars_fragment>
|
||||
#include <map_pars_fragment>
|
||||
#include <alphamap_pars_fragment>
|
||||
#include <alphatest_pars_fragment>
|
||||
#include <aomap_pars_fragment>
|
||||
#include <lightmap_pars_fragment>
|
||||
#include <envmap_common_pars_fragment>
|
||||
#include <envmap_pars_fragment>
|
||||
#include <cube_uv_reflection_fragment>
|
||||
#include <fog_pars_fragment>
|
||||
#include <specularmap_pars_fragment>
|
||||
#include <logdepthbuf_pars_fragment>
|
||||
#include <clipping_planes_pars_fragment>
|
||||
void main() {
|
||||
#include <clipping_planes_fragment>
|
||||
vec4 diffuseColor = vec4( diffuse, opacity );
|
||||
#include <logdepthbuf_fragment>
|
||||
#include <map_fragment>
|
||||
#include <color_fragment>
|
||||
#include <alphamap_fragment>
|
||||
#include <alphatest_fragment>
|
||||
#include <specularmap_fragment>
|
||||
ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );
|
||||
// accumulation (baked indirect lighting only)
|
||||
#ifdef USE_LIGHTMAP
|
||||
vec4 lightMapTexel = texture2D( lightMap, vUv2 );
|
||||
reflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;
|
||||
#else
|
||||
reflectedLight.indirectDiffuse += vec3( 1.0 );
|
||||
#endif
|
||||
// modulation
|
||||
#include <aomap_fragment>
|
||||
reflectedLight.indirectDiffuse *= diffuseColor.rgb;
|
||||
vec3 outgoingLight = reflectedLight.indirectDiffuse;
|
||||
#include <envmap_fragment>
|
||||
#include <output_fragment>
|
||||
#include <tonemapping_fragment>
|
||||
#include <encodings_fragment>
|
||||
#include <fog_fragment>
|
||||
#include <premultiplied_alpha_fragment>
|
||||
#include <dithering_fragment>
|
||||
}
|
||||
`
|
||||
@@ -0,0 +1,184 @@
|
||||
export const speckleTextVert = /* glsl */ `
|
||||
#include <common>
|
||||
#ifdef USE_RTE
|
||||
// The high component is stored as the default 'position' attribute buffer
|
||||
attribute vec3 position_low;
|
||||
uniform vec3 uViewer_high;
|
||||
uniform vec3 uViewer_low;
|
||||
#endif
|
||||
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
attribute float objIndex;
|
||||
|
||||
#if TRANSFORM_STORAGE == 0
|
||||
#if __VERSION__ == 300
|
||||
#define TRANSFORM_STRIDE 4
|
||||
#else
|
||||
#define TRANSFORM_STRIDE 4.
|
||||
#endif
|
||||
uniform sampler2D tTransforms;
|
||||
uniform float objCount;
|
||||
#elif TRANSFORM_STORAGE == 1
|
||||
uniform mat4 uTransforms[OBJ_COUNT];
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <uv_pars_vertex>
|
||||
#include <uv2_pars_vertex>
|
||||
#include <envmap_pars_vertex>
|
||||
#include <color_pars_vertex>
|
||||
#include <fog_pars_vertex>
|
||||
#include <morphtarget_pars_vertex>
|
||||
#include <skinning_pars_vertex>
|
||||
#include <logdepthbuf_pars_vertex>
|
||||
#include <clipping_planes_pars_vertex>
|
||||
|
||||
#ifdef USE_RTE
|
||||
vec4 computeRelativePositionSeparate(in vec3 position_low, in vec3 position_high, in vec3 relativeTo_low, in vec3 relativeTo_high){
|
||||
/*
|
||||
Vector calculation for the high and low differences works on everything
|
||||
*BESIDES* Apple Silicon (or whatever they call it) GPUs
|
||||
|
||||
It would seem that when this code gets compiled, vector types get a lower precision(?)
|
||||
which completely brakes the 2 float -> double reconstructio. Doing it separately for each
|
||||
vector component using floats works fine.
|
||||
*/
|
||||
vec3 highDifference;
|
||||
vec3 lowDifference;
|
||||
float t1 = position_low.x - relativeTo_low.x;
|
||||
float e = t1 - position_low.x;
|
||||
float t2 = ((-relativeTo_low.x - e) + (position_low.x - (t1 - e))) + position_high.x - relativeTo_high.x;
|
||||
highDifference.x = t1 + t2;
|
||||
lowDifference.x = t2 - (highDifference.x - t1);
|
||||
|
||||
t1 = position_low.y - relativeTo_low.y;
|
||||
e = t1 - position_low.y;
|
||||
t2 = ((-relativeTo_low.y - e) + (position_low.y - (t1 - e))) + position_high.y - relativeTo_high.y;
|
||||
highDifference.y = t1 + t2;
|
||||
lowDifference.y = t2 - (highDifference.y - t1);
|
||||
|
||||
t1 = position_low.z - relativeTo_low.z;
|
||||
e = t1 - position_low.z;
|
||||
t2 = ((-relativeTo_low.z - e) + (position_low.z - (t1 - e))) + position_high.z - relativeTo_high.z;
|
||||
highDifference.z = t1 + t2;
|
||||
lowDifference.z = t2 - (highDifference.z - t1);
|
||||
|
||||
vec3 position = highDifference.xyz + lowDifference.xyz;
|
||||
return vec4(position, 1.);
|
||||
}
|
||||
|
||||
vec4 computeRelativePosition(in vec3 position_low, in vec3 position_high, in vec3 relativeTo_low, in vec3 relativeTo_high){
|
||||
/*
|
||||
Source https://github.com/virtualglobebook/OpenGlobe/blob/master/Source/Examples/Chapter05/Jitter/GPURelativeToEyeDSFUN90/Shaders/VS.glsl
|
||||
Note here, we're storing the high part of the position encoding inside three's default 'position' attribute buffer so we avoid redundancy
|
||||
*/
|
||||
vec3 t1 = position_low.xyz - relativeTo_low;
|
||||
vec3 e = t1 - position_low.xyz;
|
||||
vec3 t2 = ((-relativeTo_low - e) + (position_low.xyz - (t1 - e))) + position_high.xyz - relativeTo_high;
|
||||
vec3 highDifference = t1 + t2;
|
||||
vec3 lowDifference = t2 - (highDifference - t1);
|
||||
|
||||
vec3 position = highDifference.xyz + lowDifference.xyz;
|
||||
return vec4(position, 1.);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
void objectTransform(out vec4 quaternion, out vec4 pivotLow, out vec4 pivotHigh, out vec4 translation, out vec4 scale){
|
||||
#if TRANSFORM_STORAGE == 0
|
||||
#if __VERSION__ == 300
|
||||
ivec2 uv = ivec2(int(objIndex) * TRANSFORM_STRIDE, 0);
|
||||
vec4 v0 = texelFetch( tTransforms, uv, 0 );
|
||||
vec4 v1 = texelFetch( tTransforms, uv + ivec2(1, 0), 0);
|
||||
vec4 v2 = texelFetch( tTransforms, uv + ivec2(2, 0), 0);
|
||||
vec4 v3 = texelFetch( tTransforms, uv + ivec2(3, 0), 0);
|
||||
quaternion = v0;
|
||||
pivotLow = vec4(v1.xyz, 1.);
|
||||
pivotHigh = vec4(v2.xyz, 1.);
|
||||
translation = vec4(v3.xyz, 1.);
|
||||
scale = vec4(v1.w, v2.w, v3.w, 1.);
|
||||
#elif
|
||||
float size = objCount * TRANSFORM_STRIDE;
|
||||
vec2 cUv = vec2(0.5/size, 0.5);
|
||||
vec2 dUv = vec2(1./size, 0.);
|
||||
|
||||
vec2 uv = vec2((objIndex * TRANSFORM_STRIDE)/size + cUv.x, cUv.y);
|
||||
vec4 v0 = texture2D( tTransforms, uv);
|
||||
vec4 v1 = texture2D( tTransforms, uv + dUv);
|
||||
vec4 v2 = texture2D( tTransforms, uv + 2. * dUv);
|
||||
vec4 v3 = texture2D( tTransforms, uv + 3. * dUv);
|
||||
quaternion = v0;
|
||||
pivotLow = vec4(v1.xyz, 1.);
|
||||
pivotHigh = vec4(v2.xyz, 1.);
|
||||
translation = vec4(v3.xyz, 1.);
|
||||
scale = vec4(v1.w, v2.w, v3.w, 1.);
|
||||
#endif
|
||||
#elif TRANSFORM_STORAGE == 1
|
||||
mat4 tMatrix = uTransforms[int(objIndex)];
|
||||
quaternion = tMatrix[0];
|
||||
pivotLow = vec4(tMatrix[1].xyz, 1.);
|
||||
pivotHigh = vec4(tMatrix[2].xyz, 1.);
|
||||
translation = vec4(tMatrix[3].xyz, 1.);
|
||||
scale = vec4(tMatrix[1][3], tMatrix[2][3], tMatrix[3][3], 1.);
|
||||
#endif
|
||||
}
|
||||
|
||||
vec3 rotate_vertex_position(vec3 position, vec4 quat)
|
||||
{
|
||||
return position + 2.0 * cross(quat.xyz, cross(quat.xyz, position) + quat.w * position);
|
||||
}
|
||||
#endif
|
||||
|
||||
uniform vec3 billboardPos;
|
||||
|
||||
void main() {
|
||||
#include <uv_vertex>
|
||||
#include <uv2_vertex>
|
||||
#include <color_vertex>
|
||||
#include <morphcolor_vertex>
|
||||
#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )
|
||||
#include <beginnormal_vertex>
|
||||
#include <morphnormal_vertex>
|
||||
#include <skinbase_vertex>
|
||||
#include <skinnormal_vertex>
|
||||
#include <defaultnormal_vertex>
|
||||
#endif
|
||||
#include <begin_vertex>
|
||||
#include <morphtarget_vertex>
|
||||
#include <skinning_vertex>
|
||||
// #include <project_vertex> COMMENTED CHUNK
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 tQuaternion, tPivotLow, tPivotHigh, tTranslation, tScale;
|
||||
objectTransform(tQuaternion, tPivotLow, tPivotHigh, tTranslation, tScale);
|
||||
#endif
|
||||
#ifdef USE_RTE
|
||||
vec4 position_lowT = vec4(position_low, 1.);
|
||||
vec4 position_highT = vec4(position, 1.);
|
||||
vec4 rteLocalPosition = computeRelativePositionSeparate(position_lowT.xyz, position_highT.xyz, uViewer_low, uViewer_high);
|
||||
#ifdef TRANSFORM_STORAGE
|
||||
vec4 rtePivot = computeRelativePositionSeparate(tPivotLow.xyz, tPivotHigh.xyz, uViewer_low, uViewer_high);
|
||||
rteLocalPosition.xyz = rotate_vertex_position((rteLocalPosition - rtePivot).xyz, tQuaternion) * tScale.xyz + rtePivot.xyz + tTranslation.xyz;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_RTE
|
||||
vec4 mvPosition = rteLocalPosition;
|
||||
#else
|
||||
vec4 mvPosition = vec4( transformed, 1.0 );
|
||||
#endif
|
||||
|
||||
#ifdef USE_INSTANCING
|
||||
mvPosition = instanceMatrix * mvPosition;
|
||||
#endif
|
||||
|
||||
mvPosition = modelViewMatrix * mvPosition;
|
||||
|
||||
|
||||
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.);//projectionMatrix * (viewMatrix * vec4(billboardPos, 1.0) + vec4(position.x, position.y, 0., 0.0));//projectionMatrix * mvPosition;//
|
||||
#include <logdepthbuf_vertex>
|
||||
#include <clipping_planes_vertex>
|
||||
#include <worldpos_vertex>
|
||||
#include <envmap_vertex>
|
||||
#include <fog_vertex>
|
||||
}
|
||||
`
|
||||
@@ -57,6 +57,7 @@ export class SpeckleRaycaster extends Raycaster {
|
||||
this.layers.enable(ObjectLayers.STREAM_CONTENT)
|
||||
this.layers.enable(ObjectLayers.STREAM_CONTENT_MESH)
|
||||
this.layers.enable(ObjectLayers.STREAM_CONTENT_LINE)
|
||||
this.layers.enable(ObjectLayers.STREAM_CONTENT_TEXT)
|
||||
// OFF by default
|
||||
// this.layers.enable(ObjectLayers.STREAM_CONTENT_POINT)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import { Mesh } from 'three'
|
||||
import { Text } from 'troika-three-text'
|
||||
import { SpeckleObject } from '../tree/DataTree'
|
||||
|
||||
export interface SpeckleTextParams {
|
||||
textValue?: string
|
||||
richTextValue?: string
|
||||
height?: number
|
||||
}
|
||||
|
||||
export class SpeckleText extends Mesh {
|
||||
private _text: Text = null
|
||||
private _background: Mesh = null
|
||||
|
||||
public static SpeckleTextParamsFromMetadata(metadata: SpeckleObject) {
|
||||
return {
|
||||
textValue: metadata.value ? metadata.value : 'N/A',
|
||||
height: metadata.height
|
||||
} as SpeckleTextParams
|
||||
}
|
||||
|
||||
public get textMesh() {
|
||||
return this._text
|
||||
}
|
||||
|
||||
public get backgroundMesh() {
|
||||
return this._background
|
||||
}
|
||||
|
||||
public constructor(uuid: string) {
|
||||
super()
|
||||
this._text = new Text()
|
||||
this._text.uuid = uuid
|
||||
this._text.depthOffset = -0.01
|
||||
this.add(this._text)
|
||||
}
|
||||
|
||||
// public async build() {
|
||||
// this._text = new Text()
|
||||
// this._text.text = text
|
||||
// this.text.fontSize = size
|
||||
// this.text.color = 0xffffff
|
||||
// const material = new SpeckleBasicMaterial({ color: 0xff0000 }, ['USE_RTE'])
|
||||
// // material.side = DoubleSide
|
||||
// this.text.frustumCulled = false
|
||||
// this.text.layers.set(ObjectLayers.PROPS)
|
||||
// this.text.material = createTextDerivedMaterial(material)
|
||||
// this.text.material.uniforms['billboardPos'] = material.userData.billboardPos
|
||||
// this.text.material.toneMapped = false
|
||||
// await this.update()
|
||||
// }
|
||||
|
||||
public async update(params: SpeckleTextParams, updateFinished?: () => void) {
|
||||
return new Promise<void>((resolve) => {
|
||||
if (params.textValue) {
|
||||
this._text.text = params.textValue
|
||||
}
|
||||
if (params.richTextValue) {
|
||||
//TO DO
|
||||
}
|
||||
if (params.height) {
|
||||
this._text.fontSize = params.height
|
||||
}
|
||||
|
||||
this._text.sync(() => {
|
||||
resolve()
|
||||
if (updateFinished) updateFinished()
|
||||
})
|
||||
})
|
||||
}
|
||||
// public get vertCount() {
|
||||
// return this.text.geometry.attributes.position.count
|
||||
// }
|
||||
|
||||
// public get triCount() {
|
||||
// return this.text.geometry.index.count
|
||||
// }
|
||||
|
||||
// public async setText(text: string, size: number) {
|
||||
// return new Promise<void>((resolve) => {
|
||||
// this.text = new Text()
|
||||
// this.text.text = text
|
||||
// this.text.fontSize = size
|
||||
// this.text.color = 0xffffff
|
||||
// const material = new SpeckleBasicMaterial({ color: 0xff0000 }, ['USE_RTE'])
|
||||
// // material.side = DoubleSide
|
||||
// this.text.frustumCulled = false
|
||||
// this.text.layers.set(ObjectLayers.PROPS)
|
||||
// this.text.material = createTextDerivedMaterial(material)
|
||||
// this.text.material.uniforms['billboardPos'] = material.userData.billboardPos
|
||||
// this.text.material.toneMapped = false
|
||||
// this.text.sync(() => {
|
||||
// this.setBackground()
|
||||
// resolve()
|
||||
// })
|
||||
// })
|
||||
// }
|
||||
|
||||
// private setBackground() {
|
||||
// this.text.geometry.computeBoundingBox()
|
||||
// const sizeBox = this.text.geometry.boundingBox.getSize(new Vector3())
|
||||
// const geometry = new PlaneGeometry(sizeBox.x, sizeBox.y)
|
||||
// const material = new SpeckleBasicMaterial({ color: 0xff0000 }, ['USE_RTE'])
|
||||
// material.toneMapped = false
|
||||
// this.background = new Mesh(geometry, material)
|
||||
// this.background.layers.set(ObjectLayers.PROPS)
|
||||
// this.background.frustumCulled = false
|
||||
// this.background.renderOrder = 1
|
||||
// }
|
||||
|
||||
// public transform(matrix: Matrix4) {
|
||||
// this.geometry.applyMatrix4(matrix)
|
||||
// this.geometry.computeBoundingBox()
|
||||
// const center = this.geometry.boundingBox.getCenter(new Vector3())
|
||||
// ;(this.material as SpeckleBasicMaterial).userData.billboardPos.value = new Vector3(
|
||||
// center.x,
|
||||
// center.y,
|
||||
// center.z
|
||||
// )
|
||||
// ;(this.material as SpeckleBasicMaterial).needsUpdate = true
|
||||
// }
|
||||
|
||||
// public setPosition(pos: Vector3) {
|
||||
// // this.text.position.copy(pos)
|
||||
// const mat = this.text.material
|
||||
// ;(mat as SpeckleBasicMaterial).userData.billboardPos.value = pos
|
||||
// ;(mat as SpeckleBasicMaterial).needsUpdate = true
|
||||
|
||||
// const backgroundPos = new Vector3().copy(pos)
|
||||
// // backgroundPos.x += sizeBox.x * 0.5
|
||||
// // backgroundPos.y += sizeBox.y * 0.5
|
||||
// ;(this.background.material as SpeckleBasicMaterial).userData.billboardPos.value =
|
||||
// backgroundPos
|
||||
// ;(this.background.material as SpeckleBasicMaterial).needsUpdate = true
|
||||
// this.text.anchorX = '50%'
|
||||
// this.text.anchorY = '50%'
|
||||
// this.text.sync()
|
||||
// }
|
||||
}
|
||||
@@ -260,6 +260,7 @@ export class Pipeline {
|
||||
ObjectLayers.STREAM_CONTENT_MESH,
|
||||
ObjectLayers.STREAM_CONTENT_LINE,
|
||||
ObjectLayers.STREAM_CONTENT_POINT,
|
||||
ObjectLayers.STREAM_CONTENT_TEXT,
|
||||
ObjectLayers.SHADOWCATCHER
|
||||
])
|
||||
this.stencilMaskPass.setLayers([ObjectLayers.STREAM_CONTENT_MESH])
|
||||
|
||||
@@ -76,6 +76,10 @@ export class NodeRenderView {
|
||||
return this._renderData.geometry && this._renderData.geometry.attributes
|
||||
}
|
||||
|
||||
public get hasMetadata() {
|
||||
return this._renderData.geometry && this._renderData.geometry.metaData
|
||||
}
|
||||
|
||||
public get speckleType() {
|
||||
return this._renderData.speckleType
|
||||
}
|
||||
@@ -131,6 +135,7 @@ export class NodeRenderView {
|
||||
|
||||
public get validGeometry() {
|
||||
return (
|
||||
this._renderData.geometry.attributes &&
|
||||
this._renderData.geometry.attributes.POSITION &&
|
||||
this._renderData.geometry.attributes.POSITION.length > 0 &&
|
||||
(this._geometryType === GeometryType.MESH
|
||||
@@ -180,6 +185,8 @@ export class NodeRenderView {
|
||||
return GeometryType.POINT
|
||||
case SpeckleType.Pointcloud:
|
||||
return GeometryType.POINT_CLOUD
|
||||
case SpeckleType.Text:
|
||||
return GeometryType.TEXT
|
||||
|
||||
default:
|
||||
return GeometryType.LINE
|
||||
@@ -220,7 +227,8 @@ export class NodeRenderView {
|
||||
const mat =
|
||||
this.renderData.renderMaterial &&
|
||||
(this.geometryType === GeometryType.MESH ||
|
||||
this.geometryType === GeometryType.POINT)
|
||||
this.geometryType === GeometryType.POINT ||
|
||||
this.geometryType === GeometryType.TEXT)
|
||||
? this.renderMaterialToString()
|
||||
: this.renderData.displayStyle &&
|
||||
this.geometryType !== GeometryType.MESH &&
|
||||
@@ -231,7 +239,11 @@ export class NodeRenderView {
|
||||
if (this.renderData.geometry.attributes)
|
||||
geometry = this.renderData.geometry.attributes.COLOR ? 'vertexColors' : ''
|
||||
|
||||
const s = this.geometryType.toString() + geometry + mat
|
||||
const s =
|
||||
this.geometryType.toString() +
|
||||
geometry +
|
||||
mat +
|
||||
(this.geometryType === GeometryType.TEXT ? this._renderData.id : '')
|
||||
return NodeRenderView.hashCode(s)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,20 +29,7 @@ export class RenderTree {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
this.applyTransforms(node)
|
||||
return true
|
||||
})
|
||||
}
|
||||
@@ -52,19 +39,7 @@ export class RenderTree {
|
||||
(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)
|
||||
}
|
||||
}
|
||||
this.applyTransforms(node)
|
||||
return !this.cancel
|
||||
},
|
||||
this.root,
|
||||
@@ -73,6 +48,29 @@ export class RenderTree {
|
||||
return p
|
||||
}
|
||||
|
||||
private applyTransforms(node: TreeNode) {
|
||||
if (node.model.renderView) {
|
||||
const transform = this.computeTransform(node)
|
||||
if (node.model.renderView.hasGeometry) {
|
||||
if (node.model.renderView.renderData.geometry.bakeTransform) {
|
||||
transform.multiply(node.model.renderView.renderData.geometry.bakeTransform)
|
||||
}
|
||||
Geometry.transformGeometryData(
|
||||
node.model.renderView.renderData.geometry,
|
||||
transform
|
||||
)
|
||||
node.model.renderView.computeAABB()
|
||||
this._treeBounds.union(node.model.renderView.aabb)
|
||||
|
||||
if (!GeometryConverter.keepGeometryData) {
|
||||
GeometryConverter.disposeNodeGeometryData(node.model)
|
||||
}
|
||||
} else if (node.model.renderView.hasMetadata) {
|
||||
node.model.renderView.renderData.geometry.bakeTransform.premultiply(transform)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private buildRenderNode(node: TreeNode): NodeRenderData {
|
||||
let ret: NodeRenderData = null
|
||||
const geometryData = GeometryConverter.convertNodeToGeometryData(node.model)
|
||||
@@ -145,7 +143,7 @@ export class RenderTree {
|
||||
.all((node: TreeNode): boolean => {
|
||||
return (
|
||||
node.model.renderView !== null &&
|
||||
node.model.renderView.hasGeometry &&
|
||||
(node.model.renderView.hasGeometry || node.model.renderView.hasMetadata) &&
|
||||
types.includes(node.model.renderView.renderData.speckleType)
|
||||
)
|
||||
})
|
||||
@@ -156,7 +154,7 @@ export class RenderTree {
|
||||
return this.root.all((node: TreeNode): boolean => {
|
||||
return (
|
||||
node.model.renderView !== null &&
|
||||
node.model.renderView.hasGeometry &&
|
||||
(node.model.renderView.hasGeometry || node.model.renderView.hasMetadata) &&
|
||||
types.includes(node.model.renderView.renderData.speckleType)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -11666,6 +11666,7 @@ __metadata:
|
||||
three: ^0.140.0
|
||||
three-mesh-bvh: 0.5.17
|
||||
tree-model: 1.0.7
|
||||
troika-three-text: 0.47.2
|
||||
typescript: ^4.5.4
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@@ -19256,6 +19257,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bidi-js@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "bidi-js@npm:1.0.2"
|
||||
dependencies:
|
||||
require-from-string: ^2.0.2
|
||||
checksum: 1e33008ed4b248b0350cfed5aebbc8717489bc0d3bcee9f5f99b778d82d997df621adefe6f22fffe005dc3bd8c08445b0b17af7c5d8d1a4a25f15f7702d30e68
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"big-integer@npm:^1.6.44":
|
||||
version: 1.6.51
|
||||
resolution: "big-integer@npm:1.6.51"
|
||||
@@ -41590,6 +41600,36 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"troika-three-text@npm:0.47.2":
|
||||
version: 0.47.2
|
||||
resolution: "troika-three-text@npm:0.47.2"
|
||||
dependencies:
|
||||
bidi-js: ^1.0.2
|
||||
troika-three-utils: ^0.47.2
|
||||
troika-worker-utils: ^0.47.2
|
||||
webgl-sdf-generator: 1.1.1
|
||||
peerDependencies:
|
||||
three: ">=0.125.0"
|
||||
checksum: ca93784f214d373496f371108e3e08d9ff29e98ee6355fccc9b4d0b06f9bba8dc80155f69967e5ec6e7f60ff25f900fcfafe26a94811e0b1e430f4662550287a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"troika-three-utils@npm:^0.47.2":
|
||||
version: 0.47.2
|
||||
resolution: "troika-three-utils@npm:0.47.2"
|
||||
peerDependencies:
|
||||
three: ">=0.125.0"
|
||||
checksum: e3a5d5edb2ed0d1889cd8a1d08da3e69ca7978fab86b107863d0dd9de752756a233c48fbae27410090e95d9c1fff4cabf34441338134a554e38c90a9c056e455
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"troika-worker-utils@npm:^0.47.2":
|
||||
version: 0.47.2
|
||||
resolution: "troika-worker-utils@npm:0.47.2"
|
||||
checksum: 67cfd8ac77bc8dd8d9543108b9cac6c36549b626e0624298c7058845eff415c9c5e4480c7e095e39a29051b59d495b10befcc38a95e5644577132a9bdad51a3a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ts-dedent@npm:^2.0.0, ts-dedent@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "ts-dedent@npm:2.2.0"
|
||||
@@ -44126,6 +44166,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webgl-sdf-generator@npm:1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "webgl-sdf-generator@npm:1.1.1"
|
||||
checksum: babf69e99ca22e8ff387bbe50a10519969ea8030d302d5ddf509ff15355e2981edf72edfcc1b29fed78fa73a79a2458c391b1ba7c9ada63ac5be665880ec5de0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webidl-conversions@npm:^3.0.0":
|
||||
version: 3.0.1
|
||||
resolution: "webidl-conversions@npm:3.0.1"
|
||||
|
||||
Reference in New Issue
Block a user