| set enabled(value: boolean) { | set enabled(value: boolean) { | ||||
| if (this._pass) this._pass.enabled = value | if (this._pass) this._pass.enabled = value | ||||
| this._enabledTemp = value | this._enabledTemp = value | ||||
| this.setDirty() | |||||
| } | } | ||||
| @uiConfig() | @uiConfig() | ||||
| @serialize('pass') | @serialize('pass') | ||||
| protected _pass?: T | protected _pass?: T | ||||
| abstract createPass(v:TViewer):T | |||||
| protected abstract _createPass():T | |||||
| /** | /** | ||||
| * This function is called every frame before composer render, if this pass is being used in the pipeline | * This function is called every frame before composer render, if this pass is being used in the pipeline | ||||
| onAdded(viewer: TViewer): void { | onAdded(viewer: TViewer): void { | ||||
| super.onAdded(viewer) | super.onAdded(viewer) | ||||
| this._pass = this.createPass(viewer) | |||||
| this._pass = this._createPass() | |||||
| this._pass.onDirty?.push(viewer.setDirty) | this._pass.onDirty?.push(viewer.setDirty) | ||||
| this._pass.beforeRender = wrapThisFunction(this._beforeRender, this._pass.beforeRender) | this._pass.beforeRender = wrapThisFunction(this._beforeRender, this._pass.beforeRender) | ||||
| viewer.renderManager.registerPass(this._pass) | viewer.renderManager.registerPass(this._pass) | ||||
| return super.fromJSON(data, meta) | return super.fromJSON(data, meta) | ||||
| } | } | ||||
| setDirty() { | |||||
| this._viewer?.setDirty() | |||||
| this.uiConfig?.uiRefresh?.(true, 'postFrame', 100) // adding delay for a few frames, so render target(if any can update) | |||||
| } | |||||
| } | } | ||||
| function wrapThisFunction<T extends AnyFunction, T2>(f1: ()=>void, f2?: T): T { | function wrapThisFunction<T extends AnyFunction, T2>(f1: ()=>void, f2?: T): T { |
| import { | import { | ||||
| BasicDepthPacking, | BasicDepthPacking, | ||||
| Color, | Color, | ||||
| IUniform, | |||||
| DepthPackingStrategies, | |||||
| MeshDepthMaterial, | MeshDepthMaterial, | ||||
| NoBlending, | NoBlending, | ||||
| Texture, | Texture, | ||||
| } from 'three' | } from 'three' | ||||
| import {GBufferRenderPass} from '../../postprocessing' | import {GBufferRenderPass} from '../../postprocessing' | ||||
| import {ThreeViewer} from '../../viewer' | import {ThreeViewer} from '../../viewer' | ||||
| import {IShaderPropertiesUpdater} from '../../materials' | |||||
| import {MaterialExtension} from '../../materials' | |||||
| import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | ||||
| import {uiFolderContainer, uiImage} from 'uiconfig.js' | |||||
| import {uiDropdown, uiFolderContainer, uiImage} from 'uiconfig.js' | |||||
| import {shaderReplaceString} from '../../utils' | |||||
| import {onChange} from 'ts-browser-helpers' | |||||
| import DepthBufferUnpack from './shaders/DepthBufferPlugin.unpack.glsl' | |||||
| import {threeConstMappings} from '../../three' | |||||
| export type DepthBufferPluginEventTypes = '' | export type DepthBufferPluginEventTypes = '' | ||||
| // type DepthBufferPluginTarget = WebGLMultipleRenderTargets | WebGLRenderTarget | // type DepthBufferPluginTarget = WebGLMultipleRenderTargets | WebGLRenderTarget | ||||
| @uiFolderContainer('Depth Buffer Plugin') | @uiFolderContainer('Depth Buffer Plugin') | ||||
| export class DepthBufferPlugin | export class DepthBufferPlugin | ||||
| extends PipelinePassPlugin<DepthBufferPluginPass, 'depth', DepthBufferPluginEventTypes> | |||||
| implements IShaderPropertiesUpdater { | |||||
| extends PipelinePassPlugin<DepthBufferPluginPass, 'depth', DepthBufferPluginEventTypes> { | |||||
| readonly passId = 'depth' | readonly passId = 'depth' | ||||
| public static readonly PluginType = 'DepthBufferPlugin' | public static readonly PluginType = 'DepthBufferPlugin' | ||||
| depthPacking: BasicDepthPacking, | depthPacking: BasicDepthPacking, | ||||
| blending: NoBlending, | blending: NoBlending, | ||||
| }) | }) | ||||
| // private _gbufferPass?: IFilter<GBufferRenderPass<WebGLMultipleRenderTargets> | |||||
| createPass(v: ThreeViewer) { | |||||
| if (!this.target) this.target = v.renderManager.createTarget<DepthBufferPluginTarget>( | |||||
| { | |||||
| depthBuffer: true, | |||||
| samples: v.renderManager.composerTarget.samples || 0, | |||||
| type: this.bufferType, | |||||
| // magFilter: NearestFilter, | |||||
| // minFilter: NearestFilter, | |||||
| // generateMipmaps: false, | |||||
| // encoding: LinearEncoding, | |||||
| }) | |||||
| @onChange(DepthBufferPlugin.prototype._depthPackingChanged) | |||||
| @uiDropdown('Depth Packing', threeConstMappings.DepthPackingStrategies.uiConfig) depthPacking: DepthPackingStrategies | |||||
| // @onChange2(DepthBufferPlugin.prototype._createTarget) | |||||
| // @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig) | |||||
| readonly bufferType: TextureDataType // cannot be changed after creation (for now) | |||||
| // @uiToggle() | |||||
| // @onChange2(DepthBufferPlugin.prototype._createTarget) | |||||
| readonly isPrimaryGBuffer: boolean // cannot be changed after creation (for now) | |||||
| protected _depthPackingChanged() { | |||||
| this.material.depthPacking = this.depthPacking | |||||
| this.material.needsUpdate = true | |||||
| if (this.unpackExtension && this.unpackExtension.extraDefines) { | |||||
| this.unpackExtension.extraDefines.DEPTH_PACKING = this.depthPacking | |||||
| this.unpackExtension.setDirty?.() | |||||
| } | |||||
| this.setDirty() | |||||
| } | |||||
| unpackExtension: MaterialExtension = { | |||||
| shaderExtender: (shader)=>{ | |||||
| shader.fragmentShader = shaderReplaceString(shader.fragmentShader, | |||||
| '#include <packing>', | |||||
| '\n' + DepthBufferUnpack + '\n', {append: true}) | |||||
| }, | |||||
| extraUniforms: { | |||||
| tDepthBuffer: ()=>({value: this.target?.texture}), | |||||
| }, | |||||
| extraDefines: { | |||||
| ['DEPTH_PACKING']: BasicDepthPacking, | |||||
| ['HAS_DEPTH_BUFFER']: ()=>this.target?.texture ? 1 : undefined, | |||||
| ['HAS_GBUFFER']: ()=>this.isPrimaryGBuffer && this.target?.texture ? 1 : undefined, | |||||
| }, | |||||
| priority: 100, | |||||
| isCompatible: () => true, | |||||
| } | |||||
| private _isPrimaryGBufferSet = false | |||||
| protected _createTarget(recreate = true) { | |||||
| if (!this._viewer) return | |||||
| if (recreate) this._disposeTarget() | |||||
| if (!this.target) | |||||
| this.target = this._viewer.renderManager.createTarget<DepthBufferPluginTarget>( | |||||
| { | |||||
| depthBuffer: true, | |||||
| samples: this._viewer.renderManager.composerTarget.samples || 0, | |||||
| type: this.bufferType, | |||||
| // magFilter: NearestFilter, | |||||
| // minFilter: NearestFilter, | |||||
| // generateMipmaps: false, | |||||
| // encoding: LinearEncoding, | |||||
| }) | |||||
| this.texture = this.target.texture | this.texture = this.target.texture | ||||
| this.texture.name = 'depthBuffer' | this.texture.name = 'depthBuffer' | ||||
| if (this.isPrimaryGBuffer) v.renderManager.gbufferTarget = this.target | |||||
| if (this.isPrimaryGBuffer) { | |||||
| this._viewer.renderManager.gbufferTarget = this.target | |||||
| this._viewer.renderManager.screenPass.material.registerMaterialExtensions([this.unpackExtension]) | |||||
| this._isPrimaryGBufferSet = true | |||||
| } | |||||
| } | |||||
| protected _disposeTarget() { | |||||
| if (!this._viewer) return | |||||
| if (this.target) { | |||||
| this._viewer.renderManager.disposeTarget(this.target) | |||||
| this.target = undefined | |||||
| } | |||||
| this.texture = undefined | |||||
| if (this._isPrimaryGBufferSet) { // using a separate flag as when isPrimaryGBuffer is changed, we cannot check it. | |||||
| this._viewer.renderManager.gbufferTarget = undefined | |||||
| // this._viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this.unpackExtension]) // todo | |||||
| this._isPrimaryGBufferSet = false | |||||
| } | |||||
| } | |||||
| protected _createPass() { | |||||
| this._createTarget(true) | |||||
| if (!this.target) throw new Error('DepthBufferPlugin: target not created') | |||||
| this.material.userData.isGBufferMaterial = true | this.material.userData.isGBufferMaterial = true | ||||
| const pass = new GBufferRenderPass('depth', this.target, this.material, new Color(0, 0, 0), 1) | const pass = new GBufferRenderPass('depth', this.target, this.material, new Color(0, 0, 0), 1) | ||||
| const preprocessMaterial = pass.preprocessMaterial | const preprocessMaterial = pass.preprocessMaterial | ||||
| } | } | ||||
| constructor( | constructor( | ||||
| public readonly bufferType: TextureDataType = UnsignedByteType, | |||||
| public readonly isPrimaryGBuffer = false, | |||||
| bufferType: TextureDataType = UnsignedByteType, | |||||
| isPrimaryGBuffer = false, | |||||
| enabled = true, | enabled = true, | ||||
| depthPacking: DepthPackingStrategies = BasicDepthPacking, | |||||
| ) { | ) { | ||||
| super() | super() | ||||
| this.enabled = enabled | this.enabled = enabled | ||||
| this.depthPacking = depthPacking | |||||
| this.bufferType = bufferType | |||||
| this.isPrimaryGBuffer = isPrimaryGBuffer | |||||
| } | } | ||||
| onRemove(viewer: ThreeViewer): void { | onRemove(viewer: ThreeViewer): void { | ||||
| if (this.target) { | |||||
| viewer.renderManager.disposeTarget(this.target) | |||||
| this.target = undefined | |||||
| } | |||||
| this._disposeTarget() | |||||
| return super.onRemove(viewer) | return super.onRemove(viewer) | ||||
| } | } | ||||
| updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this { | |||||
| if (material.uniforms.tDepth) material.uniforms.tDepth.value = this.texture ?? undefined | |||||
| else this._viewer?.console.warn('BaseRenderer: no uniform: tDepth') | |||||
| return this | |||||
| } | |||||
| } | } | ||||
| Color, | Color, | ||||
| FrontSide, | FrontSide, | ||||
| HalfFloatType, | HalfFloatType, | ||||
| IUniform, | |||||
| LinearSRGBColorSpace, | LinearSRGBColorSpace, | ||||
| MeshNormalMaterial, | MeshNormalMaterial, | ||||
| NearestFilter, | NearestFilter, | ||||
| } from 'three' | } from 'three' | ||||
| import {GBufferRenderPass} from '../../postprocessing' | import {GBufferRenderPass} from '../../postprocessing' | ||||
| import {ThreeViewer} from '../../viewer' | import {ThreeViewer} from '../../viewer' | ||||
| import {IShaderPropertiesUpdater} from '../../materials' | |||||
| import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | ||||
| import type {IMaterial, PhysicalMaterial} from '../../core' | import type {IMaterial, PhysicalMaterial} from '../../core' | ||||
| import {uiFolderContainer, uiImage} from 'uiconfig.js' | import {uiFolderContainer, uiImage} from 'uiconfig.js' | ||||
| export type NormalBufferPluginPass = GBufferRenderPass<'normal', NormalBufferPluginTarget> | export type NormalBufferPluginPass = GBufferRenderPass<'normal', NormalBufferPluginTarget> | ||||
| @uiFolderContainer('Normal Buffer Plugin') | @uiFolderContainer('Normal Buffer Plugin') | ||||
| export class NormalBufferPlugin | export class NormalBufferPlugin | ||||
| extends PipelinePassPlugin<NormalBufferPluginPass, 'normal', NormalBufferPluginEventTypes> | |||||
| implements IShaderPropertiesUpdater { | |||||
| extends PipelinePassPlugin<NormalBufferPluginPass, 'normal', NormalBufferPluginEventTypes> { | |||||
| readonly passId = 'normal' | readonly passId = 'normal' | ||||
| public static readonly PluginType = 'NormalBufferPlugin' | public static readonly PluginType = 'NormalBufferPlugin' | ||||
| readonly material: MeshNormalMaterial = new MeshNormalMaterial2({ | readonly material: MeshNormalMaterial = new MeshNormalMaterial2({ | ||||
| blending: NoBlending, | blending: NoBlending, | ||||
| }) | }) | ||||
| // private _gbufferPass?: IFilter<GBufferRenderPass<WebGLMultipleRenderTargets> | |||||
| createPass(v: ThreeViewer) { | |||||
| if (!this.target) this.target = v.renderManager.createTarget<NormalBufferPluginTarget>( | |||||
| // @onChange2(NormalBufferPlugin.prototype._createTarget) | |||||
| // @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig) | |||||
| readonly bufferType: TextureDataType // cannot be changed after creation (for now) | |||||
| protected _createTarget(recreate = true) { | |||||
| if (!this._viewer) return | |||||
| if (recreate) this._disposeTarget() | |||||
| if (!this.target) this.target = this._viewer.renderManager.createTarget<NormalBufferPluginTarget>( | |||||
| { | { | ||||
| depthBuffer: true, | depthBuffer: true, | ||||
| // samples: v.renderManager.composerTarget.samples || 0, | // samples: v.renderManager.composerTarget.samples || 0, | ||||
| }) | }) | ||||
| this.texture = this.target.texture | this.texture = this.target.texture | ||||
| this.texture.name = 'normalBuffer' | this.texture.name = 'normalBuffer' | ||||
| } | |||||
| protected _disposeTarget() { | |||||
| if (!this._viewer) return | |||||
| if (this.target) { | |||||
| this._viewer.renderManager.disposeTarget(this.target) | |||||
| this.target = undefined | |||||
| } | |||||
| this.texture = undefined | |||||
| } | |||||
| protected _createPass() { | |||||
| this._createTarget(true) | |||||
| if (!this.target) throw new Error('NormalBufferPlugin: target not created') | |||||
| this.material.userData.isGBufferMaterial = true | this.material.userData.isGBufferMaterial = true | ||||
| const pass = new GBufferRenderPass('normal', this.target, this.material, new Color(0, 0, 0), 1) | const pass = new GBufferRenderPass('normal', this.target, this.material, new Color(0, 0, 0), 1) | ||||
| const preprocessMaterial = pass.preprocessMaterial | const preprocessMaterial = pass.preprocessMaterial | ||||
| } | } | ||||
| constructor( | constructor( | ||||
| public readonly bufferType: TextureDataType = HalfFloatType, | |||||
| bufferType: TextureDataType = HalfFloatType, | |||||
| enabled = true, | enabled = true, | ||||
| ) { | ) { | ||||
| super() | super() | ||||
| this.enabled = enabled | this.enabled = enabled | ||||
| this.bufferType = bufferType | |||||
| } | } | ||||
| onRemove(viewer: ThreeViewer): void { | onRemove(viewer: ThreeViewer): void { | ||||
| if (this.target) { | |||||
| viewer.renderManager.disposeTarget(this.target) | |||||
| this.target = undefined | |||||
| } | |||||
| this._disposeTarget() | |||||
| return super.onRemove(viewer) | return super.onRemove(viewer) | ||||
| } | } | ||||
| updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this { | |||||
| if (material.uniforms.tNormal) material.uniforms.tNormal.value = this.texture ?? undefined | |||||
| else this._viewer?.console.warn('BaseRenderer: no uniform: tNormal') | |||||
| return this | |||||
| } | |||||
| } | } | ||||
| class MeshNormalMaterial2 extends MeshNormalMaterial { | class MeshNormalMaterial2 extends MeshNormalMaterial { |
| #if defined(HAS_DEPTH_BUFFER) | |||||
| #if DEPTH_PACKING == 3200 | |||||
| #define unpackDepth(rgba_depth) (1.0 - rgba_depth.r) | |||||
| #elif DEPTH_PACKING == 3201 | |||||
| #define unpackDepth(rgba_depth) unpackRGBAToDepth(rgba_depth) | |||||
| #endif | |||||
| uniform sampler2D tDepthBuffer; | |||||
| #define getDepth(uv) unpackDepth(texture2D(tDepthBuffer, uv)) | |||||
| #endif | |||||
| #include <packing> | |||||
| varying vec2 vUv; | |||||
| #include <alphatest_pars_fragment> | |||||
| void main() { | |||||
| vec4 diffuseColor = tDiffuseTexelToLinear (texture2D(tDiffuse, vUv)); | |||||
| #ifdef HAS_TRANSPARENT_TARGET | |||||
| vec4 transparentColor = tTransparentTexelToLinear (texture2D(tTransparent, vUv)); | |||||
| #else | |||||
| vec4 transparentColor = vec4(0.0); | |||||
| #endif | |||||
| #ifdef HAS_GBUFFER | |||||
| float depth = getDepth(vUv); | |||||
| bool isBackground = depth>0.99 && transparentColor.a < 0.001; | |||||
| #endif | |||||
| #glMarker | |||||
| #ifdef HAS_GBUFFER | |||||
| #ifdef CLIP_BACKGROUND | |||||
| if(isBackground) diffuseColor.a = 0.0; | |||||
| if(depth>0.99 && transparentColor.a >= 0.001) diffuseColor.a = transparentColor.a; | |||||
| #endif | |||||
| if(depth < 0.00001) diffuseColor.a = 0.0; | |||||
| #endif | |||||
| #include <alphatest_fragment> | |||||
| #ifdef OPAQUE | |||||
| diffuseColor.a = 1.0; | |||||
| #endif | |||||
| gl_FragColor = diffuseColor; | |||||
| //gl_FragColor = isBackground ? vec4(0, 0, 0, 1) : gl_FragColor; | |||||
| // gl_FragColor = vec4(depth, 0, 0, 1); | |||||
| #include <encodings_fragment> | |||||
| } |
| WebGLMultipleRenderTargets, | WebGLMultipleRenderTargets, | ||||
| WebGLRenderTarget, | WebGLRenderTarget, | ||||
| } from 'three' | } from 'three' | ||||
| import {IWebGLRenderer, ShaderMaterial2} from '../core' | |||||
| import {ICamera, IRenderManager, IScene, IWebGLRenderer, ShaderMaterial2} from '../core' | |||||
| import {CopyShader} from 'three/examples/jsm/shaders/CopyShader.js' | import {CopyShader} from 'three/examples/jsm/shaders/CopyShader.js' | ||||
| import {IPassID, IPipelinePass} from './Pass' | import {IPassID, IPipelinePass} from './Pass' | ||||
| import {uiFolderContainer} from 'uiconfig.js' | |||||
| import {uiFolderContainer, uiToggle} from 'uiconfig.js' | |||||
| import {ViewerRenderManager} from '../viewer' | |||||
| import {matDefine} from '../three' | |||||
| import ScreenPassShader from './ScreenPass.glsl' | |||||
| import {shaderReplaceString} from '../utils' | |||||
| export type TViewerScreenShaderFrag = string | [string, string] | {pars?: string, main: string} | export type TViewerScreenShaderFrag = string | [string, string] | {pars?: string, main: string} | ||||
| export type TViewerScreenShader = TViewerScreenShaderFrag | ShaderMaterialParameters | ShaderMaterial2 | export type TViewerScreenShader = TViewerScreenShaderFrag | ShaderMaterialParameters | ShaderMaterial2 | ||||
| after: IPassID[] = ['render'] | after: IPassID[] = ['render'] | ||||
| required: IPassID[] = ['render'] | required: IPassID[] = ['render'] | ||||
| constructor(shader: TViewerScreenShader, ...textureID: string[]) { | |||||
| constructor(shader: TViewerScreenShader = '', ...textureID: string[]) { | |||||
| super( | super( | ||||
| (<any>shader)?.fragmentShader || (<ShaderMaterial2>shader)?.isShaderMaterial ? <ShaderMaterialParameters|ShaderMaterial2>shader : | (<any>shader)?.fragmentShader || (<ShaderMaterial2>shader)?.isShaderMaterial ? <ShaderMaterialParameters|ShaderMaterial2>shader : | ||||
| makeScreenShader(shader), | makeScreenShader(shader), | ||||
| ...textureID.length ? textureID : ['tDiffuse']) | |||||
| ...textureID.length ? textureID : ['tDiffuse', 'tTransparent', 'tGBuffer']) | |||||
| this.material.addEventListener('materialUpdate', this.setDirty) | |||||
| } | } | ||||
| outputColorSpace: ColorSpace = SRGBColorSpace | outputColorSpace: ColorSpace = SRGBColorSpace | ||||
| this._lastReadBuffer = undefined | this._lastReadBuffer = undefined | ||||
| super.dispose() | super.dispose() | ||||
| } | } | ||||
| @matDefine('CLIP_BACKGROUND', undefined, undefined, ScreenPass.prototype.setDirty, (v)=>v ? '1' : undefined, (v)=>!!v) | |||||
| @uiToggle() clipBackground = false | |||||
| beforeRender(_: IScene, _1: ICamera, renderManager: ViewerRenderManager) { | |||||
| this.material.uniforms.tTransparent.value = renderManager.renderPass.preserveTransparentTarget ? renderManager.renderPass.transparentTarget?.texture || null : null | |||||
| this.material.defines.HAS_TRANSPARENT_TARGET = this.material.uniforms.tTransparent.value ? 1 : undefined | |||||
| if (!this.material.defines.HAS_TRANSPARENT_TARGET) delete this.material.defines.HAS_TRANSPARENT_TARGET | |||||
| } | |||||
| setDirty() { | |||||
| super.setDirty() | |||||
| this._needsReRender = true | |||||
| } | |||||
| } | } | ||||
| function makeScreenShader(shader: string | [string, string] | {pars?: string; main: string} | ShaderMaterialParameters | ShaderMaterial2) { | function makeScreenShader(shader: string | [string, string] | {pars?: string; main: string} | ShaderMaterialParameters | ShaderMaterial2) { | ||||
| return { | return { | ||||
| ...CopyShader, | ...CopyShader, | ||||
| fragmentShader: ` | |||||
| varying vec2 vUv; | |||||
| #include <alphatest_pars_fragment> | |||||
| ${Array.isArray(shader) ? shader[0] : (<any>shader)?.pars || ''} | |||||
| void main() { | |||||
| vec4 diffuseColor = tDiffuseTexelToLinear (texture2D(tDiffuse, vUv)); | |||||
| #glMarker | |||||
| ${Array.isArray(shader) ? shader[1] : typeof shader === 'string' ? shader : (shader as any)?.main || ''} | |||||
| #include <alphatest_fragment> | |||||
| #ifdef OPAQUE | |||||
| diffuseColor.a = 1.0; | |||||
| #endif | |||||
| gl_FragColor = diffuseColor; | |||||
| #include <encodings_fragment> | |||||
| }`, | |||||
| fragmentShader: | |||||
| shaderReplaceString(shaderReplaceString(ScreenPassShader, | |||||
| 'void main()', (Array.isArray(shader) ? shader[0] : (<any>shader)?.pars || '') + '\n', {prepend: true}), | |||||
| '#glMarker', (Array.isArray(shader) ? shader[1] : typeof shader === 'string' ? shader : (shader as any)?.main || '') + '\n', {prepend: true}), | |||||
| uniforms: { | uniforms: { | ||||
| tDiffuse: {value: null}, | tDiffuse: {value: null}, | ||||
| tTransparent: {value: null}, | |||||
| }, | }, | ||||
| transparent: true, | transparent: true, | ||||
| blending: NoBlending, | blending: NoBlending, |
| export {absMax, clearBit, updateBit} from 'ts-browser-helpers' | export {absMax, clearBit, updateBit} from 'ts-browser-helpers' | ||||
| export {includesAll} from 'ts-browser-helpers' | export {includesAll} from 'ts-browser-helpers' | ||||
| export {copyProps, getOrCall, getPropertyDescriptor, isPropertyWritable, safeSetProperty} from 'ts-browser-helpers' | export {copyProps, getOrCall, getPropertyDescriptor, isPropertyWritable, safeSetProperty} from 'ts-browser-helpers' | ||||
| export {deepAccessObject, getKeyByValue, objectHasOwn} from 'ts-browser-helpers' | |||||
| export {deepAccessObject, getKeyByValue, objectHasOwn, objectMap2, objectMap} from 'ts-browser-helpers' | |||||
| export {makeColorSvg, makeTextSvg, makeColorSvgCircle, svgToCanvas, svgToPng} from 'ts-browser-helpers' | export {makeColorSvg, makeTextSvg, makeColorSvgCircle, svgToCanvas, svgToPng} from 'ts-browser-helpers' | ||||
| export {timeout, now} from 'ts-browser-helpers' | export {timeout, now} from 'ts-browser-helpers' | ||||
| export {pathJoin, getUrlQueryParam, setUrlQueryParam, remoteWorkerURL} from 'ts-browser-helpers' | export {pathJoin, getUrlQueryParam, setUrlQueryParam, remoteWorkerURL} from 'ts-browser-helpers' | ||||
| export {css, glsl, html, svgUrl} from 'ts-browser-helpers' | export {css, glsl, html, svgUrl} from 'ts-browser-helpers' | ||||
| export {Serialization} from 'ts-browser-helpers' | export {Serialization} from 'ts-browser-helpers' | ||||