Selaa lähdekoodia

Add sendArgs: false wherever required, CameraViewPlugin improvements, add clear option to ThreeViewer.dispose and RenderTargetManager.dispose, minor fixes.

master
Palash Bansal 1 vuosi sitten
vanhempi
commit
b237f88069
No account linked to committer's email address

+ 1
- 0
package.json Näytä tiedosto

"files": [ "files": [
"dist", "dist",
"src", "src",
"lib",
"examples", "examples",
"plugins/*/dist", "plugins/*/dist",
"plugins/*/src", "plugins/*/src",

+ 2
- 2
src/core/IRenderer.ts Näytä tiedosto

import {IDisposable, PartialRecord} from 'ts-browser-helpers'
import {PartialRecord} from 'ts-browser-helpers'
import { import {
Blending, Blending,
Clock, Clock,
} }
export type IRenderManagerEventTypes = 'animationLoop'|'update'|'resize'|'contextLost'|'contextRestored' export type IRenderManagerEventTypes = 'animationLoop'|'update'|'resize'|'contextLost'|'contextRestored'
export interface RendererBlitOptions {source?: Texture, viewport?: Vector4, material?: ShaderMaterial, clear?: boolean, respectColorSpace?: boolean, blending?: Blending, transparent?: boolean} export interface RendererBlitOptions {source?: Texture, viewport?: Vector4, material?: ShaderMaterial, clear?: boolean, respectColorSpace?: boolean, blending?: Blending, transparent?: boolean}
export interface IRenderManager<E extends IRenderManagerEvent = IRenderManagerEvent, ET extends string = IRenderManagerEventTypes> extends RenderTargetManager<E, ET>, IDisposable, IShaderPropertiesUpdater{
export interface IRenderManager<E extends IRenderManagerEvent = IRenderManagerEvent, ET extends string = IRenderManagerEventTypes> extends RenderTargetManager<E, ET>, IShaderPropertiesUpdater{
readonly renderer: IWebGLRenderer readonly renderer: IWebGLRenderer
readonly needsRender: boolean readonly needsRender: boolean
rebuildPipeline(setDirty?: boolean): void rebuildPipeline(setDirty?: boolean): void

+ 21
- 11
src/core/object/RootScene.ts Näytä tiedosto

this.setDirty({refreshScene: true}) this.setDirty({refreshScene: true})
} }


@uiButton()
@uiButton(undefined, {sendArgs: false})
centerAllGeometries(keepPosition = true, obj?: IObject3D) { centerAllGeometries(keepPosition = true, obj?: IObject3D) {
const geoms = new Set<IGeometry>() const geoms = new Set<IGeometry>()
;(obj ?? this.modelRoot).traverse((o) => o.geometry && geoms.add(o.geometry)) ;(obj ?? this.modelRoot).traverse((o) => o.geometry && geoms.add(o.geometry))
setDirty && this.setDirty({refreshScene: true}) setDirty && this.setDirty({refreshScene: true})
} }


disposeSceneModels(setDirty = true) {
[...this.modelRoot.children].forEach(child => child.dispose ? child.dispose() : child.removeFromParent())
this.modelRoot.clear()
if (setDirty) this.setDirty({refreshScene: true})
disposeSceneModels(setDirty = true, clear = true) {
if (clear) {
[...this.modelRoot.children].forEach(child => child.dispose ? child.dispose() : child.removeFromParent())
this.modelRoot.clear()
if (setDirty) this.setDirty({refreshScene: true})
} else {
this.modelRoot.children.forEach(child => child.dispose && child.dispose())
}
} }


private _onEnvironmentChange() { private _onEnvironmentChange() {
* Dispose the scene and clear all resources. * Dispose the scene and clear all resources.
* @warn Not fully implemented yet, just clears the scene. * @warn Not fully implemented yet, just clears the scene.
*/ */
dispose(): void {
this.disposeSceneModels();
[...this.children].forEach(child => child.dispose ? child.dispose() : child.removeFromParent())
this.clear()
dispose(clear = true): void {
this.disposeSceneModels(false, clear)

if (clear) {
[...this.children].forEach(child => child.dispose ? child.dispose() : child.removeFromParent())
this.clear()
}


// todo: dispose more stuff? // todo: dispose more stuff?
this.environment?.dispose() this.environment?.dispose()
if ((this.background as ITexture)?.isTexture) (this.background as ITexture)?.dispose?.() if ((this.background as ITexture)?.isTexture) (this.background as ITexture)?.dispose?.()
this.environment = null
this.background = null

if (clear) {
this.environment = null
this.background = null
}
return return
} }



+ 18
- 7
src/plugins/animation/CameraViewPlugin.ts Näytä tiedosto

return super.onRemove(viewer) return super.onRemove(viewer)
} }


@uiButton('Reset To First View')
@uiButton('Reset To First View', {sendArgs: false})
public async resetToFirstView(duration = 100) { public async resetToFirstView(duration = 100) {
if (this.isDisabled()) return if (this.isDisabled()) return
this._currentView = undefined this._currentView = undefined
} }


addView(view: CameraView) { addView(view: CameraView) {
this._cameraViews.push(view)
if (!this._cameraViews.includes(view)) this._cameraViews.push(view)
view.addEventListener('setView', this._viewSetView as any) view.addEventListener('setView', this._viewSetView as any)
view.addEventListener('updateView', this._viewUpdateView as any) view.addEventListener('updateView', this._viewUpdateView as any)
view.addEventListener('deleteView', this._viewDeleteView as any) view.addEventListener('deleteView', this._viewDeleteView as any)
const i = this._cameraViews.indexOf(view) const i = this._cameraViews.indexOf(view)
if (i >= 0) if (i >= 0)
this._cameraViews.splice(i, 1) this._cameraViews.splice(i, 1)
view.removeEventListener('setView', this._viewSetView as any)
view.removeEventListener('updateView', this._viewUpdateView as any)
view.removeEventListener('deleteView', this._viewDeleteView as any)
view.removeEventListener('animateView', this._viewAnimateView as any)
this.uiConfig.uiRefresh?.() this.uiConfig.uiRefresh?.()
this.dispatchEvent({type: 'viewDelete', view}) this.dispatchEvent({type: 'viewDelete', view})
} }


private _currentView: CameraView | undefined private _currentView: CameraView | undefined


@uiButton('Focus Next') focusNext = (wrap = true)=>{
@uiButton('Focus Next', {sendArgs: false}) focusNext = (wrap = true)=>{
if (this._animating) return if (this._animating) return
if (this._cameraViews.length < 2) return if (this._cameraViews.length < 2) return
let index = this._cameraViews.findIndex(v=>v === this._currentView) let index = this._cameraViews.findIndex(v=>v === this._currentView)
else index = index % this._cameraViews.length else index = index % this._cameraViews.length
this.animateToView(index) this.animateToView(index)
} }
@uiButton('Focus Previous') focusPrevious = (wrap = true)=> {
@uiButton('Focus Previous', {sendArgs: false}) focusPrevious = (wrap = true)=> {
if (this._animating) return if (this._animating) return
if (this._cameraViews.length < 2 || !this._currentView) return if (this._cameraViews.length < 2 || !this._currentView) return
let index = this._cameraViews.findIndex(v=>v === this._currentView) let index = this._cameraViews.findIndex(v=>v === this._currentView)


private _popAnimations: AnimationResult[] = [] private _popAnimations: AnimationResult[] = []


async animateToView(_view: CameraView|number, duration?: number, easing?: Easing|EasingFunctionType, camera?: ICamera, throwOnStop = false) {
async animateToView(_view: CameraView|number|string, duration?: number, easing?: Easing|EasingFunctionType, camera?: ICamera, throwOnStop = false) {
camera = camera || this._viewer?.scene.mainCamera camera = camera || this._viewer?.scene.mainCamera
if (!camera) return if (!camera) return
// if (this._currentView === view) return // todo: also check if the camera is at the correct position and orientation, till then use resetToFirstView to reset current view // if (this._currentView === view) return // todo: also check if the camera is at the correct position and orientation, till then use resetToFirstView to reset current view
return return
} }
} }
const view = typeof _view === 'number' ? this._cameraViews[_view] : _view
const view = typeof _view === 'number' ? this._cameraViews[_view] :
typeof _view === 'string' ? this._cameraViews.find(v=>v.name === _view) :
_view
if (!view) {
this._viewer?.console.warn('Invalid view', _view)
return
}


this._currentView = view this._currentView = view
this._animating = true this._animating = true
fromJSON(data: any, meta?: any): this | null { fromJSON(data: any, meta?: any): this | null {
this._cameraViews.forEach(v=>this.deleteView(v)) // deserialize pushes to the existing array this._cameraViews.forEach(v=>this.deleteView(v)) // deserialize pushes to the existing array
if (super.fromJSON(data, meta)) { if (super.fromJSON(data, meta)) {
this._cameraViews.forEach(v=>this.addView(v))
this.uiConfig.uiRefresh?.() this.uiConfig.uiRefresh?.()
return this return this
} }


public async animateToObject(selected?: Object3D, distanceMultiplier = 4, duration?: number, ease?: Easing|EasingFunctionType, distanceBounds = {min: 0.5, max: 5.0}) { public async animateToObject(selected?: Object3D, distanceMultiplier = 4, duration?: number, ease?: Easing|EasingFunctionType, distanceBounds = {min: 0.5, max: 5.0}) {
if (!this._viewer) return if (!this._viewer) return
const bbox = new Box3B().expandByObject(selected || this._viewer.scene.modelRoot.modelObject, false, true)
const bbox = new Box3B().expandByObject(selected || this._viewer.scene.modelRoot, false, true)
const center = bbox.getCenter(new Vector3()) const center = bbox.getCenter(new Vector3())
const size = bbox.getSize(new Vector3()) const size = bbox.getSize(new Vector3())
const radius = size.length() / 2 const radius = size.length() / 2

+ 2
- 2
src/plugins/animation/GLTFAnimationPlugin.ts Näytä tiedosto

this._viewer?.setDirty() this._viewer?.setDirty()
} }


@uiButton('Stop')
@uiButton('Stop', {sendArgs: false})
stopAnimation(reset = false) { stopAnimation(reset = false) {
this._animationState = 'stopped' this._animationState = 'stopped'
// safeSetProperty(this._viewer?.getPlugin<PickingPlugin>('Picking'), 'enabled', true) // safeSetProperty(this._viewer?.getPlugin<PickingPlugin>('Picking'), 'enabled', true)


} }


@uiButton('Reset')
@uiButton('Reset', {sendArgs: false})
resetAnimation() { resetAnimation() {
if (this._animationState !== 'stopped' && this._animationState !== 'none') { if (this._animationState !== 'stopped' && this._animationState !== 'none') {
this.stopAnimation(true) // reset and stop this.stopAnimation(true) // reset and stop

+ 6
- 1
src/plugins/export/CanvasSnapshotPlugin.ts Näytä tiedosto

quality: 0.9, quality: 0.9,
} }


@uiButton('Download .png')
// @uiButton('Download .png', {sendArgs: false})
async downloadSnapshot(filename?: string, options: CanvasSnapshotOptions&{waitForProgressive?: boolean} = {waitForProgressive: true}): Promise<void> { async downloadSnapshot(filename?: string, options: CanvasSnapshotOptions&{waitForProgressive?: boolean} = {waitForProgressive: true}): Promise<void> {
if (!this._viewer) return if (!this._viewer) return
if (!options.mimeType && !filename) this.filename = this.filename.split('.').slice(0, -1).join('.') + '.png' if (!options.mimeType && !filename) this.filename = this.filename.split('.').slice(0, -1).join('.') + '.png'
if (file) await this._viewer.exportBlob(file, file.name) if (file) await this._viewer.exportBlob(file, file.name)
} }


@uiButton('Download .png')
protected async _downloadPng(): Promise<void> {
this.filename = this.filename.split('.').slice(0, -1).join('.') + '.png'
return this.downloadSnapshot(undefined, {mimeType: 'image/png'})
}
@uiButton('Download .jpeg') @uiButton('Download .jpeg')
protected async _downloadJpeg(): Promise<void> { protected async _downloadJpeg(): Promise<void> {
this.filename = this.filename.split('.').slice(0, -1).join('.') + '.jpeg' this.filename = this.filename.split('.').slice(0, -1).join('.') + '.jpeg'

+ 2
- 2
src/plugins/extras/ContactShadowGroundPlugin.ts Näytä tiedosto

import {GBufferRenderPass} from '../../postprocessing' import {GBufferRenderPass} from '../../postprocessing'
import {ThreeViewer} from '../../viewer' import {ThreeViewer} from '../../viewer'
import {IRenderTarget} from '../../rendering' import {IRenderTarget} from '../../rendering'
import {uiFolderContainer, uiSlider, uiToggle} from 'uiconfig.js'
import {uiPanelContainer, uiSlider, uiToggle} from 'uiconfig.js'
import {HVBlurHelper} from '../../three/utils/HVBlurHelper' import {HVBlurHelper} from '../../three/utils/HVBlurHelper'
import {shaderReplaceString} from '../../utils' import {shaderReplaceString} from '../../utils'


@uiFolderContainer('Contact Shadow Ground')
@uiPanelContainer('Contact Shadow Ground')
export class ContactShadowGroundPlugin extends BaseGroundPlugin { export class ContactShadowGroundPlugin extends BaseGroundPlugin {
static readonly PluginType = 'ContactShadowGroundPlugin' static readonly PluginType = 'ContactShadowGroundPlugin'



+ 1
- 1
src/plugins/extras/Object3DGeneratorPlugin.ts Näytä tiedosto

})) }))
protected _selectedType = '' protected _selectedType = ''


@uiButton('Generate')
@uiButton('Generate', {sendArgs: false})
generate(type?: string, params?: any, addToScene = true) { generate(type?: string, params?: any, addToScene = true) {
if (!this._viewer) throw new Error('No viewer') if (!this._viewer) throw new Error('No viewer')
const obj = this.generators[type ?? this._selectedType]?.(params) const obj = this.generators[type ?? this._selectedType]?.(params)

+ 1
- 1
src/plugins/extras/SimplifyModifierPlugin.ts Näytä tiedosto

*/ */
protected abstract _simplify(geometry: IGeometry, count: number): IGeometry protected abstract _simplify(geometry: IGeometry, count: number): IGeometry


@uiButton('Simplify All')
@uiButton('Simplify All', {sendArgs: false})
async simplifyAll(root?: IObject3D, options?: SimplifyOptions) { async simplifyAll(root?: IObject3D, options?: SimplifyOptions) {
if (!root && this._viewer) root = this._viewer.scene.modelRoot if (!root && this._viewer) root = this._viewer.scene.modelRoot
if (!root) { if (!root) {

+ 3
- 3
src/plugins/interaction/FullScreenPlugin.ts Näytä tiedosto

} }
} }


@uiButton('Enter FullScreen')
@uiButton('Enter FullScreen', {sendArgs: false})
async enter(element?: HTMLElement): Promise<void> { async enter(element?: HTMLElement): Promise<void> {
if (this.isFullScreen()) return if (this.isFullScreen()) return


return elem.msRequestFullscreen() return elem.msRequestFullscreen()
} }
} }
@uiButton('Exit FullScreen')
@uiButton('Exit FullScreen', {sendArgs: false})
async exit(): Promise<void> { async exit(): Promise<void> {
if (document.exitFullscreen) { if (document.exitFullscreen) {
return document.exitFullscreen() return document.exitFullscreen()
return (document as any).msExitFullscreen() return (document as any).msExitFullscreen()
} }
} }
@uiButton('Toggle FullScreen')
@uiButton('Toggle FullScreen', {sendArgs: false})
async toggle(element?: HTMLElement): Promise<void> { async toggle(element?: HTMLElement): Promise<void> {
if (this.isFullScreen()) { if (this.isFullScreen()) {
return this.exit() return this.exit()

+ 1
- 1
src/plugins/material/ParallaxMappingPlugin.ts Näytä tiedosto

* This is a port of Relief Parallax Mapping from [Rabbid76/graphics-snippets](https://github.com/Rabbid76/graphics-snippets/blob/master/html/technique/parallax_005_parallax_relief_mapping_derivative_tbn.html) * This is a port of Relief Parallax Mapping from [Rabbid76/graphics-snippets](https://github.com/Rabbid76/graphics-snippets/blob/master/html/technique/parallax_005_parallax_relief_mapping_derivative_tbn.html)
* @category Plugins * @category Plugins
*/ */
@uiFolderContainer('Parallax Mapping')
@uiFolderContainer('Parallax Bump Mapping (MatExt)')
export class ParallaxMappingPlugin extends AViewerPluginSync<''> { export class ParallaxMappingPlugin extends AViewerPluginSync<''> {
public static PluginType = 'ReliefParallaxMapping' public static PluginType = 'ReliefParallaxMapping'



+ 2
- 1
src/postprocessing/GBufferRenderPass.ts Näytä tiedosto



preprocessMaterial = (material: IMaterial, renderToGBuffer?: boolean) => { preprocessMaterial = (material: IMaterial, renderToGBuffer?: boolean) => {
renderToGBuffer = renderToGBuffer ?? material.userData.renderToGBuffer renderToGBuffer = renderToGBuffer ?? material.userData.renderToGBuffer
if (material.userData.pluginsDisabled) renderToGBuffer = false
if ( if (
material.transparent && renderToGBuffer || // transparent and render to gbuffer
material.transparent && (renderToGBuffer || material.opacity > 0.99) || // transparent and render to gbuffer
!material.transparent && !material.transmission && renderToGBuffer === false // opaque and dont render to gbuffer !material.transparent && !material.transmission && renderToGBuffer === false // opaque and dont render to gbuffer
) { ) {
this._transparentMats.add(material) this._transparentMats.add(material)

+ 6
- 6
src/rendering/RenderManager.ts Näytä tiedosto

@onChange2(RenderManager.prototype.rebuildPipeline) @onChange2(RenderManager.prototype.rebuildPipeline)
public autoBuildPipeline = true public autoBuildPipeline = true


@uiButton('Rebuild Pipeline')
@uiButton('Rebuild Pipeline', {sendArgs: false})
rebuildPipeline(setDirty = true): void { rebuildPipeline(setDirty = true): void {
this._passesNeedsUpdate = true this._passesNeedsUpdate = true
if (setDirty) this._updated({change: 'rebuild'}) if (setDirty) this._updated({change: 'rebuild'})
this._updated({change: 'passRefresh'}) this._updated({change: 'passRefresh'})
} }


dispose(): void {
super.dispose()
dispose(clear = true): void {
super.dispose(clear)
this._renderer.dispose() this._renderer.dispose()
} }


* @param quality * @param quality
* @param textureIndex - index of the texture to use in the render target (only in case of multiple render target) * @param textureIndex - index of the texture to use in the render target (only in case of multiple render target)
*/ */
renderTargetToDataUrl(target: WebGLMultipleRenderTargets|WebGLRenderTarget, mimeType = 'image/png', quality = 90, textureIndex = 0): string {
renderTargetToDataUrl(target: WebGLMultipleRenderTargets|WebGLRenderTarget|IRenderTarget, mimeType = 'image/png', quality = 90, textureIndex = 0): string {
const canvas = document.createElement('canvas') const canvas = document.createElement('canvas')
canvas.width = target.width canvas.width = target.width
canvas.height = target.height canvas.height = target.height
const texture = Array.isArray(target.texture) ? target.texture[textureIndex] : target.texture const texture = Array.isArray(target.texture) ? target.texture[textureIndex] : target.texture
const imageData = ctx.createImageData(target.width, target.height, {colorSpace: ['display-p3', 'srgb'].includes(texture.colorSpace) ? <PredefinedColorSpace>texture.colorSpace : undefined}) const imageData = ctx.createImageData(target.width, target.height, {colorSpace: ['display-p3', 'srgb'].includes(texture.colorSpace) ? <PredefinedColorSpace>texture.colorSpace : undefined})
if (texture.type === HalfFloatType || texture.type === FloatType) { if (texture.type === HalfFloatType || texture.type === FloatType) {
const buffer = this.renderTargetToBuffer(target, textureIndex)
const buffer = this.renderTargetToBuffer(target as any, textureIndex)
textureDataToImageData({data: buffer, width: target.width, height: target.height}, texture.colorSpace, imageData) // this handles converting to srgb textureDataToImageData({data: buffer, width: target.width, height: target.height}, texture.colorSpace, imageData) // this handles converting to srgb
} else { } else {
// todo: handle rgbm to srgb conversion? // todo: handle rgbm to srgb conversion?
this._renderer.readRenderTargetPixels(target, 0, 0, target.width, target.height, imageData.data, undefined, textureIndex)
this._renderer.readRenderTargetPixels(target as any, 0, 0, target.width, target.height, imageData.data, undefined, textureIndex)
} }


ctx.putImageData(imageData, 0, 0) ctx.putImageData(imageData, 0, 0)

+ 6
- 4
src/rendering/RenderTargetManager.ts Näytä tiedosto



protected abstract _createTargetClass(clazz: Class<WebGLRenderTarget>, size: number[], options: WebGLRenderTargetOptions): IRenderTarget protected abstract _createTargetClass(clazz: Class<WebGLRenderTarget>, size: number[], options: WebGLRenderTargetOptions): IRenderTarget


dispose() {
dispose(clear = true) {
this._trackedTargets.forEach(t=>t.dispose()) this._trackedTargets.forEach(t=>t.dispose())
Object.values(this._trackedTempTargets).forEach(t=>t.dispose()) Object.values(this._trackedTempTargets).forEach(t=>t.dispose())
this._trackedTargets = []
this._releasedTempTargets = {}
this._trackedTempTargets = []
if (clear) {
this._trackedTargets = []
this._releasedTempTargets = {}
this._trackedTempTargets = []
}
} }


/** /**

+ 16
- 12
src/viewer/ThreeViewer.ts Näytä tiedosto

/** /**
* Disposes the viewer and frees up all resource and events. Do not use the viewer after calling dispose. * Disposes the viewer and frees up all resource and events. Do not use the viewer after calling dispose.
* @note - If you want to reuse the viewer, set viewer.enabled to false instead, then set it to true again when required. To dispose all the objects, materials in the scene use `viewer.scene.disposeSceneModels()` * @note - If you want to reuse the viewer, set viewer.enabled to false instead, then set it to true again when required. To dispose all the objects, materials in the scene use `viewer.scene.disposeSceneModels()`
* This function is not fully implemented yet. There might be some memory leaks.
* This function is not fully implemented yet. There might be some leaks.
* @todo - return promise? * @todo - return promise?
*/ */
public dispose(): void {
public dispose(clear = true): void {
// todo: dispose stuff from constructor etc // todo: dispose stuff from constructor etc
for (const plugin of [...Object.values(this.plugins)]) {
this.removePlugin(plugin, true)
if (clear) {
for (const plugin of [...Object.values(this.plugins)]) {
this.removePlugin(plugin, true)
}
} }


this._scene.dispose()
this.renderManager.dispose()
this._scene.dispose(clear)
this.renderManager.dispose(clear)


this._canvas.removeEventListener('webglcontextrestored', this._onContextRestore, false)
this._canvas.removeEventListener('webglcontextlost', this._onContextLost, false)
if (clear) {
this._canvas.removeEventListener('webglcontextrestored', this._onContextRestore, false)
this._canvas.removeEventListener('webglcontextlost', this._onContextLost, false)


;(window as any).threeViewers?.splice((window as any).threeViewers.indexOf(this), 1)
;(window as any).threeViewers?.splice((window as any).threeViewers.indexOf(this), 1)


if (this.resizeObserver) this.resizeObserver.unobserve(this._canvas)
window.removeEventListener('resize', this.resize)
if (this.resizeObserver) this.resizeObserver.unobserve(this._canvas)
window.removeEventListener('resize', this.resize)
}


this.dispatchEvent({type: 'dispose'})
this.dispatchEvent({type: 'dispose', clear})
} }


/** /**

+ 1
- 1
src/viewer/version.ts Näytä tiedosto

export const VERSION = '0.0.31'
export const VERSION = '0.0.32'

Loading…
Peruuta
Tallenna