Files
speckle-server/packages/viewer/src/modules/Assets.ts
T
Alexandru Popovici b3d8c5d28c 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
2023-06-06 15:49:47 +03:00

209 lines
6.1 KiB
TypeScript

import {
Texture,
PMREMGenerator,
WebGLRenderer,
TextureLoader,
Color,
DataTexture
} 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 | Font } = {}
private static getLoader(src: string, assetType: AssetType): TextureLoader {
if (assetType === undefined) assetType = src.split('.').pop() as AssetType
if (!Object.values(AssetType).includes(assetType)) {
Logger.warn(`Asset ${src} could not be loaded. Unknown type`)
return null
}
switch (assetType) {
case AssetType.TEXTURE_EXR:
return new EXRLoader()
case AssetType.TEXTURE_HDR:
return new RGBELoader()
case AssetType.TEXTURE_8BPP:
return new TextureLoader()
}
}
public static getEnvironment(
asset: Asset | string,
renderer: WebGLRenderer
): Promise<Texture> {
let srcUrl: string = null
let assetType: AssetType = undefined
if ((<Asset>asset).src) {
srcUrl = (asset as Asset).src
assetType = (asset as Asset).type
} else {
srcUrl = asset as string
}
if (this._cache[srcUrl]) {
return Promise.resolve(this._cache[srcUrl] as Texture)
}
return new Promise<Texture>((resolve, reject) => {
const loader = Assets.getLoader(srcUrl, assetType)
if (loader) {
loader.load(
srcUrl,
(texture) => {
const generator = new PMREMGenerator(renderer)
generator.compileEquirectangularShader()
const pmremRT = generator.fromEquirectangular(texture)
this._cache[srcUrl] = pmremRT.texture
texture.dispose()
generator.dispose()
resolve(this._cache[srcUrl] as Texture)
},
undefined,
(error: ErrorEvent) => {
reject(`Loading asset ${srcUrl} failed ${error.message}`)
}
)
} else {
reject(`Loading asset ${srcUrl} failed`)
}
})
}
/** Will unify with environment fetching soon */
public static getTexture(asset: Asset | string): Promise<Texture> {
let srcUrl: string = null
let assetType: AssetType = undefined
if ((<Asset>asset).src) {
srcUrl = (asset as Asset).src
assetType = (asset as Asset).type
} else {
srcUrl = asset as string
}
if (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
// gradient map as a data image url, rather than a file (?).
if (srcUrl.includes('data:image')) {
const image = new Image()
image.src = srcUrl
image.onload = () => {
const texture = new Texture(image)
texture.needsUpdate = true
this._cache[srcUrl] = texture
resolve(texture)
}
image.onerror = (ev) => {
reject(`Loading asset ${srcUrl} failed with ${ev.toString()}`)
}
} else {
const loader = Assets.getLoader(srcUrl, assetType)
if (loader) {
loader.load(
srcUrl,
(texture) => {
this._cache[srcUrl] = texture
resolve(this._cache[srcUrl] as Texture)
},
undefined,
(error: ErrorEvent) => {
reject(`Loading asset ${srcUrl} failed ${error.message}`)
}
)
} else {
reject(`Loading asset ${srcUrl} failed`)
}
}
})
}
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)
const canvas = document.createElement('canvas')
canvas.width = texture.image.width
canvas.height = texture.image.height
const context = canvas.getContext('2d')
context.drawImage(texture.image, 0, 0)
const data = context.getImageData(0, 0, canvas.width, canvas.height)
return Promise.resolve(data)
}
public static generateGradientRampTexture(
fromColor: string,
toColor: string,
steps: number
) {
fromColor
toColor
steps
// NOT NECESSARY AT THE MOMENT. USING STATIC GRADIENT RAMP
}
public static generateDiscreetRampTexture(hexColors: number[]): Texture {
const width = hexColors.length
const height = 1
const size = width * height
const data = new Uint8Array(4 * size)
for (let k = 0; k < hexColors.length; k++) {
const stride = k * 4
const color = new Color(hexColors[k])
color.convertSRGBToLinear()
data[stride] = Math.floor(color.r * 255)
data[stride + 1] = Math.floor(color.g * 255)
data[stride + 2] = Math.floor(color.b * 255)
data[stride + 3] = 255
}
const texture = new DataTexture(data, width, height)
texture.needsUpdate = true
/** In case we want to see what gets generated */
// const canvas = document.createElement('canvas')
// canvas.width = width
// canvas.height = height
// const context = canvas.getContext('2d')
// const imageData = new ImageData(width, height)
// imageData.data.set(data)
// context.putImageData(imageData, 0, 0)
// console.log('SRC:', canvas.toDataURL())
return texture
}
}