diff --git a/examples/example.css b/examples/example.css index 47957c0f29e209..e7e2093563cb9e 100644 --- a/examples/example.css +++ b/examples/example.css @@ -15,7 +15,7 @@ a { position: fixed; top: 15px; left: 15px; - z-index: 1001; + z-index: 100; display: grid; grid-template-columns: 50px auto; @@ -25,6 +25,11 @@ a { color: #e0e0e0; text-shadow: 1px 1px 5px rgba(0, 0, 0, .7); font: 400 14px 'Inter', 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + pointer-events: none; +} + +#info > a { + pointer-events: auto; } #info > a.logo-link { diff --git a/examples/jsm/inspector/tabs/Memory.js b/examples/jsm/inspector/tabs/Memory.js index d9337671d47b3e..53c29510968e1d 100644 --- a/examples/jsm/inspector/tabs/Memory.js +++ b/examples/jsm/inspector/tabs/Memory.js @@ -80,7 +80,6 @@ class Memory extends Tab { updateGraph( inspector ) { const renderer = inspector.getRenderer(); - if ( ! renderer ) return; const memory = renderer.info.memory; @@ -95,7 +94,6 @@ class Memory extends Tab { updateText( inspector ) { const renderer = inspector.getRenderer(); - if ( ! renderer ) return; const memory = renderer.info.memory; diff --git a/examples/jsm/inspector/tabs/Viewer.js b/examples/jsm/inspector/tabs/Viewer.js index 7028213146b11a..e72ee8ad7d9462 100644 --- a/examples/jsm/inspector/tabs/Viewer.js +++ b/examples/jsm/inspector/tabs/Viewer.js @@ -4,11 +4,15 @@ import { Item } from '../ui/Item.js'; import { splitPath, splitCamelCase } from '../ui/utils.js'; import { getItem, setItem } from '../Inspector.js'; -import { RendererUtils, NoToneMapping, LinearSRGBColorSpace, QuadMesh, NodeMaterial, CanvasTarget, Vector2 } from 'three/webgpu'; +import { RendererUtils, NoToneMapping, LinearSRGBColorSpace, QuadMesh, NodeMaterial, CanvasTarget, Vector2, Color } from 'three/webgpu'; import { renderOutput, vec2, vec3, vec4, Fn, screenUV, step, OnMaterialUpdate, uniform, float } from 'three/tsl'; const _size = /*@__PURE__*/ new Vector2(); +const splitIcon = ''; +const fullscreenIcon = ''; +const backIcon = ''; + const aspectRatioUV = /*@__PURE__*/ Fn( ( [ uv, textureNode, canvasAspect ] ) => { const textureAspect = uniform( 0 ); @@ -55,7 +59,7 @@ class Viewer extends Tab { const backBtn = document.createElement( 'button' ); backBtn.className = 'viewer-back-btn'; backBtn.innerHTML = ''; - backBtn.title = 'Back to list'; + backBtn.title = 'Back'; backBtn.style.display = 'none'; toolbar.appendChild( backBtn ); @@ -73,11 +77,13 @@ class Viewer extends Tab { select.appendChild( defaultOption ); toolbar.appendChild( select ); - this.content.appendChild( toolbar ); const nodeList = new List( 'Viewer', 'Name' ); nodeList.setGridStyle( '150px minmax(200px, 2fr)' ); nodeList.domElement.style.minWidth = '400px'; + this.nodeList = nodeList; + + this.content.appendChild( toolbar ); const scrollWrapper = document.createElement( 'div' ); scrollWrapper.className = 'list-scroll-wrapper'; @@ -103,7 +109,6 @@ class Viewer extends Tab { this.folderLibrary = new Map(); this.canvasNodes = new Map(); this.currentDataList = []; - this.nodeList = nodeList; this.nodes = nodes; this.scrollWrapper = scrollWrapper; this.fullViewerContainer = fullViewerContainer; @@ -112,8 +117,24 @@ class Viewer extends Tab { this.activeFullNodeId = null; this.pendingRestoreView = true; + this.splitActive = false; + this.splitCanvasData = null; + this.splitOverlay = null; + this.splitLine = null; + this.splitQuad = null; + this.splitMaterial = null; + this.splitUniforms = null; + this.splitCanvas = null; + this.splitCanvasTarget = null; + backBtn.addEventListener( 'click', () => { + if ( this.splitActive ) { + + this.stopSplitMode(); + + } + select.value = 'list'; this.showListView(); @@ -162,8 +183,6 @@ class Viewer extends Tab { const renderer = this.inspector.getRenderer(); - if ( ! renderer || ! renderer.domElement ) return; - if ( e.isForwarded ) return; // Block native event from reaching other document-level listeners (OrbitControls) @@ -220,6 +239,12 @@ class Viewer extends Tab { this.activeSourceCanvas = null; this.activePointerIds.clear(); + if ( this.splitActive ) { + + this.stopSplitMode(); + + } + } addNodeItem( canvasData ) { @@ -249,6 +274,13 @@ class Viewer extends Tab { viewBtn.onclick = ( e ) => { e.stopPropagation(); + + if ( this.splitActive ) { + + this.stopSplitMode(); + + } + this.select.value = canvasData.id; this.showNodeView( canvasData.id ); this.saveLastView(); @@ -259,45 +291,60 @@ class Viewer extends Tab { const fullscreenBtn = document.createElement( 'button' ); fullscreenBtn.className = 'node-canvas-fullscreen-btn'; fullscreenBtn.title = 'Fullscreen view'; - fullscreenBtn.innerHTML = ''; + fullscreenBtn.innerHTML = fullscreenIcon; fullscreenBtn.onclick = ( e ) => { e.stopPropagation(); - this.select.value = canvasData.id; - this.showNodeView( canvasData.id ); - if ( this.profiler && ! this.profiler.panel.classList.contains( 'maximized' ) ) { + if ( this.splitActive && this.splitCanvasData === canvasData && this.splitFullscreen ) { - this.profiler.toggleMaximize(); - this.maximizedByFullscreenButton = true; + this.stopSplitMode(); + + } else { + + this.splitFullscreen = true; + this.startSplitMode( canvasData ); - if ( ! this._maximizeListenerAdded && this.profiler.maximizeBtn ) { + } - this.profiler.maximizeBtn.addEventListener( 'click', () => { + }; - this.maximizedByFullscreenButton = false; + // Split screen button + const splitBtn = document.createElement( 'button' ); + splitBtn.className = 'node-canvas-split-btn'; + splitBtn.title = 'Interactive split screen'; + splitBtn.innerHTML = splitIcon; - } ); - this._maximizeListenerAdded = true; + splitBtn.onclick = ( e ) => { - } + e.stopPropagation(); - } + if ( this.splitActive && this.splitCanvasData === canvasData && ! this.splitFullscreen ) { - this.saveLastView(); + this.stopSplitMode(); + + } else { + + this.splitFullscreen = false; + this.startSplitMode( canvasData ); + + } }; wrapper.appendChild( domElement ); wrapper.appendChild( viewBtn ); wrapper.appendChild( fullscreenBtn ); + wrapper.appendChild( splitBtn ); this.setupEventForwarding( domElement ); // Store elements in canvasData for access canvasData.domElement = domElement; canvasData.wrapperElement = wrapper; + canvasData.splitBtn = splitBtn; + canvasData.fullscreenBtn = fullscreenBtn; item = new Item( wrapper, name ); item.itemRow.children[ 1 ].style[ 'justify-content' ] = 'flex-start'; @@ -441,6 +488,45 @@ class Viewer extends Tab { } + init( inspector ) { + + super.init( inspector ); + + const updateLayoutFromPosition = () => { + + const position = inspector.profiler.position; + const isHorizontal = position === 'top' || position === 'bottom'; + this.setViewMode( isHorizontal ? 'grid' : 'list' ); + + }; + + inspector.profiler.addEventListener( 'resize', updateLayoutFromPosition ); + + // Run initially once + updateLayoutFromPosition(); + + } + + setViewMode( mode ) { + + if ( this.nodeList ) { + + if ( mode === 'grid' ) { + + this.nodeList.domElement.style.minWidth = '0'; + + } else { + + this.nodeList.domElement.style.minWidth = '400px'; + + } + + this.nodeList.setViewMode( mode ); + + } + + } + showListView() { if ( this.activeFullNodeId ) { @@ -514,6 +600,273 @@ class Viewer extends Tab { } + get isActive() { + + if ( this.splitActive ) return true; + + return super.isActive; + + } + + set isActive( value ) { + + super.isActive = value; + + } + + startSplitMode( canvasData ) { + + this.splitActive = true; + this.splitCanvasData = canvasData; + + const renderer = this.inspector.getRenderer(); + const mainCanvas = renderer.domElement; + const rect = mainCanvas.getBoundingClientRect(); + + // Position target canvas on top of main canvas + if ( ! this.splitCanvas ) { + + this.splitCanvas = document.createElement( 'canvas' ); + this.splitCanvas.style.position = 'fixed'; + this.splitCanvas.style.pointerEvents = 'none'; + this.splitCanvas.style.zIndex = '998'; + + this.splitCanvasTarget = new CanvasTarget( this.splitCanvas ); + this.splitCanvasTarget.setPixelRatio( window.devicePixelRatio ); + + } + + this.splitCanvas.style.left = `${ rect.left }px`; + this.splitCanvas.style.top = `${ rect.top }px`; + this.splitCanvas.style.width = `${ rect.width }px`; + this.splitCanvas.style.height = `${ rect.height }px`; + + this.splitCanvasTarget.setSize( rect.width, rect.height ); + renderer.backend.delete( this.splitCanvasTarget ); + + document.body.appendChild( this.splitCanvas ); + + // Overlay divider line (only in split/non-fullscreen mode) + if ( ! this.splitFullscreen ) { + + if ( ! this.splitOverlay ) { + + const overlay = document.createElement( 'div' ); + overlay.className = 'split-screen-overlay'; + + const line = document.createElement( 'div' ); + line.className = 'split-screen-line'; + + overlay.appendChild( line ); + + let isDragging = false; + + const onPointerDown = ( e ) => { + + isDragging = true; + e.preventDefault(); + + }; + + const onPointerMove = ( e ) => { + + if ( ! isDragging ) return; + + const minPadding = 10; // Keep the line at least 15px away from the edges for easy grabbing + const x = Math.max( minPadding, Math.min( window.innerWidth - minPadding, e.clientX ) ); + const pct = x / window.innerWidth; + this.splitX = pct; + line.style.left = `${ pct * 100 }%`; + + if ( this.splitUniforms && this.splitUniforms.splitX ) { + + this.splitUniforms.splitX.value = pct; + + } + + }; + + const onPointerUp = () => { + + isDragging = false; + + }; + + line.addEventListener( 'pointerdown', onPointerDown ); + window.addEventListener( 'pointermove', onPointerMove ); + window.addEventListener( 'pointerup', onPointerUp ); + + this.splitOverlay = overlay; + this.splitLine = line; + + } + + this.splitLine.style.left = '50%'; + + this.profiler.domElement.appendChild( this.splitOverlay ); + + } else { + + // If we transitioned from split to fullscreen, remove the overlay slider + if ( this.splitOverlay && this.splitOverlay.parentElement ) { + + this.splitOverlay.parentElement.removeChild( this.splitOverlay ); + + } + + } + + if ( ! this.splitUniforms ) { + + this.splitUniforms = { + splitX: uniform( 0.5 ), + viewportWidth: uniform( window.innerWidth ) + }; + + } + + this.splitUniforms.splitX.value = 0.5; + this.splitUniforms.viewportWidth.value = renderer.domElement.width; + + // Recreate or setup material for split screen comparison + const node = canvasData.node; + const target = node.context( { getUV: () => screenUV } ); + const targetColor = renderOutput( vec4( vec3( target ), 1 ), NoToneMapping, renderer.outputColorSpace ); + const targetColorCtx = targetColor.context( { inspector: true } ); + + let finalColor; + + if ( this.splitFullscreen ) { + + finalColor = targetColorCtx; + + } else { + + const thickness = float( 1 ).div( this.splitUniforms.viewportWidth ); + const isLine = screenUV.x.sub( this.splitUniforms.splitX ).abs().lessThan( thickness ); + finalColor = Fn( () => { + + screenUV.x.lessThan( this.splitUniforms.splitX ).discard(); + + return isLine.select( vec4( 0.29, 0.29, 0.35, 1.0 ), targetColorCtx ); + + } )(); + + } + + if ( this.splitMaterial ) { + + this.splitMaterial.dispose(); + + } + + this.splitMaterial = new NodeMaterial(); + this.splitMaterial.outputNode = finalColor; + this.splitMaterial.depthTest = false; + this.splitMaterial.depthWrite = false; + + if ( this.splitQuad ) { + + this.splitQuad.material = this.splitMaterial; + + } else { + + this.splitQuad = new QuadMesh( this.splitMaterial ); + + } + + this.updateSplitButtonsState(); + + } + + stopSplitMode() { + + this.splitActive = false; + this.splitCanvasData = null; + this.splitFullscreen = false; + + if ( this.splitOverlay && this.splitOverlay.parentElement ) { + + this.splitOverlay.parentElement.removeChild( this.splitOverlay ); + + } + + if ( this.splitCanvas && this.splitCanvas.parentElement ) { + + this.splitCanvas.parentElement.removeChild( this.splitCanvas ); + + } + + this.updateSplitButtonsState(); + + } + + updateSplitButtonsState() { + + if ( this.splitActive ) { + + this.backBtn.style.display = ''; + + } else { + + if ( this.select.value === 'list' ) { + + this.backBtn.style.display = 'none'; + + } else { + + this.backBtn.style.display = ''; + + } + + } + + for ( const canvasData of this.canvasNodes.values() ) { + + if ( ! canvasData.splitBtn || ! canvasData.fullscreenBtn ) continue; + + const isSelected = this.splitActive && this.splitCanvasData === canvasData; + + if ( isSelected ) { + + if ( this.splitFullscreen ) { + + canvasData.fullscreenBtn.classList.add( 'active' ); + canvasData.fullscreenBtn.innerHTML = backIcon; + canvasData.fullscreenBtn.title = 'Exit Fullscreen'; + + canvasData.splitBtn.classList.remove( 'active' ); + canvasData.splitBtn.innerHTML = splitIcon; + canvasData.splitBtn.title = 'Interactive split screen'; + + } else { + + canvasData.splitBtn.classList.add( 'active' ); + canvasData.splitBtn.innerHTML = backIcon; + canvasData.splitBtn.title = 'Exit Split Screen'; + + canvasData.fullscreenBtn.classList.remove( 'active' ); + canvasData.fullscreenBtn.innerHTML = fullscreenIcon; + canvasData.fullscreenBtn.title = 'Fullscreen view'; + + } + + } else { + + canvasData.splitBtn.classList.remove( 'active' ); + canvasData.splitBtn.innerHTML = splitIcon; + canvasData.splitBtn.title = 'Interactive split screen'; + + canvasData.fullscreenBtn.classList.remove( 'active' ); + canvasData.fullscreenBtn.innerHTML = fullscreenIcon; + canvasData.fullscreenBtn.title = 'Fullscreen view'; + + } + + } + + } + getCanvasDataByNode( renderer, node ) { let canvasData = this.canvasNodes.get( node ); @@ -575,6 +928,48 @@ class Viewer extends Tab { update( inspector ) { const renderer = inspector.getRenderer(); + + if ( this.splitActive ) { + + // Resize canvas target to match the main canvas if window resized + const mainCanvas = renderer.domElement; + const rect = mainCanvas.getBoundingClientRect(); + + if ( this.splitCanvasTarget.domElement.width !== rect.width || this.splitCanvasTarget.domElement.height !== rect.height ) { + + this.splitCanvas.style.width = `${ rect.width }px`; + this.splitCanvas.style.height = `${ rect.height }px`; + this.splitCanvas.style.left = `${ rect.left }px`; + this.splitCanvas.style.top = `${ rect.top }px`; + + this.splitCanvasTarget.setSize( rect.width, rect.height ); + + renderer.backend.delete( this.splitCanvasTarget ); + + } + + // Render the split quad to the splitCanvasTarget + const previousCanvasTarget = renderer.getCanvasTarget(); + const previousClearColor = renderer.getClearColor( new Color() ); + const previousClearAlpha = renderer.getClearAlpha(); + + const state = RendererUtils.resetRendererState( renderer ); + + renderer.toneMapping = NoToneMapping; + renderer.outputColorSpace = LinearSRGBColorSpace; + + renderer.setCanvasTarget( this.splitCanvasTarget ); + renderer.setClearColor( 0x000000, 0 ); + + this.splitQuad.render( renderer ); + + renderer.setCanvasTarget( previousCanvasTarget ); + renderer.setClearColor( previousClearColor, previousClearAlpha ); + + RendererUtils.restoreRendererState( renderer, state ); + + } + const nodes = inspector.getNodes(); if ( nodes.length > 0 ) { diff --git a/examples/jsm/inspector/ui/List.js b/examples/jsm/inspector/ui/List.js index 53d574a9690a8f..62f8e43ee809b6 100644 --- a/examples/jsm/inspector/ui/List.js +++ b/examples/jsm/inspector/ui/List.js @@ -39,6 +39,20 @@ export class List { } + setViewMode( mode ) { + + if ( mode === 'grid' ) { + + this.domElement.classList.add( 'grid-mode' ); + + } else { + + this.domElement.classList.remove( 'grid-mode' ); + + } + + } + add( item ) { if ( item.parent !== null ) { diff --git a/examples/jsm/inspector/ui/Profiler.js b/examples/jsm/inspector/ui/Profiler.js index 138ae798d710ca..4e5c7f70295466 100644 --- a/examples/jsm/inspector/ui/Profiler.js +++ b/examples/jsm/inspector/ui/Profiler.js @@ -2004,6 +2004,9 @@ export class Profiler extends EventDispatcher { // Update panel size after loading layout this.updatePanelSize(); + // Update widget position (toggle and mini panel alignment) + this.updateWidgetPosition(); + // Ensure initial open state applies to mini panel as well if ( this.panel.classList.contains( 'visible' ) ) { diff --git a/examples/jsm/inspector/ui/Style.js b/examples/jsm/inspector/ui/Style.js index c4d888de0427a3..ceeb16b324b299 100644 --- a/examples/jsm/inspector/ui/Style.js +++ b/examples/jsm/inspector/ui/Style.js @@ -53,7 +53,7 @@ export class Style { border-radius: 12px 6px 6px 12px; color: var(--text-primary); cursor: pointer; - z-index: 1001; + z-index: 1002; transition: all 0.2s ease-in-out; /*font-size: 14px;*/ font-size: 15px; @@ -596,6 +596,7 @@ export class Style { .profiler-panel.maximized { height: 100%; + z-index: 10000 !important; } /* Position-specific styles */ @@ -1197,6 +1198,7 @@ export class Style { .list-children-container.closed { max-height: 0; + display: none !important; } .item-toggler { @@ -2047,6 +2049,140 @@ export class Style { touch-action: none; } + .node-canvas-wrapper .node-canvas-split-btn { + position: absolute; + top: 5px; + left: 5px; + background: rgba(30, 30, 36, 0.85); + border: 1px solid var(--profiler-border); + color: var(--text-primary); + border-radius: 4px; + padding: 4px; + cursor: pointer; + opacity: 1; + transition: background-color 0.2s, border-color 0.2s, color 0.2s; + display: flex; + align-items: center; + justify-content: center; + z-index: 10; + } + + .node-canvas-wrapper .node-canvas-split-btn:hover { + background-color: var(--color-accent); + border-color: var(--color-accent); + color: white; + } + + .node-canvas-wrapper .node-canvas-split-btn.active, + .node-canvas-wrapper .node-canvas-fullscreen-btn.active { + background-color: var(--color-accent) !important; + border-color: var(--color-accent) !important; + color: white !important; + } + + .split-screen-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none !important; + z-index: 999; + touch-action: none; + } + + .split-screen-line { + position: absolute; + top: 0; + bottom: 0; + width: 4px; + margin-left: -2px; + left: 50%; + background: var(--profiler-border); + cursor: ew-resize; + pointer-events: auto !important; + z-index: 10; + touch-action: none; + } + + .split-screen-line::before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: -10px; + width: 24px; + background: transparent; + cursor: ew-resize; + } + + /* Grid Mode styles for List component */ + .list-scroll-wrapper:has(> .list-container.grid-mode) { + width: 100% !important; + } + + .list-container.grid-mode { + min-width: 0 !important; + width: 100% !important; + box-sizing: border-box; + } + + .list-container.grid-mode .list-header { + display: none !important; + } + + .list-container.grid-mode .list-children-container { + display: flex; + flex-wrap: wrap; + gap: 15px; + padding-left: 0 !important; + margin-top: 10px; + margin-bottom: 15px; + width: 100%; + box-sizing: border-box; + } + + .list-container.grid-mode .list-children-container > .list-item-wrapper { + display: inline-block; + width: 160px; + margin: 0; + } + + .list-container.grid-mode .list-children-container > .list-item-wrapper > .list-item-row { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + /*background-color: var(--profiler-header); + border: 1px solid var(--profiler-border);*/ + border-radius: 6px; + padding: 8px; + gap: 8px; + width: 100%; + box-sizing: border-box; + grid-template-columns: none !important; + } + + .list-container.grid-mode .list-children-container > .list-item-wrapper > .list-item-row > .list-item-cell:first-child { + width: 140px; + height: 140px; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + } + + .list-container.grid-mode .list-children-container > .list-item-wrapper > .list-item-row > .list-item-cell:not(:first-child) { + width: 100%; + text-align: center !important; + font-size: 11px; + font-weight: 500; + color: var(--text-primary); + white-space: normal; + word-break: break-all; + justify-content: center !important; + } + } `; diff --git a/examples/webgpu_postprocessing_ao.html b/examples/webgpu_postprocessing_ao.html index fef87ec3a203fd..a8aead8d8cdea9 100644 --- a/examples/webgpu_postprocessing_ao.html +++ b/examples/webgpu_postprocessing_ao.html @@ -59,7 +59,7 @@ const params = { aoType: 'GTAO', samples: 16, - radius: 0.5, + radius: 0.4, resolutionScale: 0.5, scale: 0.8, // GTAO thickness: 1, // GTAO diff --git a/src/core/Object3D.js b/src/core/Object3D.js index 556ee3f4ac5848..42c9888d22a97c 100644 --- a/src/core/Object3D.js +++ b/src/core/Object3D.js @@ -1653,6 +1653,27 @@ class Object3D extends EventDispatcher { } + /** + * Frees the GPU-related resources allocated by this instance. Call this + * method whenever this instance is no longer used in your app. + * + * Geometries, materials and textures are potentially shared with other + * 3D objects and must be disposed of separately. + * + * @fires Object3D#dispose + */ + dispose() { + + /** + * Fires when the 3D object has been disposed of. + * + * @event Object3D#dispose + * @type {Object} + */ + this.dispatchEvent( { type: 'dispose' } ); + + } + } /** diff --git a/src/helpers/ArrowHelper.js b/src/helpers/ArrowHelper.js index e99afff8db4d5b..406b2388bc0cd2 100644 --- a/src/helpers/ArrowHelper.js +++ b/src/helpers/ArrowHelper.js @@ -159,6 +159,8 @@ class ArrowHelper extends Object3D { */ dispose() { + super.dispose(); + this.line.geometry.dispose(); this.line.material.dispose(); this.cone.geometry.dispose(); diff --git a/src/helpers/AxesHelper.js b/src/helpers/AxesHelper.js index 305eae5e5b21ef..0287a0f8e8e4ec 100644 --- a/src/helpers/AxesHelper.js +++ b/src/helpers/AxesHelper.js @@ -85,6 +85,8 @@ class AxesHelper extends LineSegments { */ dispose() { + super.dispose(); + this.geometry.dispose(); this.material.dispose(); diff --git a/src/helpers/Box3Helper.js b/src/helpers/Box3Helper.js index af0ceee8449349..7d70d9f2d3c8eb 100644 --- a/src/helpers/Box3Helper.js +++ b/src/helpers/Box3Helper.js @@ -73,6 +73,8 @@ class Box3Helper extends LineSegments { */ dispose() { + super.dispose(); + this.geometry.dispose(); this.material.dispose(); diff --git a/src/helpers/BoxHelper.js b/src/helpers/BoxHelper.js index ce41a78db7c772..46080feb6dd6bb 100644 --- a/src/helpers/BoxHelper.js +++ b/src/helpers/BoxHelper.js @@ -138,6 +138,8 @@ class BoxHelper extends LineSegments { */ dispose() { + super.dispose(); + this.geometry.dispose(); this.material.dispose(); diff --git a/src/helpers/CameraHelper.js b/src/helpers/CameraHelper.js index 2ff070c265f6d6..ac31e077c2fc54 100644 --- a/src/helpers/CameraHelper.js +++ b/src/helpers/CameraHelper.js @@ -314,6 +314,8 @@ class CameraHelper extends LineSegments { */ dispose() { + super.dispose(); + this.geometry.dispose(); this.material.dispose(); diff --git a/src/helpers/DirectionalLightHelper.js b/src/helpers/DirectionalLightHelper.js index b8f5e71ee80918..93d0ad2fb24d0b 100644 --- a/src/helpers/DirectionalLightHelper.js +++ b/src/helpers/DirectionalLightHelper.js @@ -103,6 +103,8 @@ class DirectionalLightHelper extends Object3D { */ dispose() { + super.dispose(); + this.lightPlane.geometry.dispose(); this.lightPlane.material.dispose(); this.targetLine.geometry.dispose(); diff --git a/src/helpers/GridHelper.js b/src/helpers/GridHelper.js index 78c4c02da5a3c1..b26576b4556758 100644 --- a/src/helpers/GridHelper.js +++ b/src/helpers/GridHelper.js @@ -71,6 +71,8 @@ class GridHelper extends LineSegments { */ dispose() { + super.dispose(); + this.geometry.dispose(); this.material.dispose(); diff --git a/src/helpers/HemisphereLightHelper.js b/src/helpers/HemisphereLightHelper.js index b64cbd004637d0..119a8ea1cc088e 100644 --- a/src/helpers/HemisphereLightHelper.js +++ b/src/helpers/HemisphereLightHelper.js @@ -82,6 +82,8 @@ class HemisphereLightHelper extends Object3D { */ dispose() { + super.dispose(); + this.children[ 0 ].geometry.dispose(); this.children[ 0 ].material.dispose(); diff --git a/src/helpers/PlaneHelper.js b/src/helpers/PlaneHelper.js index f1f36324c0c938..4aadd716fea2c9 100644 --- a/src/helpers/PlaneHelper.js +++ b/src/helpers/PlaneHelper.js @@ -84,6 +84,8 @@ class PlaneHelper extends Line { */ dispose() { + super.dispose(); + this.geometry.dispose(); this.material.dispose(); this.children[ 0 ].geometry.dispose(); diff --git a/src/helpers/PointLightHelper.js b/src/helpers/PointLightHelper.js index 43793ab1260823..37c0f3235f6555 100644 --- a/src/helpers/PointLightHelper.js +++ b/src/helpers/PointLightHelper.js @@ -65,6 +65,8 @@ class PointLightHelper extends Mesh { */ dispose() { + super.dispose(); + this.geometry.dispose(); this.material.dispose(); diff --git a/src/helpers/PolarGridHelper.js b/src/helpers/PolarGridHelper.js index 90994e03ebc476..7dd54eda976611 100644 --- a/src/helpers/PolarGridHelper.js +++ b/src/helpers/PolarGridHelper.js @@ -115,6 +115,8 @@ class PolarGridHelper extends LineSegments { */ dispose() { + super.dispose(); + this.geometry.dispose(); this.material.dispose(); diff --git a/src/helpers/SkeletonHelper.js b/src/helpers/SkeletonHelper.js index 4ea9cbf191b818..05cc2ca0d003b3 100644 --- a/src/helpers/SkeletonHelper.js +++ b/src/helpers/SkeletonHelper.js @@ -162,6 +162,8 @@ class SkeletonHelper extends LineSegments { */ dispose() { + super.dispose(); + this.geometry.dispose(); this.material.dispose(); diff --git a/src/helpers/SpotLightHelper.js b/src/helpers/SpotLightHelper.js index fef093d40d602e..fa2be33037feed 100644 --- a/src/helpers/SpotLightHelper.js +++ b/src/helpers/SpotLightHelper.js @@ -95,6 +95,8 @@ class SpotLightHelper extends Object3D { */ dispose() { + super.dispose(); + this.cone.geometry.dispose(); this.cone.material.dispose(); diff --git a/src/lights/Light.js b/src/lights/Light.js index 4d81b98fe10c5a..60f8757d2a01fc 100644 --- a/src/lights/Light.js +++ b/src/lights/Light.js @@ -48,16 +48,6 @@ class Light extends Object3D { } - /** - * Frees the GPU-related resources allocated by this instance. Call this - * method whenever this instance is no longer used in your app. - */ - dispose() { - - this.dispatchEvent( { type: 'dispose' } ); - - } - copy( source, recursive ) { super.copy( source, recursive ); diff --git a/src/objects/BatchedMesh.js b/src/objects/BatchedMesh.js index 7ab4e4756a46fb..9dc076b34e4480 100644 --- a/src/objects/BatchedMesh.js +++ b/src/objects/BatchedMesh.js @@ -1498,6 +1498,8 @@ class BatchedMesh extends Mesh { */ dispose() { + super.dispose(); + // Assuming the geometry is not shared with other meshes this.geometry.dispose(); diff --git a/src/objects/InstancedMesh.js b/src/objects/InstancedMesh.js index 7c29cc68873fe3..d55dc5d1311200 100644 --- a/src/objects/InstancedMesh.js +++ b/src/objects/InstancedMesh.js @@ -395,7 +395,7 @@ class InstancedMesh extends Mesh { */ dispose() { - this.dispatchEvent( { type: 'dispose' } ); + super.dispose(); if ( this.morphTexture !== null ) { diff --git a/src/renderers/common/RenderObject.js b/src/renderers/common/RenderObject.js index c900fa4c9654f8..8c1091da73db2f 100644 --- a/src/renderers/common/RenderObject.js +++ b/src/renderers/common/RenderObject.js @@ -343,6 +343,19 @@ class RenderObject { }; + /** + * An event listener which is executed when `dispose()` is called on + * the 3D object of this render object. + * + * @method + */ + this.onObjectDispose = () => { + + this.dispose(); + + }; + + this.object.addEventListener( 'dispose', this.onObjectDispose ); this.material.addEventListener( 'dispose', this.onMaterialDispose ); this.geometry.addEventListener( 'dispose', this.onGeometryDispose ); @@ -957,6 +970,7 @@ class RenderObject { */ dispose() { + this.object.removeEventListener( 'dispose', this.onObjectDispose ); this.material.removeEventListener( 'dispose', this.onMaterialDispose ); this.geometry.removeEventListener( 'dispose', this.onGeometryDispose ); diff --git a/src/renderers/webgl-fallback/utils/WebGLState.js b/src/renderers/webgl-fallback/utils/WebGLState.js index eea31cd9c07479..beae95be961b5b 100644 --- a/src/renderers/webgl-fallback/utils/WebGLState.js +++ b/src/renderers/webgl-fallback/utils/WebGLState.js @@ -1,7 +1,7 @@ import { CullFaceNone, CullFaceBack, CullFaceFront, DoubleSide, BackSide, NormalBlending, NoBlending, CustomBlending, AddEquation, - AdditiveBlending, SubtractiveBlending, MultiplyBlending, SubtractEquation, ReverseSubtractEquation, + AdditiveBlending, SubtractiveBlending, MultiplyBlending, SubtractEquation, ReverseSubtractEquation, MinEquation, MaxEquation, ZeroFactor, OneFactor, SrcColorFactor, SrcAlphaFactor, SrcAlphaSaturateFactor, DstColorFactor, DstAlphaFactor, OneMinusSrcColorFactor, OneMinusSrcAlphaFactor, OneMinusDstColorFactor, OneMinusDstAlphaFactor, NeverDepth, AlwaysDepth, LessDepth, LessEqualDepth, EqualDepth, GreaterEqualDepth, GreaterDepth, NotEqualDepth, @@ -108,7 +108,9 @@ class WebGLState { equationToGL = { [ AddEquation ]: gl.FUNC_ADD, [ SubtractEquation ]: gl.FUNC_SUBTRACT, - [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT + [ ReverseSubtractEquation ]: gl.FUNC_REVERSE_SUBTRACT, + [ MinEquation ]: gl.MIN, + [ MaxEquation ]: gl.MAX }; factorToGL = {