diff --git a/examples/jsm/exporters/GLTFExporter.js b/examples/jsm/exporters/GLTFExporter.js index 0c8964dbf53cf1..dd18fff8e84d59 100644 --- a/examples/jsm/exporters/GLTFExporter.js +++ b/examples/jsm/exporters/GLTFExporter.js @@ -652,7 +652,8 @@ class GLTFWriter { onlyVisible: true, maxTextureSize: Infinity, animations: [], - includeCustomExtensions: false + includeCustomExtensions: false, + copyright: null }, options ); if ( this.options.animations.length > 0 ) { @@ -687,6 +688,8 @@ class GLTFWriter { // Update bytelength of the single buffer. if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size; + if ( options.copyright ) json.asset.copyright = options.copyright; + if ( options.binary === true ) { // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification @@ -3833,6 +3836,7 @@ GLTFExporter.Utils = { * @property {Array|Array>} [animations=[]] - List of animations to be included in the export. When exporting a single 3D object or scene, this is a flat list of clips. * When exporting an array of multiple scenes, this must be a nested array with one list of clips per scene, matched to the input by index. * @property {boolean} [includeCustomExtensions=false] - Export custom glTF extensions defined on an object's `userData.gltfExtensions` property. + * @property {string} [copyright=null] - Export with a copyright notice embedded in the glTF. **/ /** diff --git a/examples/jsm/inspector/Inspector.js b/examples/jsm/inspector/Inspector.js index fe97227600b63a..4ae0b318e4eed2 100644 --- a/examples/jsm/inspector/Inspector.js +++ b/examples/jsm/inspector/Inspector.js @@ -135,6 +135,20 @@ class Inspector extends RendererInspector { } + setVisible( value ) { + + this.domElement.style.display = value ? '' : 'none'; + + return this; + + } + + getVisible() { + + return this.domElement.style.display !== 'none'; + + } + getSize() { return this.profiler.getSize(); @@ -149,6 +163,22 @@ class Inspector extends RendererInspector { } + setHorizontalAlign( value ) { + + this.profiler.setHorizontalAlign( value ); + + return this; + + } + + setVerticalAlign( value ) { + + this.profiler.setVerticalAlign( value ); + + return this; + + } + addTab( tab ) { this.profiler.addTab( tab ); diff --git a/examples/jsm/inspector/ui/Profiler.js b/examples/jsm/inspector/ui/Profiler.js index 67562ca4208bca..138ae798d710ca 100644 --- a/examples/jsm/inspector/ui/Profiler.js +++ b/examples/jsm/inspector/ui/Profiler.js @@ -20,11 +20,16 @@ export class Profiler extends EventDispatcher { this.maxZIndex = 1002; // Track the highest z-index for detached windows (starts at base z-index from CSS) this.nextTabOriginalIndex = 0; // Track the original order of tabs as they are added + this.horizontalAlign = 'right'; // 'left' or 'right' + this.verticalAlign = 'top'; // 'top' or 'bottom' + this.setupShell(); this.setupResizing(); Style.init( this.domElement ); + this.updateWidgetPosition(); + // Setup window resize listener and update mobile status this.setupWindowResizeListener(); @@ -495,13 +500,13 @@ export class Profiler extends EventDispatcher { // Maximize based on current position if ( this.position === 'bottom' ) { - this.panel.style.height = '100vh'; + this.panel.style.height = '100%'; this.panel.style.width = '100%'; } else if ( this.position === 'right' ) { this.panel.style.height = '100%'; - this.panel.style.width = '100vw'; + this.panel.style.width = '100%'; } @@ -509,6 +514,8 @@ export class Profiler extends EventDispatcher { } + this.updateWidgetPosition(); + this.dispatchEvent( { type: 'resize' } ); } @@ -1691,6 +1698,8 @@ export class Profiler extends EventDispatcher { } + this.updateWidgetPosition(); + this.dispatchEvent( { type: 'resize' } ); this.saveLayout(); @@ -1723,8 +1732,6 @@ export class Profiler extends EventDispatcher { // Apply right position styles this.panel.classList.remove( 'position-bottom' ); this.panel.classList.add( 'position-right' ); - this.toggleButton.classList.add( 'position-right' ); - this.miniPanel.classList.add( 'position-right' ); this.panel.style.bottom = ''; this.panel.style.top = '0'; this.panel.style.right = '0'; @@ -1733,7 +1740,7 @@ export class Profiler extends EventDispatcher { // Apply size based on maximized state if ( isMaximized ) { - this.panel.style.width = '100vw'; + this.panel.style.width = '100%'; this.panel.style.height = '100%'; } else { @@ -1753,8 +1760,6 @@ export class Profiler extends EventDispatcher { // Apply bottom position styles this.panel.classList.remove( 'position-right' ); this.panel.classList.add( 'position-bottom' ); - this.toggleButton.classList.remove( 'position-right' ); - this.miniPanel.classList.remove( 'position-right' ); this.panel.style.top = ''; this.panel.style.right = ''; this.panel.style.bottom = '0'; @@ -1764,7 +1769,7 @@ export class Profiler extends EventDispatcher { if ( isMaximized ) { this.panel.style.width = '100%'; - this.panel.style.height = '100vh'; + this.panel.style.height = '100%'; } else { @@ -1775,6 +1780,8 @@ export class Profiler extends EventDispatcher { } + this.updateWidgetPosition(); + // Re-enable transition after a brief delay setTimeout( () => { @@ -2117,4 +2124,94 @@ export class Profiler extends EventDispatcher { } + setHorizontalAlign( value ) { + + this.horizontalAlign = value; + this.updateWidgetPosition(); + + return this; + + } + + setVerticalAlign( value ) { + + this.verticalAlign = value; + this.updateWidgetPosition(); + + return this; + + } + + updateWidgetPosition() { + + const isVisible = this.panel.classList.contains( 'visible' ); + const isMaximized = this.panel.classList.contains( 'maximized' ); + const isRight = this.position === 'right'; + + let horizontal = this.horizontalAlign; // 'left' or 'right' + let vertical = this.verticalAlign; // 'top' or 'bottom' + + if ( isVisible ) { + + if ( isRight ) { + + // If panel is open on the right: + // Toggle should be on the opposite side of 'right' if default is 'right' to avoid overlapping the panel. + if ( this.horizontalAlign === 'right' ) { + + horizontal = 'left'; + + } + + } else { + + // If panel is open at the bottom: + if ( ! isMaximized ) { + + // If default is bottom, we must move to top to avoid overlapping the panel at the bottom. + if ( this.verticalAlign === 'bottom' ) { + + vertical = 'top'; + + } + + } else { + + // If maximized, move to the opposite vertical position to avoid overlap with header/tabs. + vertical = this.verticalAlign === 'top' ? 'bottom' : 'top'; + + } + + } + + } + + // Apply horizontal class + if ( horizontal === 'left' ) { + + this.toggleButton.classList.add( 'toggle-left' ); + this.miniPanel.classList.add( 'toggle-left' ); + + } else { + + this.toggleButton.classList.remove( 'toggle-left' ); + this.miniPanel.classList.remove( 'toggle-left' ); + + } + + // Apply vertical class + if ( vertical === 'bottom' ) { + + this.toggleButton.classList.add( 'toggle-bottom' ); + this.miniPanel.classList.add( 'toggle-bottom' ); + + } else { + + this.toggleButton.classList.remove( 'toggle-bottom' ); + this.miniPanel.classList.remove( 'toggle-bottom' ); + + } + + } + } diff --git a/examples/jsm/inspector/ui/Style.js b/examples/jsm/inspector/ui/Style.js index 7f77e94a6b60b3..c4d888de0427a3 100644 --- a/examples/jsm/inspector/ui/Style.js +++ b/examples/jsm/inspector/ui/Style.js @@ -20,6 +20,18 @@ export class Style { --color-call: rgba(255, 185, 34, 1); --font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; --font-mono: 'Courier New', Courier, monospace; + + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 1000; + } + + :scope * { + pointer-events: auto; } .profiler-panel, .profiler-toggle, .detached-tab-panel, @@ -33,7 +45,7 @@ export class Style { } .profiler-toggle { - position: fixed; + position: absolute; top: 15px; right: 15px; background-color: rgba(30, 30, 36, 0.85); @@ -68,14 +80,14 @@ export class Style { opacity: 0.5; } - .profiler-toggle.position-right.panel-open { + .profiler-toggle.toggle-left { right: auto; left: 15px; border-radius: 6px 12px 12px 6px; flex-direction: row-reverse; } - .profiler-toggle.position-right.panel-open .builtin-tabs-container { + .profiler-toggle.toggle-left .builtin-tabs-container { border-right: none; border-left: 1px solid #262636; } @@ -227,7 +239,7 @@ export class Style { } .profiler-mini-panel { - position: fixed; + position: absolute; top: 60px; right: 15px; background-color: rgba(30, 30, 36, 0.85); @@ -252,7 +264,7 @@ export class Style { transform 0.25s cubic-bezier(0.4, 0, 0.2, 1); } - .profiler-mini-panel.position-right.panel-open { + .profiler-mini-panel.toggle-left { right: auto; left: 15px; } @@ -263,20 +275,14 @@ export class Style { transform: translateY(0) scale(1); } - /* Position toggle and mini-panel at the bottom when maximized */ - :scope:has(.profiler-panel.maximized) .profiler-toggle, - :scope.maximized .profiler-toggle { - top: auto !important; - bottom: 15px !important; - z-index: 10005 !important; + .profiler-toggle.toggle-bottom { + top: auto; + bottom: 15px; } - :scope:has(.profiler-panel.maximized) .profiler-mini-panel, - :scope.maximized .profiler-mini-panel { - top: auto !important; - bottom: 60px !important; - max-height: calc(100vh - 120px) !important; - z-index: 10006 !important; + .profiler-mini-panel.toggle-bottom { + top: auto; + bottom: 60px; } .profiler-mini-panel::-webkit-scrollbar { @@ -560,7 +566,7 @@ export class Style { } .profiler-panel { - position: fixed; + position: absolute; z-index: 1001 !important; bottom: 0; left: 0; @@ -589,7 +595,7 @@ export class Style { } .profiler-panel.maximized { - height: 100vh; + height: 100%; } /* Position-specific styles */ @@ -1689,7 +1695,7 @@ export class Style { } .drag-preview-indicator { - position: fixed; + position: absolute; background-color: rgba(0, 170, 255, 0.2); border: 2px dashed var(--color-accent); z-index: 999; @@ -1699,7 +1705,7 @@ export class Style { /* Detached Tab Windows */ .detached-tab-panel { - position: fixed; + position: absolute; width: 500px; height: 400px; background: var(--profiler-background);