Explorar o código

NodeEditor: FileURLEditor + MatcapUVEditor (#23705)

* add MatcapUVNode

* FileURLEditor + MatcapUVEditor

* WebGPUNodeBuilder: fix scene lights after https://github.com/mrdoob/three.js/pull/23652

* fix unserialize

* cleanup

* cleanup(2)

* normalize x
sunag %!s(int64=3) %!d(string=hai) anos
pai
achega
36ca872385

+ 16 - 1
examples/jsm/node-editor/NodeEditor.js

@@ -20,6 +20,7 @@ import { TextureEditor } from './inputs/TextureEditor.js';
 import { BlendEditor } from './display/BlendEditor.js';
 import { NormalMapEditor } from './display/NormalMapEditor.js';
 import { UVEditor } from './accessors/UVEditor.js';
+import { MatcapUVEditor } from './accessors/MatcapUVEditor.js';
 import { PositionEditor } from './accessors/PositionEditor.js';
 import { NormalEditor } from './accessors/NormalEditor.js';
 import { PreviewEditor } from './utils/PreviewEditor.js';
@@ -31,6 +32,7 @@ import { CheckerEditor } from './procedural/CheckerEditor.js';
 import { PointsEditor } from './scene/PointsEditor.js';
 import { MeshEditor } from './scene/MeshEditor.js';
 import { FileEditor } from './core/FileEditor.js';
+import { FileURLEditor } from './core/FileURLEditor.js';
 import { EventDispatcher } from 'three';
 
 Styles.icons.unlink = 'ti ti-unlink';
@@ -74,6 +76,11 @@ export const NodeList = [
 				name: 'Texture',
 				icon: 'photo',
 				nodeClass: TextureEditor
+			},
+			{
+				name: 'File URL',
+				icon: 'cloud-download',
+				nodeClass: FileURLEditor
 			}
 		]
 	},
@@ -95,6 +102,11 @@ export const NodeList = [
 				name: 'Normal',
 				icon: 'fold-up',
 				nodeClass: NormalEditor
+			},
+			{
+				name: 'Matcap UV',
+				icon: 'circle',
+				nodeClass: MatcapUVEditor
 			}
 		]
 	},
