1010c9d23a
* Addede a bunch of abstract hdris as well as the option to cycle cle though them at runtime * Added an extended PMREMGenerator that allows for the rotation of the hdri image.Now our IBL sources are finally straight not tilded 90 deg anymore. Added artificial shininess to all objects configurable in real time from the sandbox. * Added color grading operations to our standard shader and material. Updated the sandbox to reflect the required AEC Aesthetics properties
201 lines
5.9 KiB
TypeScript
201 lines
5.9 KiB
TypeScript
import {
|
|
Texture,
|
|
WebGLRenderer,
|
|
TextureLoader,
|
|
Color,
|
|
DataTexture,
|
|
Matrix4,
|
|
Euler
|
|
} 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'
|
|
import { RotatablePMREMGenerator } from './objects/RotatablePMREMGenerator'
|
|
|
|
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()
|
|
}
|
|
}
|
|
|
|
private static hdriToPMREM(renderer: WebGLRenderer, hdriTex: Texture): Texture {
|
|
const generator = new RotatablePMREMGenerator(renderer)
|
|
const mat = new Matrix4().makeRotationFromEuler(
|
|
new Euler(-Math.PI * 0.5, 0, -Math.PI * 0.5)
|
|
)
|
|
generator.compileProperEquirectShader(mat)
|
|
const pmremRT = generator.fromEquirectangular(hdriTex)
|
|
generator.dispose()
|
|
return pmremRT.texture
|
|
}
|
|
|
|
public static getEnvironment(
|
|
asset: Asset,
|
|
renderer: WebGLRenderer
|
|
): Promise<Texture> {
|
|
if (this._cache[asset.id]) {
|
|
return Promise.resolve(
|
|
Assets.hdriToPMREM(renderer, this._cache[asset.id] as Texture)
|
|
)
|
|
}
|
|
|
|
return new Promise<Texture>((resolve, reject) => {
|
|
const loader = Assets.getLoader(asset.src, asset.type)
|
|
if (loader) {
|
|
loader.load(
|
|
asset.src,
|
|
(texture) => {
|
|
this._cache[asset.id] = texture
|
|
resolve(Assets.hdriToPMREM(renderer, texture))
|
|
},
|
|
undefined,
|
|
(error: ErrorEvent) => {
|
|
reject(`Loading asset ${asset.id} failed ${error.message}`)
|
|
}
|
|
)
|
|
} else {
|
|
reject(`Loading asset ${asset.id} failed`)
|
|
}
|
|
})
|
|
}
|
|
|
|
public static getTexture(asset: Asset): Promise<Texture> {
|
|
if (this._cache[asset.id]) {
|
|
return Promise.resolve(this._cache[asset.id] 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 (asset.src.includes('data:image')) {
|
|
const image = new Image()
|
|
image.src = asset.src
|
|
image.onload = () => {
|
|
const texture = new Texture(image)
|
|
texture.needsUpdate = true
|
|
this._cache[asset.id] = texture
|
|
resolve(texture)
|
|
}
|
|
image.onerror = (ev) => {
|
|
reject(`Loading asset ${asset.id} failed with ${ev.toString()}`)
|
|
}
|
|
} else {
|
|
const loader = Assets.getLoader(asset.src, asset.type)
|
|
if (loader) {
|
|
loader.load(
|
|
asset.src,
|
|
(texture) => {
|
|
this._cache[asset.id] = texture
|
|
resolve(this._cache[asset.id] as Texture)
|
|
},
|
|
undefined,
|
|
(error: ErrorEvent) => {
|
|
reject(`Loading asset ${asset.id} failed ${error.message}`)
|
|
}
|
|
)
|
|
} else {
|
|
reject(`Loading asset ${asset.id} 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): 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
|
|
}
|
|
}
|