b3d8c5d28c
* 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
209 lines
6.1 KiB
TypeScript
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
|
|
}
|
|
}
|