@@ -264,13 +276,15 @@ export const ClassLib = {
 	BlendEditor,
 	NormalMapEditor,
 	UVEditor,
+	MatcapUVEditor,
 	PositionEditor,
 	NormalEditor,
 	TimerEditor,
 	OscillatorEditor,
 	SplitEditor,
 	JoinEditor,
-	CheckerEditor
+	CheckerEditor,
+	FileURLEditor
 };
 
 export class NodeEditor extends EventDispatcher {
@@ -527,6 +541,7 @@ export class NodeEditor extends EventDispatcher {
 		addExample( basicContext, 'Animate UV' );
 		addExample( basicContext, 'Fake top light' );
 		addExample( basicContext, 'Oscillator color' );
+		addExample( basicContext, 'Matcap' );
 
 		addExample( advancedContext, 'Rim' );
 

+ 14 - 0
examples/jsm/node-editor/accessors/MatcapUVEditor.js

@@ -0,0 +1,14 @@
+import { BaseNode } from '../core/BaseNode.js';
+import { MatcapUVNode } from 'three-nodes/Nodes.js';
+
+export class MatcapUVEditor extends BaseNode {
+
+	constructor() {
+
+		const node = new MatcapUVNode();
+
+		super( 'Matcap UV', 2, node, 200 );
+
+	}
+
+}

+ 1 - 1
examples/jsm/node-editor/core/BaseNode.js

@@ -70,7 +70,7 @@ export class BaseNode extends ObjectNode {
 
 			return 'orange';
 
-		} else if ( value instanceof File ) {
+		} else if ( value.isDataFile === true ) {
 
 			return 'aqua';
 

+ 59 - 0
examples/jsm/node-editor/core/DataFile.js

@@ -0,0 +1,59 @@
+export class DataFile {
+
+	constructor( value ) {
+
+		this.value = value;
+		this.url = null;
+
+	}
+
+	setValue( value ) {
+
+		this.value = value;
+		this.url = null;
+
+	}
+
+	isURL( uri ) {
+
+		const pattern = new RegExp( '^((ft|htt)ps?:\\/\\/)?' + // protocol
+			'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name and extension
+			'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
+			'(\\:\\d+)?' + // port
+			'(\\/[-a-z\\d%@_.~+&:]*)*' + // path
+			'(\\?[;&a-z\\d%@_.,~+&:=-]*)?' + // query string
+			'(\\#[-a-z\\d_]*)?$', 'i' ); // fragment locator
+
+		return pattern.test( uri );
+
+	}
+
+	getURL() {
+
+		let url = this.url;
+
+		if ( url === null ) {
+
+			const value = this.value;
+
+			if ( value instanceof File ) {
+
+				url = URL.createObjectURL( value );
+
+			} else {
+
+				url = value;
+
+			}
+
+			this.url = this.isURL( url ) ? url : null;
+
+		}
+
+		return url;
+
+	}
+
+}
+
+DataFile.prototype.isDataFile = true;

+ 5 - 2
examples/jsm/node-editor/core/FileEditor.js

@@ -1,11 +1,14 @@
 import { StringInput, Element } from '../../libs/flow.module.js';
-import { BaseNode } from '../core/BaseNode.js';
+import { BaseNode } from './BaseNode.js';
+import { DataFile } from './DataFile.js';
 
 export class FileEditor extends BaseNode {
 
 	constructor( file ) {
 
-		super( 'File', 1, file, 250 );
+		const dataFile = new DataFile( file );
+
+		super( 'File', 1, dataFile, 250 );
 
 		this.file = file;
 		this.nameInput = new StringInput( file.name ).setReadOnly( true );

+ 29 - 0
examples/jsm/node-editor/core/FileURLEditor.js

@@ -0,0 +1,29 @@
+import { StringInput, Element } from '../../libs/flow.module.js';
+import { BaseNode } from './BaseNode.js';
+import { DataFile } from './DataFile.js';
+
+export class FileURLEditor extends BaseNode {
+
+	constructor() {
+
+		const dataFile = new DataFile();
+
+		super( 'File URL', 1, dataFile, 250 );
+
+		const urlInput = new StringInput().onChange( () => {
+
+			if ( urlInput.getValue() !== dataFile.getURL() ) {
+
+				dataFile.setValue( urlInput.getValue() );
+
+				this.invalidate();
+
+			}
+
+		} );
+
+		this.add( new Element().add( urlInput ) );
+
+	}
+
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
examples/jsm/node-editor/examples/matcap.json


+ 17 - 7
examples/jsm/node-editor/inputs/TextureEditor.js

@@ -4,19 +4,29 @@ import { TextureNode, UVNode } from 'three-nodes/Nodes.js';
 import { Texture, TextureLoader, RepeatWrapping, ClampToEdgeWrapping, MirroredRepeatWrapping } from 'three';
 
 const fileTexture = new WeakMap();
+const fileURL = new WeakMap();
 const textureLoader = new TextureLoader();
 const defaultTexture = new Texture();
 const defaultUV = new UVNode();
 
-const getTextureFromFile = ( file ) => {
+const getTexture = ( file ) => {
 
 	let texture = fileTexture.get( file );
 
-	if ( texture === undefined ) {
+	if ( texture === undefined || file.getURL() !== fileURL.get( file ) ) {
 
-		texture = textureLoader.load( URL.createObjectURL( file ) );
+		const url = file.getURL();
+
+		if ( texture !== undefined ) {
+
+			texture.dispose();
+
+		}
+
+		texture = textureLoader.load( url );
 
 		fileTexture.set( file, texture );
+		fileURL.set( file, url );
 
 	}
 
@@ -49,7 +59,7 @@ export class TextureEditor extends BaseNode {
 
 			const object = target.getObject();
 
-			if ( object && ( object instanceof File ) === false ) {
+			if ( object && object.isDataFile !== true ) {
 
 				if ( stage === 'dragged' ) {
 
@@ -68,13 +78,13 @@ export class TextureEditor extends BaseNode {
 			const file = fileElement.getLinkedObject();
 			const node = this.value;
 
-			this.texture = file ? getTextureFromFile( file ) : null;
+			this.texture = file ? getTexture( file ) : null;
 
 			node.value = this.texture || defaultTexture;
 
 			this.update();
 
-		} );
+		}, true );
 
 		this.add( fileElement );
 
@@ -112,7 +122,7 @@ export class TextureEditor extends BaseNode {
 
 		} );
 
-		this.flipYInput = new ToggleInput( false ).onClick( () => {
+		this.flipYInput = new ToggleInput( false ).onChange( () => {
 
 			this.update();
 

+ 3 - 0
examples/jsm/nodes/Nodes.js

@@ -68,6 +68,7 @@ import ConvertNode from './utils/ConvertNode.js';
 import JoinNode from './utils/JoinNode.js';
 import SplitNode from './utils/SplitNode.js';
 import SpriteSheetUVNode from './utils/SpriteSheetUVNode.js';
+import MatcapUVNode from './utils/MatcapUVNode.js';
 import OscNode from './utils/OscNode.js';
 import TimerNode from './utils/TimerNode.js';
 
@@ -166,6 +167,7 @@ const nodeLib = {
 	JoinNode,
 	SplitNode,
 	SpriteSheetUVNode,
+	MatcapUVNode,
 	OscNode,
 	TimerNode,
 
@@ -260,6 +262,7 @@ export {
 	JoinNode,
 	SplitNode,
 	SpriteSheetUVNode,
+	MatcapUVNode,
 	OscNode,
 	TimerNode,
 

+ 25 - 0
examples/jsm/nodes/utils/MatcapUVNode.js

@@ -0,0 +1,25 @@
+import TempNode from '../core/TempNode.js';
+import { join, negate, normalize, cross, dot, mul, add, transformedNormalView, positionViewDirection } from '../ShaderNode.js';
+
+class MatcapUVNode extends TempNode {
+
+	constructor() {
+
+		super( 'vec2' );
+
+	}
+
+	generate( builder ) {
+
+		const x = normalize( join( positionViewDirection.z, 0, negate( positionViewDirection.x ) ) );
+		const y = cross( positionViewDirection, x );
+
+		const uv = add( mul( join( dot( x, transformedNormalView ), dot( y, transformedNormalView ) ), 0.495 ), 0.5 );
+
+		return uv.build( builder, this.getNodeType( builder ) );
+
+	}
+
+}
+
+export default MatcapUVNode;

+ 6 - 4
examples/jsm/renderers/webgpu/nodes/WebGPUNodeBuilder.js

@@ -143,16 +143,18 @@ class WebGPUNodeBuilder extends NodeBuilder {
 
 			let lightNode = material.lightNode;
 
-			// VERTEX STAGE
-
-			let vertex = new PositionNode( PositionNode.GEOMETRY );
+			if ( material.isMeshStandardMaterial && lightNode === null && this.lightNode ) {
 
-			if ( lightNode === null && this.lightNode ) {
+				// use scene lights
 
 				lightNode = this.lightNode;
 
 			}
 
+			// VERTEX STAGE
+
+			let vertex = new PositionNode( PositionNode.GEOMETRY );
+
 			if ( material.positionNode && material.positionNode.isNode ) {
 
 				const assignPositionNode = new OperatorNode( '=', new PositionNode( PositionNode.LOCAL ), material.positionNode );

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio