diff --git a/editor/js/Menubar.Add.js b/editor/js/Menubar.Add.js index b49c18a23c3499..b097e502416d9e 100644 --- a/editor/js/Menubar.Add.js +++ b/editor/js/Menubar.Add.js @@ -3,6 +3,7 @@ import * as THREE from 'three'; import { UIPanel, UIRow } from './libs/ui.js'; import { AddObjectCommand } from './commands/AddObjectCommand.js'; +import { MultiCmdsCommand } from './commands/MultiCmdsCommand.js'; import { FontLoader } from 'three/addons/loaders/FontLoader.js'; import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'; @@ -419,7 +420,10 @@ function MenubarAdd( editor ) { light.position.set( 5, 10, 7.5 ); - editor.execute( new AddObjectCommand( editor, light ) ); + editor.execute( new MultiCmdsCommand( editor, [ + new AddObjectCommand( editor, light.target ), + new AddObjectCommand( editor, light ) + ] ) ); } ); lightSubmenu.add( option ); @@ -483,7 +487,10 @@ function MenubarAdd( editor ) { light.position.set( 5, 10, 7.5 ); - editor.execute( new AddObjectCommand( editor, light ) ); + editor.execute( new MultiCmdsCommand( editor, [ + new AddObjectCommand( editor, light.target ), + new AddObjectCommand( editor, light ) + ] ) ); } ); lightSubmenu.add( option ); diff --git a/editor/js/Menubar.Edit.js b/editor/js/Menubar.Edit.js index e4a5d517daef94..6be689bb8482f7 100644 --- a/editor/js/Menubar.Edit.js +++ b/editor/js/Menubar.Edit.js @@ -4,6 +4,7 @@ import { clone } from 'three/addons/utils/SkeletonUtils.js'; import { UIPanel, UIRow, UIHorizontalRule, UIText } from './libs/ui.js'; import { AddObjectCommand } from './commands/AddObjectCommand.js'; +import { MultiCmdsCommand } from './commands/MultiCmdsCommand.js'; import { RemoveObjectCommand } from './commands/RemoveObjectCommand.js'; import { SetPositionCommand } from './commands/SetPositionCommand.js'; @@ -129,7 +130,16 @@ function MenubarEdit( editor ) { const object = editor.selected; - if ( object !== null && object.parent !== null ) { + if ( object === null || object.parent === null ) return; + + if ( object.isSpotLight || object.isDirectionalLight ) { + + editor.execute( new MultiCmdsCommand( editor, [ + new RemoveObjectCommand( editor, object ), + new RemoveObjectCommand( editor, object.target ) + ] ) ); + + } else { editor.execute( new RemoveObjectCommand( editor, object ) ); diff --git a/editor/js/Sidebar.Settings.Shortcuts.js b/editor/js/Sidebar.Settings.Shortcuts.js index 8e96b03eca2149..f09e0a9e9ee486 100644 --- a/editor/js/Sidebar.Settings.Shortcuts.js +++ b/editor/js/Sidebar.Settings.Shortcuts.js @@ -1,5 +1,6 @@ import { UIPanel, UIText, UIRow, UIInput } from './libs/ui.js'; +import { MultiCmdsCommand } from './commands/MultiCmdsCommand.js'; import { RemoveObjectCommand } from './commands/RemoveObjectCommand.js'; function SidebarSettingsShortcuts( editor ) { @@ -109,10 +110,20 @@ function SidebarSettingsShortcuts( editor ) { const object = editor.selected; - if ( object === null ) return; + if ( object === null || object.parent === null ) return; - const parent = object.parent; - if ( parent !== null ) editor.execute( new RemoveObjectCommand( editor, object ) ); + if ( object.isSpotLight || object.isDirectionalLight ) { + + editor.execute( new MultiCmdsCommand( editor, [ + new RemoveObjectCommand( editor, object ), + new RemoveObjectCommand( editor, object.target ) + ] ) ); + + } else { + + editor.execute( new RemoveObjectCommand( editor, object ) ); + + } break; diff --git a/editor/js/Viewport.js b/editor/js/Viewport.js index 2cb7855a3ce8ef..a4b0eb12cc047d 100644 --- a/editor/js/Viewport.js +++ b/editor/js/Viewport.js @@ -498,6 +498,20 @@ function Viewport( editor ) { } + // update light helper when light target is changed + + for ( const id in editor.helpers ) { + + const helper = editor.helpers[ id ]; + + if ( helper.light && helper.light.target === object ) { + + helper.update(); + + } + + } + initPT(); render(); diff --git a/examples/jsm/exporters/USDZExporter.js b/examples/jsm/exporters/USDZExporter.js index ff6777166ede6c..2d3ae16dcb696c 100644 --- a/examples/jsm/exporters/USDZExporter.js +++ b/examples/jsm/exporters/USDZExporter.js @@ -284,11 +284,14 @@ class USDZExporter { texture.flipY, options.maxTextureSize ); + + const mimeType = ( texture.userData.mimeType === 'image/jpeg' ) ? 'image/jpeg' : 'image/png'; + const blob = await new Promise( ( resolve ) => - canvas.toBlob( resolve, 'image/png', 1 ) + canvas.toBlob( resolve, mimeType ) ); - files[ `textures/Texture_${id}.png` ] = new Uint8Array( + files[ `textures/Texture_${id}.${getTextureExtension( texture )}` ] = new Uint8Array( await blob.arrayBuffer() ); @@ -363,6 +366,12 @@ function getName( object, namesSet ) { } +function getTextureExtension( texture ) { + + return texture.userData.mimeType === 'image/jpeg' ? 'jpg' : 'png'; + +} + function imageToCanvas( image, flipY, maxTextureSize ) { if ( @@ -839,14 +848,15 @@ function buildMaterial( material, textures, quickLookCompatible = false ) { 'Shader' ); textureNode.addProperty( 'uniform token info:id = "UsdUVTexture"' ); - textureNode.addProperty( `asset inputs:file = @textures/Texture_${id}.png@` ); + textureNode.addProperty( `asset inputs:file = @textures/Texture_${id}.${getTextureExtension( texture )}@` ); textureNode.addProperty( `float2 inputs:st.connect = ` ); if ( color !== undefined ) { - textureNode.addProperty( `float4 inputs:scale = ${buildColor4( color )}` ); + const alpha = ( mapType === 'diffuse' ) ? material.opacity : 1; + textureNode.addProperty( `float4 inputs:scale = ${buildColor4( color, alpha )}` ); } @@ -1137,9 +1147,9 @@ function buildColor( color ) { } -function buildColor4( color ) { +function buildColor4( color, alpha = 1 ) { - return `(${color.r}, ${color.g}, ${color.b}, 1.0)`; + return `(${color.r}, ${color.g}, ${color.b}, ${alpha})`; } diff --git a/examples/jsm/loaders/usd/USDAParser.js b/examples/jsm/loaders/usd/USDAParser.js index 174438265e0064..510cedfc758aff 100644 --- a/examples/jsm/loaders/usd/USDAParser.js +++ b/examples/jsm/loaders/usd/USDAParser.js @@ -675,12 +675,23 @@ class USDAParser { // Parse value based on type const parsedValue = this._parseAttributeValue( valueType, rawValue ); - // Store as attribute spec + // Store as attribute spec, preserving any existing fields + // (e.g. connectionPaths set by an earlier `.connect` form) const attrPath = path + '.' + attrName; - specsByPath[ attrPath ] = { - specType: SpecType.Attribute, - fields: { default: parsedValue, typeName: valueType } - }; + + if ( specsByPath[ attrPath ] ) { + + specsByPath[ attrPath ].fields.default = parsedValue; + specsByPath[ attrPath ].fields.typeName = valueType; + + } else { + + specsByPath[ attrPath ] = { + specType: SpecType.Attribute, + fields: { default: parsedValue, typeName: valueType } + }; + + } } diff --git a/examples/jsm/tsl/display/DepthOfFieldNode.js b/examples/jsm/tsl/display/DepthOfFieldNode.js index 66e1f7f913f824..be86d887393663 100644 --- a/examples/jsm/tsl/display/DepthOfFieldNode.js +++ b/examples/jsm/tsl/display/DepthOfFieldNode.js @@ -1,5 +1,5 @@ import { TempNode, NodeMaterial, NodeUpdateType, RenderTarget, Vector2, HalfFloatType, RedFormat, QuadMesh, RendererUtils } from 'three/webgpu'; -import { convertToTexture, nodeObject, Fn, uniform, smoothstep, step, texture, max, uniformArray, outputStruct, property, vec4, vec3, uv, Loop, min, mix } from 'three/tsl'; +import { convertToTexture, nodeObject, Fn, uniform, smoothstep, step, texture, max, uniformArray, outputStruct, property, vec4, vec3, uv, Loop, min, mix, float } from 'three/tsl'; import { gaussianBlur } from './GaussianBlurNode.js'; const _quadMesh = /*@__PURE__*/ new QuadMesh(); @@ -368,7 +368,7 @@ class DepthOfFieldNode extends TempNode { nearField.assign( step( signedDist, 0 ).mul( CoC ) ); farField.assign( step( 0, signedDist ).mul( CoC ) ); - return vec4( 0 ); + return float( 0 ); } ); diff --git a/src/materials/nodes/NodeMaterial.js b/src/materials/nodes/NodeMaterial.js index be6de97edb3ac3..5efbcbc3edc539 100644 --- a/src/materials/nodes/NodeMaterial.js +++ b/src/materials/nodes/NodeMaterial.js @@ -596,7 +596,7 @@ class NodeMaterial extends Material { if ( fragmentNode.isOutputStructNode !== true ) { - fragmentNode = vec4( fragmentNode ); + fragmentNode = fragmentNode.convert( builder.getOutputType() ); } diff --git a/src/nodes/core/MRTNode.js b/src/nodes/core/MRTNode.js index 74c23f3bcf26b4..b6d46ba751ef1f 100644 --- a/src/nodes/core/MRTNode.js +++ b/src/nodes/core/MRTNode.js @@ -1,5 +1,5 @@ import OutputStructNode from './OutputStructNode.js'; -import { nodeProxy, vec4 } from '../tsl/TSLBase.js'; +import { nodeProxy } from '../tsl/TSLBase.js'; import { MaterialBlending, NoBlending } from '../../constants.js'; import BlendMode from '../../renderers/common/BlendMode.js'; @@ -170,8 +170,9 @@ class MRTNode extends OutputStructNode { for ( const name in outputNodes ) { const index = getTextureIndex( textures, name ); + const type = builder.getOutputType( index ); - members[ index ] = vec4( outputNodes[ name ] ); + members[ index ] = outputNodes[ name ].convert( type ); } diff --git a/src/nodes/core/NodeBuilder.js b/src/nodes/core/NodeBuilder.js index 14e8e79bdd4b31..638edda147e6f3 100644 --- a/src/nodes/core/NodeBuilder.js +++ b/src/nodes/core/NodeBuilder.js @@ -23,7 +23,7 @@ import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; import BindGroup from '../../renderers/common/BindGroup.js'; -import { REVISION, IntType, UnsignedIntType, LinearFilter, LinearMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapLinearFilter, NormalBlending } from '../../constants.js'; +import { REVISION, IntType, UnsignedIntType, LinearFilter, LinearMipmapNearestFilter, NearestMipmapLinearFilter, LinearMipmapLinearFilter, NormalBlending, RedFormat, RGFormat, RGBFormat, RedIntegerFormat, RGIntegerFormat, RGBIntegerFormat } from '../../constants.js'; import { RenderTarget } from '../../core/RenderTarget.js'; import { Color } from '../../math/Color.js'; import { Vector2 } from '../../math/Vector2.js'; @@ -535,6 +535,61 @@ class NodeBuilder { } + /** + * Returns the type of the color output based on the renderer's render target. + * + * @param {number} [index=0] - The index of the render target texture. + * @return {string} The type. + */ + getOutputType( index = 0 ) { + + let type = 'vec4'; + + const renderTarget = this.renderer.getRenderTarget(); + + if ( renderTarget !== null ) { + + const renderTargetType = renderTarget.textures[ index ].type; + const renderTargetFormat = renderTarget.textures[ index ].format; + + let typeStr = 'vec'; + + if ( renderTargetType === IntType ) { + + typeStr = 'ivec'; + + } else if ( renderTargetType === UnsignedIntType ) { + + typeStr = 'uvec'; + + } + + if ( renderTargetFormat === RedFormat || renderTargetFormat === RedIntegerFormat ) { + + if ( renderTargetType === IntType ) type = 'int'; + else if ( renderTargetType === UnsignedIntType ) type = 'uint'; + else type = 'float'; + + } else if ( renderTargetFormat === RGFormat || renderTargetFormat === RGIntegerFormat ) { + + type = `${ typeStr }2`; + + } else if ( renderTargetFormat === RGBFormat || renderTargetFormat === RGBIntegerFormat ) { + + type = `${ typeStr }3`; + + } else { + + type = `${ typeStr }4`; + + } + + } + + return type; + + } + /** * Returns the output struct name which is required by * {@link OutputStructNode}. @@ -1469,12 +1524,10 @@ class NodeBuilder { const type = texture.type; - if ( texture.isDataTexture ) { + if ( texture.isDepthTexture === true ) return 'float'; - if ( type === IntType ) return 'int'; - if ( type === UnsignedIntType ) return 'uint'; - - } + if ( type === IntType ) return 'int'; + if ( type === UnsignedIntType ) return 'uint'; return 'float'; diff --git a/src/nodes/core/OutputStructNode.js b/src/nodes/core/OutputStructNode.js index 2413076a46b949..7c81d0a048ba88 100644 --- a/src/nodes/core/OutputStructNode.js +++ b/src/nodes/core/OutputStructNode.js @@ -80,7 +80,7 @@ class OutputStructNode extends Node { for ( let i = 0; i < members.length; i ++ ) { - const snippet = members[ i ].build( builder ); + const snippet = members[ i ].build( builder, nodeData.membersLayout[ i ].type ); builder.addLineFlowCode( `${ structPrefix }m${ i } = ${ snippet }`, this ); diff --git a/src/nodes/core/PropertyNode.js b/src/nodes/core/PropertyNode.js index f42708ab47c332..1c379328ae2f8c 100644 --- a/src/nodes/core/PropertyNode.js +++ b/src/nodes/core/PropertyNode.js @@ -69,6 +69,20 @@ class PropertyNode extends Node { } + getNodeType( builder ) { + + const nodeType = super.getNodeType( builder ); + + if ( nodeType === 'output' ) { + + return builder.getOutputType(); + + } + + return nodeType; + + } + customCacheKey() { return hashString( this.type + ':' + ( this.name || '' ) + ':' + ( this.varying ? '1' : '0' ) ); @@ -292,7 +306,7 @@ export const shininess = /*@__PURE__*/ nodeImmutable( PropertyNode, 'float', 'Sh * @tsl * @type {PropertyNode} */ -export const output = /*@__PURE__*/ nodeImmutable( PropertyNode, 'vec4', 'Output' ); +export const output = /*@__PURE__*/ nodeImmutable( PropertyNode, 'output', 'Output' ); /** * TSL object that represents the shader variable `dashSize`. diff --git a/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js b/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js index a2653e6679c4d2..e35c9dc8ae3ba1 100644 --- a/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +++ b/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js @@ -1075,7 +1075,7 @@ ${ flowData.code } if ( shaderStage === 'fragment' && outputSnippet.length === 0 ) { - outputSnippet.push( 'layout( location = 0 ) out vec4 fragColor;' ); + outputSnippet.push( `layout( location = 0 ) out ${ this.getOutputType() } fragColor;` ); } @@ -1633,14 +1633,14 @@ void main() { if ( shaderStage === 'vertex' ) { flow += 'gl_Position = '; - flow += `${ flowSlotData.result };`; + flow += `${ this.format( flowSlotData.result, mainNode.getNodeType( this ), 'vec4' ) };`; } else if ( shaderStage === 'fragment' ) { if ( ! node.outputNode.isOutputStructNode ) { flow += 'fragColor = '; - flow += `${ flowSlotData.result };`; + flow += `${ this.format( flowSlotData.result, mainNode.getNodeType( this ), this.getOutputType() ) };`; } diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js index b640bfb959f531..bbc543cd4743c1 100644 --- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -2223,7 +2223,7 @@ ${ flowData.code } } else { - let structSnippet = '\t@location( 0 ) color: vec4'; + let structSnippet = `\t@location( 0 ) color: ${ this.getType( this.getOutputType() ) }`; const builtins = this.getBuiltins( 'output' ); @@ -2233,7 +2233,7 @@ ${ flowData.code } stageData.structs += this._getWGSLStruct( 'OutputStruct', structSnippet ); stageData.structs += '\nvar output : OutputStruct;'; - flow += `output.color = ${ flowSlotData.result };\n\n\treturn output;`; + flow += `output.color = ${ this.format( flowSlotData.result, mainNode.getNodeType( this ), this.getOutputType() ) };\n\n\treturn output;`; } diff --git a/src/renderers/webgpu/utils/WebGPUBindingUtils.js b/src/renderers/webgpu/utils/WebGPUBindingUtils.js index fb3c7f5c8b567c..54a84971ba8e53 100644 --- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -506,7 +506,7 @@ class WebGPUBindingUtils { } - } else if ( binding.texture.isDataTexture || binding.texture.isDataArrayTexture || binding.texture.isData3DTexture || binding.texture.isStorageTexture ) { + } else { const type = binding.texture.type; diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js index a0bc2b40bebab9..eb67d1768da404 100644 --- a/test/e2e/puppeteer.js +++ b/test/e2e/puppeteer.js @@ -35,6 +35,7 @@ const exceptionList = [ 'webgl_shadowmap', 'webaudio_visualizer', 'webgpu_compute_audio', + 'webgpu_compute_cloth', 'webgpu_compute_sort_bitonic', 'webgpu_storage_buffer', 'webgpu_tsl_editor',