From 90b7b1b6431e5edb3bc79b21116302d4338eeec1 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:12:29 -0400 Subject: [PATCH 01/18] Add examples-jsm --- examples-jsm/.prettierrc.json | 8 ++++++++ examples-jsm/README.md | 22 ++++++++++++++++++++++ examples-jsm/index.js | 24 ++++++++++++++++++++++++ examples-jsm/package.json | 20 ++++++++++++++++++++ examples-jsm/tsconfig.json | 10 ++++++++++ pnpm-lock.yaml | 13 +++++++++++++ pnpm-workspace.yaml | 1 + 7 files changed, 98 insertions(+) create mode 100644 examples-jsm/.prettierrc.json create mode 100644 examples-jsm/README.md create mode 100644 examples-jsm/index.js create mode 100644 examples-jsm/package.json create mode 100644 examples-jsm/tsconfig.json diff --git a/examples-jsm/.prettierrc.json b/examples-jsm/.prettierrc.json new file mode 100644 index 000000000..b1738e2c9 --- /dev/null +++ b/examples-jsm/.prettierrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json.schemastore.org/prettierrc", + "arrowParens": "avoid", + "singleQuote": true, + "trailingComma": "all", + "tabWidth": 4, + "printWidth": 120 +} diff --git a/examples-jsm/README.md b/examples-jsm/README.md new file mode 100644 index 000000000..05018d6cc --- /dev/null +++ b/examples-jsm/README.md @@ -0,0 +1,22 @@ +# Update patch + +- `pnpm run create-examples` +- Commit changes +- `git apply changes.patch` +- Make changes +- `pnpm run type-check` +- `git diff > ../changes.patch` +- Reset changes +- Move patch file + +# Update sources + +- `pnpm run create-examples` +- Commit changes +- `git apply --reject changes.patch` +- Fix conflicts +- `pnpm run type-check` +- `git diff > ../changes.patch` +- Reset example changes +- Move patch file +- Commit changes diff --git a/examples-jsm/index.js b/examples-jsm/index.js new file mode 100644 index 000000000..33660ad2c --- /dev/null +++ b/examples-jsm/index.js @@ -0,0 +1,24 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +import prettier from 'prettier'; + +const files = ['nodes/core/Node', 'nodes/core/NodeBuilder']; + +const inDir = '../three.js/examples/jsm'; +const outDir = './examples'; + +fs.rmSync(outDir, { recursive: true, force: true }); +fs.mkdirSync(outDir); + +for (const file of files) { + console.log(file); + const fileContents = fs.readFileSync(path.join(inDir, `${file}.js`), { + encoding: 'utf-8', + }); + const options = await prettier.resolveConfig(file); + const formattedFile = await prettier.format(fileContents, { ...options, parser: 'babel' }); + const outPath = path.join(outDir, `${file}.ts`); + fs.mkdirSync(path.dirname(outPath), { recursive: true }); + fs.writeFileSync(outPath, formattedFile); +} diff --git a/examples-jsm/package.json b/examples-jsm/package.json new file mode 100644 index 000000000..e8fd054fb --- /dev/null +++ b/examples-jsm/package.json @@ -0,0 +1,20 @@ +{ + "name": "examples-jsm", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "create-examples": "node index.js", + "type-check": "tsc", + "format": "prettier --write .", + "format-check": "prettier --check ." + }, + "type": "module", + "author": "", + "license": "ISC", + "dependencies": { + "@types/three": "file:../types/three", + "prettier": "^3.2.5", + "typescript": "latest" + } +} diff --git a/examples-jsm/tsconfig.json b/examples-jsm/tsconfig.json new file mode 100644 index 000000000..7da05e87c --- /dev/null +++ b/examples-jsm/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "NodeNext", + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true, + "strict": true + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6f04f867..eaad8b457 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,18 @@ importers: specifier: latest version: 5.4.5 + examples-jsm: + dependencies: + '@types/three': + specifier: file:../types/three + version: file:types/three + prettier: + specifier: ^3.2.5 + version: 3.2.5 + typescript: + specifier: latest + version: 5.4.5 + examples-testing: dependencies: '@types/three': @@ -2842,6 +2854,7 @@ packages: /npmlog@4.1.2: resolution: {integrity: sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==} + deprecated: This package is no longer supported. requiresBuild: true dependencies: are-we-there-yet: 1.1.7 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 8111e1c3f..25df933e6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,4 @@ packages: + - 'examples-jsm' - 'examples-testing' - 'types/three' From a8b50e21c7704efb74eafa2a008fea750a99cf36 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:22:25 -0400 Subject: [PATCH 02/18] Update --- examples-jsm/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples-jsm/index.js b/examples-jsm/index.js index 33660ad2c..17c4921ad 100644 --- a/examples-jsm/index.js +++ b/examples-jsm/index.js @@ -3,7 +3,7 @@ import * as path from 'node:path'; import prettier from 'prettier'; -const files = ['nodes/core/Node', 'nodes/core/NodeBuilder']; +const files = ['nodes/core/Node', 'nodes/core/constants']; const inDir = '../three.js/examples/jsm'; const outDir = './examples'; From be3afa0084cd5722406dfd272da4b559b1fd744c Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:23:09 -0400 Subject: [PATCH 03/18] Add examples --- examples-jsm/examples/nodes/core/Node.ts | 411 ++++++++++++++++++ examples-jsm/examples/nodes/core/constants.ts | 28 ++ 2 files changed, 439 insertions(+) create mode 100644 examples-jsm/examples/nodes/core/Node.ts create mode 100644 examples-jsm/examples/nodes/core/constants.ts diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts new file mode 100644 index 000000000..438c44dd1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/Node.ts @@ -0,0 +1,411 @@ +import { EventDispatcher } from 'three'; +import { NodeUpdateType } from './constants.js'; +import { getNodeChildren, getCacheKey } from './NodeUtils.js'; +import { MathUtils } from 'three'; + +const NodeClasses = new Map(); + +let _nodeId = 0; + +class Node extends EventDispatcher { + constructor(nodeType = null) { + super(); + + this.nodeType = nodeType; + + this.updateType = NodeUpdateType.NONE; + this.updateBeforeType = NodeUpdateType.NONE; + + this.uuid = MathUtils.generateUUID(); + + this.version = 0; + + this._cacheKey = null; + this._cacheKeyVersion = 0; + + this.isNode = true; + + Object.defineProperty(this, 'id', { value: _nodeId++ }); + } + + set needsUpdate(value) { + if (value === true) { + this.version++; + } + } + + get type() { + return this.constructor.type; + } + + onUpdate(callback, updateType) { + this.updateType = updateType; + this.update = callback.bind(this.getSelf()); + + return this; + } + + onFrameUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.FRAME); + } + + onRenderUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.RENDER); + } + + onObjectUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.OBJECT); + } + + onReference(callback) { + this.updateReference = callback.bind(this.getSelf()); + + return this; + } + + getSelf() { + // Returns non-node object. + + return this.self || this; + } + + updateReference(/*state*/) { + return this; + } + + isGlobal(/*builder*/) { + return false; + } + + *getChildren() { + for (const { childNode } of getNodeChildren(this)) { + yield childNode; + } + } + + dispose() { + this.dispatchEvent({ type: 'dispose' }); + } + + traverse(callback) { + callback(this); + + for (const childNode of this.getChildren()) { + childNode.traverse(callback); + } + } + + getCacheKey(force = false) { + force = force || this.version !== this._cacheKeyVersion; + + if (force === true || this._cacheKey === null) { + this._cacheKey = getCacheKey(this, force); + this._cacheKeyVersion = this.version; + } + + return this._cacheKey; + } + + getHash(/*builder*/) { + return this.uuid; + } + + getUpdateType() { + return this.updateType; + } + + getUpdateBeforeType() { + return this.updateBeforeType; + } + + getElementType(builder) { + const type = this.getNodeType(builder); + const elementType = builder.getElementType(type); + + return elementType; + } + + getNodeType(builder) { + const nodeProperties = builder.getNodeProperties(this); + + if (nodeProperties.outputNode) { + return nodeProperties.outputNode.getNodeType(builder); + } + + return this.nodeType; + } + + getShared(builder) { + const hash = this.getHash(builder); + const nodeFromHash = builder.getNodeFromHash(hash); + + return nodeFromHash || this; + } + + setup(builder) { + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of this.getChildren()) { + nodeProperties['_node' + childNode.id] = childNode; + } + + // return a outputNode if exists + return null; + } + + construct(builder) { + // @deprecated, r157 + + console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); + + return this.setup(builder); + } + + increaseUsage(builder) { + const nodeData = builder.getDataFromNode(this); + nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; + + return nodeData.usageCount; + } + + analyze(builder) { + const usageCount = this.increaseUsage(builder); + + if (usageCount === 1) { + // node flow children + + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of Object.values(nodeProperties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } + + generate(builder, output) { + const { outputNode } = builder.getNodeProperties(this); + + if (outputNode && outputNode.isNode === true) { + return outputNode.build(builder, output); + } + } + + updateBefore(/*frame*/) { + console.warn('Abstract function.'); + } + + update(/*frame*/) { + console.warn('Abstract function.'); + } + + build(builder, output = null) { + const refNode = this.getShared(builder); + + if (this !== refNode) { + return refNode.build(builder, output); + } + + builder.addNode(this); + builder.addChain(this); + + /* Build stages expected results: + - "setup" -> Node + - "analyze" -> null + - "generate" -> String + */ + let result = null; + + const buildStage = builder.getBuildStage(); + + if (buildStage === 'setup') { + this.updateReference(builder); + + const properties = builder.getNodeProperties(this); + + if (properties.initialized !== true || builder.context.tempRead === false) { + const stackNodesBeforeSetup = builder.stack.nodes.length; + + properties.initialized = true; + properties.outputNode = this.setup(builder); + + if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { + properties.outputNode = builder.stack; + } + + for (const childNode of Object.values(properties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } else if (buildStage === 'analyze') { + this.analyze(builder); + } else if (buildStage === 'generate') { + const isGenerateOnce = this.generate.length === 1; + + if (isGenerateOnce) { + const type = this.getNodeType(builder); + const nodeData = builder.getDataFromNode(this); + + result = nodeData.snippet; + + if (result === undefined /*|| builder.context.tempRead === false*/) { + result = this.generate(builder) || ''; + + nodeData.snippet = result; + } + + result = builder.format(result, type, output); + } else { + result = this.generate(builder, output) || ''; + } + } + + builder.removeChain(this); + + return result; + } + + getSerializeChildren() { + return getNodeChildren(this); + } + + serialize(json) { + const nodeChildren = this.getSerializeChildren(); + + const inputNodes = {}; + + for (const { property, index, childNode } of nodeChildren) { + if (index !== undefined) { + if (inputNodes[property] === undefined) { + inputNodes[property] = Number.isInteger(index) ? [] : {}; + } + + inputNodes[property][index] = childNode.toJSON(json.meta).uuid; + } else { + inputNodes[property] = childNode.toJSON(json.meta).uuid; + } + } + + if (Object.keys(inputNodes).length > 0) { + json.inputNodes = inputNodes; + } + } + + deserialize(json) { + if (json.inputNodes !== undefined) { + const nodes = json.meta.nodes; + + for (const property in json.inputNodes) { + if (Array.isArray(json.inputNodes[property])) { + const inputArray = []; + + for (const uuid of json.inputNodes[property]) { + inputArray.push(nodes[uuid]); + } + + this[property] = inputArray; + } else if (typeof json.inputNodes[property] === 'object') { + const inputObject = {}; + + for (const subProperty in json.inputNodes[property]) { + const uuid = json.inputNodes[property][subProperty]; + + inputObject[subProperty] = nodes[uuid]; + } + + this[property] = inputObject; + } else { + const uuid = json.inputNodes[property]; + + this[property] = nodes[uuid]; + } + } + } + } + + toJSON(meta) { + const { uuid, type } = this; + const isRoot = meta === undefined || typeof meta === 'string'; + + if (isRoot) { + meta = { + textures: {}, + images: {}, + nodes: {}, + }; + } + + // serialize + + let data = meta.nodes[uuid]; + + if (data === undefined) { + data = { + uuid, + type, + meta, + metadata: { + version: 4.6, + type: 'Node', + generator: 'Node.toJSON', + }, + }; + + if (isRoot !== true) meta.nodes[data.uuid] = data; + + this.serialize(data); + + delete data.meta; + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache(cache) { + const values = []; + + for (const key in cache) { + const data = cache[key]; + delete data.metadata; + values.push(data); + } + + return values; + } + + if (isRoot) { + const textures = extractFromCache(meta.textures); + const images = extractFromCache(meta.images); + const nodes = extractFromCache(meta.nodes); + + if (textures.length > 0) data.textures = textures; + if (images.length > 0) data.images = images; + if (nodes.length > 0) data.nodes = nodes; + } + + return data; + } +} + +export default Node; + +export function addNodeClass(type, nodeClass) { + if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); + if (NodeClasses.has(type)) { + console.warn(`Redefinition of node class ${type}`); + return; + } + + NodeClasses.set(type, nodeClass); + nodeClass.type = type; +} + +export function createNodeFromType(type) { + const Class = NodeClasses.get(type); + + if (Class !== undefined) { + return new Class(); + } +} diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts new file mode 100644 index 000000000..3b01a9a6d --- /dev/null +++ b/examples-jsm/examples/nodes/core/constants.ts @@ -0,0 +1,28 @@ +export const NodeShaderStage = { + VERTEX: 'vertex', + FRAGMENT: 'fragment', +}; + +export const NodeUpdateType = { + NONE: 'none', + FRAME: 'frame', + RENDER: 'render', + OBJECT: 'object', +}; + +export const NodeType = { + BOOLEAN: 'bool', + INTEGER: 'int', + FLOAT: 'float', + VECTOR2: 'vec2', + VECTOR3: 'vec3', + VECTOR4: 'vec4', + MATRIX2: 'mat2', + MATRIX3: 'mat3', + MATRIX4: 'mat4', +}; + +export const defaultShaderStages = ['fragment', 'vertex']; +export const defaultBuildStages = ['setup', 'analyze', 'generate']; +export const shaderStages = [...defaultShaderStages, 'compute']; +export const vectorComponents = ['x', 'y', 'z', 'w']; From f500f38bd3c5fce1a8f35cb7f105992f7e261a25 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:24:16 -0400 Subject: [PATCH 04/18] Add patch --- examples-jsm/changes.patch | 90 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 examples-jsm/changes.patch diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch new file mode 100644 index 000000000..9eb2a7057 --- /dev/null +++ b/examples-jsm/changes.patch @@ -0,0 +1,90 @@ +diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts +index 438c44dd..eb111a83 100644 +--- a/examples-jsm/examples/nodes/core/Node.ts ++++ b/examples-jsm/examples/nodes/core/Node.ts +@@ -8,6 +8,24 @@ const NodeClasses = new Map(); + let _nodeId = 0; + + class Node extends EventDispatcher { ++ // TODO ++ // nodeType: string?? ++ ++ updateType: NodeUpdateType; ++ updateBeforeType: NodeUpdateType; ++ ++ uuid: string; ++ ++ version: number; ++ ++ // TODO ++ // _cacheKey?? ++ _cacheKeyVersion: number; ++ ++ readonly isNode: true; ++ ++ readonly id!: number; ++ + constructor(nodeType = null) { + super(); + +@@ -28,7 +46,7 @@ class Node extends EventDispatcher { + Object.defineProperty(this, 'id', { value: _nodeId++ }); + } + +- set needsUpdate(value) { ++ set needsUpdate(value: boolean) { + if (value === true) { + this.version++; + } +diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts +index 3b01a9a6..3391a4be 100644 +--- a/examples-jsm/examples/nodes/core/constants.ts ++++ b/examples-jsm/examples/nodes/core/constants.ts +@@ -1,26 +1,26 @@ +-export const NodeShaderStage = { +- VERTEX: 'vertex', +- FRAGMENT: 'fragment', +-}; ++export enum NodeShaderStage { ++ VERTEX = 'vertex', ++ FRAGMENT = 'fragment', ++} + +-export const NodeUpdateType = { +- NONE: 'none', +- FRAME: 'frame', +- RENDER: 'render', +- OBJECT: 'object', +-}; ++export enum NodeUpdateType { ++ NONE = 'none', ++ FRAME = 'frame', ++ RENDER = 'render', ++ OBJECT = 'object', ++} + +-export const NodeType = { +- BOOLEAN: 'bool', +- INTEGER: 'int', +- FLOAT: 'float', +- VECTOR2: 'vec2', +- VECTOR3: 'vec3', +- VECTOR4: 'vec4', +- MATRIX2: 'mat2', +- MATRIX3: 'mat3', +- MATRIX4: 'mat4', +-}; ++export enum NodeType { ++ BOOLEAN = 'bool', ++ INTEGER = 'int', ++ FLOAT = 'float', ++ VECTOR2 = 'vec2', ++ VECTOR3 = 'vec3', ++ VECTOR4 = 'vec4', ++ MATRIX2 = 'mat2', ++ MATRIX3 = 'mat3', ++ MATRIX4 = 'mat4', ++} + + export const defaultShaderStages = ['fragment', 'vertex']; + export const defaultBuildStages = ['setup', 'analyze', 'generate']; From c39dab05769814464acd8d1e20e66ddc3b5e489c Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:24:27 -0400 Subject: [PATCH 05/18] Delete examples --- examples-jsm/examples/nodes/core/Node.ts | 411 ------------------ examples-jsm/examples/nodes/core/constants.ts | 28 -- 2 files changed, 439 deletions(-) delete mode 100644 examples-jsm/examples/nodes/core/Node.ts delete mode 100644 examples-jsm/examples/nodes/core/constants.ts diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts deleted file mode 100644 index 438c44dd1..000000000 --- a/examples-jsm/examples/nodes/core/Node.ts +++ /dev/null @@ -1,411 +0,0 @@ -import { EventDispatcher } from 'three'; -import { NodeUpdateType } from './constants.js'; -import { getNodeChildren, getCacheKey } from './NodeUtils.js'; -import { MathUtils } from 'three'; - -const NodeClasses = new Map(); - -let _nodeId = 0; - -class Node extends EventDispatcher { - constructor(nodeType = null) { - super(); - - this.nodeType = nodeType; - - this.updateType = NodeUpdateType.NONE; - this.updateBeforeType = NodeUpdateType.NONE; - - this.uuid = MathUtils.generateUUID(); - - this.version = 0; - - this._cacheKey = null; - this._cacheKeyVersion = 0; - - this.isNode = true; - - Object.defineProperty(this, 'id', { value: _nodeId++ }); - } - - set needsUpdate(value) { - if (value === true) { - this.version++; - } - } - - get type() { - return this.constructor.type; - } - - onUpdate(callback, updateType) { - this.updateType = updateType; - this.update = callback.bind(this.getSelf()); - - return this; - } - - onFrameUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.FRAME); - } - - onRenderUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.RENDER); - } - - onObjectUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.OBJECT); - } - - onReference(callback) { - this.updateReference = callback.bind(this.getSelf()); - - return this; - } - - getSelf() { - // Returns non-node object. - - return this.self || this; - } - - updateReference(/*state*/) { - return this; - } - - isGlobal(/*builder*/) { - return false; - } - - *getChildren() { - for (const { childNode } of getNodeChildren(this)) { - yield childNode; - } - } - - dispose() { - this.dispatchEvent({ type: 'dispose' }); - } - - traverse(callback) { - callback(this); - - for (const childNode of this.getChildren()) { - childNode.traverse(callback); - } - } - - getCacheKey(force = false) { - force = force || this.version !== this._cacheKeyVersion; - - if (force === true || this._cacheKey === null) { - this._cacheKey = getCacheKey(this, force); - this._cacheKeyVersion = this.version; - } - - return this._cacheKey; - } - - getHash(/*builder*/) { - return this.uuid; - } - - getUpdateType() { - return this.updateType; - } - - getUpdateBeforeType() { - return this.updateBeforeType; - } - - getElementType(builder) { - const type = this.getNodeType(builder); - const elementType = builder.getElementType(type); - - return elementType; - } - - getNodeType(builder) { - const nodeProperties = builder.getNodeProperties(this); - - if (nodeProperties.outputNode) { - return nodeProperties.outputNode.getNodeType(builder); - } - - return this.nodeType; - } - - getShared(builder) { - const hash = this.getHash(builder); - const nodeFromHash = builder.getNodeFromHash(hash); - - return nodeFromHash || this; - } - - setup(builder) { - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of this.getChildren()) { - nodeProperties['_node' + childNode.id] = childNode; - } - - // return a outputNode if exists - return null; - } - - construct(builder) { - // @deprecated, r157 - - console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); - - return this.setup(builder); - } - - increaseUsage(builder) { - const nodeData = builder.getDataFromNode(this); - nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; - - return nodeData.usageCount; - } - - analyze(builder) { - const usageCount = this.increaseUsage(builder); - - if (usageCount === 1) { - // node flow children - - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of Object.values(nodeProperties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } - - generate(builder, output) { - const { outputNode } = builder.getNodeProperties(this); - - if (outputNode && outputNode.isNode === true) { - return outputNode.build(builder, output); - } - } - - updateBefore(/*frame*/) { - console.warn('Abstract function.'); - } - - update(/*frame*/) { - console.warn('Abstract function.'); - } - - build(builder, output = null) { - const refNode = this.getShared(builder); - - if (this !== refNode) { - return refNode.build(builder, output); - } - - builder.addNode(this); - builder.addChain(this); - - /* Build stages expected results: - - "setup" -> Node - - "analyze" -> null - - "generate" -> String - */ - let result = null; - - const buildStage = builder.getBuildStage(); - - if (buildStage === 'setup') { - this.updateReference(builder); - - const properties = builder.getNodeProperties(this); - - if (properties.initialized !== true || builder.context.tempRead === false) { - const stackNodesBeforeSetup = builder.stack.nodes.length; - - properties.initialized = true; - properties.outputNode = this.setup(builder); - - if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { - properties.outputNode = builder.stack; - } - - for (const childNode of Object.values(properties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } else if (buildStage === 'analyze') { - this.analyze(builder); - } else if (buildStage === 'generate') { - const isGenerateOnce = this.generate.length === 1; - - if (isGenerateOnce) { - const type = this.getNodeType(builder); - const nodeData = builder.getDataFromNode(this); - - result = nodeData.snippet; - - if (result === undefined /*|| builder.context.tempRead === false*/) { - result = this.generate(builder) || ''; - - nodeData.snippet = result; - } - - result = builder.format(result, type, output); - } else { - result = this.generate(builder, output) || ''; - } - } - - builder.removeChain(this); - - return result; - } - - getSerializeChildren() { - return getNodeChildren(this); - } - - serialize(json) { - const nodeChildren = this.getSerializeChildren(); - - const inputNodes = {}; - - for (const { property, index, childNode } of nodeChildren) { - if (index !== undefined) { - if (inputNodes[property] === undefined) { - inputNodes[property] = Number.isInteger(index) ? [] : {}; - } - - inputNodes[property][index] = childNode.toJSON(json.meta).uuid; - } else { - inputNodes[property] = childNode.toJSON(json.meta).uuid; - } - } - - if (Object.keys(inputNodes).length > 0) { - json.inputNodes = inputNodes; - } - } - - deserialize(json) { - if (json.inputNodes !== undefined) { - const nodes = json.meta.nodes; - - for (const property in json.inputNodes) { - if (Array.isArray(json.inputNodes[property])) { - const inputArray = []; - - for (const uuid of json.inputNodes[property]) { - inputArray.push(nodes[uuid]); - } - - this[property] = inputArray; - } else if (typeof json.inputNodes[property] === 'object') { - const inputObject = {}; - - for (const subProperty in json.inputNodes[property]) { - const uuid = json.inputNodes[property][subProperty]; - - inputObject[subProperty] = nodes[uuid]; - } - - this[property] = inputObject; - } else { - const uuid = json.inputNodes[property]; - - this[property] = nodes[uuid]; - } - } - } - } - - toJSON(meta) { - const { uuid, type } = this; - const isRoot = meta === undefined || typeof meta === 'string'; - - if (isRoot) { - meta = { - textures: {}, - images: {}, - nodes: {}, - }; - } - - // serialize - - let data = meta.nodes[uuid]; - - if (data === undefined) { - data = { - uuid, - type, - meta, - metadata: { - version: 4.6, - type: 'Node', - generator: 'Node.toJSON', - }, - }; - - if (isRoot !== true) meta.nodes[data.uuid] = data; - - this.serialize(data); - - delete data.meta; - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache(cache) { - const values = []; - - for (const key in cache) { - const data = cache[key]; - delete data.metadata; - values.push(data); - } - - return values; - } - - if (isRoot) { - const textures = extractFromCache(meta.textures); - const images = extractFromCache(meta.images); - const nodes = extractFromCache(meta.nodes); - - if (textures.length > 0) data.textures = textures; - if (images.length > 0) data.images = images; - if (nodes.length > 0) data.nodes = nodes; - } - - return data; - } -} - -export default Node; - -export function addNodeClass(type, nodeClass) { - if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); - if (NodeClasses.has(type)) { - console.warn(`Redefinition of node class ${type}`); - return; - } - - NodeClasses.set(type, nodeClass); - nodeClass.type = type; -} - -export function createNodeFromType(type) { - const Class = NodeClasses.get(type); - - if (Class !== undefined) { - return new Class(); - } -} diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts deleted file mode 100644 index 3b01a9a6d..000000000 --- a/examples-jsm/examples/nodes/core/constants.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const NodeShaderStage = { - VERTEX: 'vertex', - FRAGMENT: 'fragment', -}; - -export const NodeUpdateType = { - NONE: 'none', - FRAME: 'frame', - RENDER: 'render', - OBJECT: 'object', -}; - -export const NodeType = { - BOOLEAN: 'bool', - INTEGER: 'int', - FLOAT: 'float', - VECTOR2: 'vec2', - VECTOR3: 'vec3', - VECTOR4: 'vec4', - MATRIX2: 'mat2', - MATRIX3: 'mat3', - MATRIX4: 'mat4', -}; - -export const defaultShaderStages = ['fragment', 'vertex']; -export const defaultBuildStages = ['setup', 'analyze', 'generate']; -export const shaderStages = [...defaultShaderStages, 'compute']; -export const vectorComponents = ['x', 'y', 'z', 'w']; From cbbe44cc950352c229425a33fc4971c4aaa59076 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:25:18 -0400 Subject: [PATCH 06/18] Add examples --- examples-jsm/examples/nodes/core/Node.ts | 411 +++++++ .../examples/nodes/core/NodeBuilder.ts | 1011 +++++++++++++++++ examples-jsm/examples/nodes/core/constants.ts | 28 + examples-jsm/index.js | 2 +- 4 files changed, 1451 insertions(+), 1 deletion(-) create mode 100644 examples-jsm/examples/nodes/core/Node.ts create mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts create mode 100644 examples-jsm/examples/nodes/core/constants.ts diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts new file mode 100644 index 000000000..438c44dd1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/Node.ts @@ -0,0 +1,411 @@ +import { EventDispatcher } from 'three'; +import { NodeUpdateType } from './constants.js'; +import { getNodeChildren, getCacheKey } from './NodeUtils.js'; +import { MathUtils } from 'three'; + +const NodeClasses = new Map(); + +let _nodeId = 0; + +class Node extends EventDispatcher { + constructor(nodeType = null) { + super(); + + this.nodeType = nodeType; + + this.updateType = NodeUpdateType.NONE; + this.updateBeforeType = NodeUpdateType.NONE; + + this.uuid = MathUtils.generateUUID(); + + this.version = 0; + + this._cacheKey = null; + this._cacheKeyVersion = 0; + + this.isNode = true; + + Object.defineProperty(this, 'id', { value: _nodeId++ }); + } + + set needsUpdate(value) { + if (value === true) { + this.version++; + } + } + + get type() { + return this.constructor.type; + } + + onUpdate(callback, updateType) { + this.updateType = updateType; + this.update = callback.bind(this.getSelf()); + + return this; + } + + onFrameUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.FRAME); + } + + onRenderUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.RENDER); + } + + onObjectUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.OBJECT); + } + + onReference(callback) { + this.updateReference = callback.bind(this.getSelf()); + + return this; + } + + getSelf() { + // Returns non-node object. + + return this.self || this; + } + + updateReference(/*state*/) { + return this; + } + + isGlobal(/*builder*/) { + return false; + } + + *getChildren() { + for (const { childNode } of getNodeChildren(this)) { + yield childNode; + } + } + + dispose() { + this.dispatchEvent({ type: 'dispose' }); + } + + traverse(callback) { + callback(this); + + for (const childNode of this.getChildren()) { + childNode.traverse(callback); + } + } + + getCacheKey(force = false) { + force = force || this.version !== this._cacheKeyVersion; + + if (force === true || this._cacheKey === null) { + this._cacheKey = getCacheKey(this, force); + this._cacheKeyVersion = this.version; + } + + return this._cacheKey; + } + + getHash(/*builder*/) { + return this.uuid; + } + + getUpdateType() { + return this.updateType; + } + + getUpdateBeforeType() { + return this.updateBeforeType; + } + + getElementType(builder) { + const type = this.getNodeType(builder); + const elementType = builder.getElementType(type); + + return elementType; + } + + getNodeType(builder) { + const nodeProperties = builder.getNodeProperties(this); + + if (nodeProperties.outputNode) { + return nodeProperties.outputNode.getNodeType(builder); + } + + return this.nodeType; + } + + getShared(builder) { + const hash = this.getHash(builder); + const nodeFromHash = builder.getNodeFromHash(hash); + + return nodeFromHash || this; + } + + setup(builder) { + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of this.getChildren()) { + nodeProperties['_node' + childNode.id] = childNode; + } + + // return a outputNode if exists + return null; + } + + construct(builder) { + // @deprecated, r157 + + console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); + + return this.setup(builder); + } + + increaseUsage(builder) { + const nodeData = builder.getDataFromNode(this); + nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; + + return nodeData.usageCount; + } + + analyze(builder) { + const usageCount = this.increaseUsage(builder); + + if (usageCount === 1) { + // node flow children + + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of Object.values(nodeProperties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } + + generate(builder, output) { + const { outputNode } = builder.getNodeProperties(this); + + if (outputNode && outputNode.isNode === true) { + return outputNode.build(builder, output); + } + } + + updateBefore(/*frame*/) { + console.warn('Abstract function.'); + } + + update(/*frame*/) { + console.warn('Abstract function.'); + } + + build(builder, output = null) { + const refNode = this.getShared(builder); + + if (this !== refNode) { + return refNode.build(builder, output); + } + + builder.addNode(this); + builder.addChain(this); + + /* Build stages expected results: + - "setup" -> Node + - "analyze" -> null + - "generate" -> String + */ + let result = null; + + const buildStage = builder.getBuildStage(); + + if (buildStage === 'setup') { + this.updateReference(builder); + + const properties = builder.getNodeProperties(this); + + if (properties.initialized !== true || builder.context.tempRead === false) { + const stackNodesBeforeSetup = builder.stack.nodes.length; + + properties.initialized = true; + properties.outputNode = this.setup(builder); + + if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { + properties.outputNode = builder.stack; + } + + for (const childNode of Object.values(properties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } else if (buildStage === 'analyze') { + this.analyze(builder); + } else if (buildStage === 'generate') { + const isGenerateOnce = this.generate.length === 1; + + if (isGenerateOnce) { + const type = this.getNodeType(builder); + const nodeData = builder.getDataFromNode(this); + + result = nodeData.snippet; + + if (result === undefined /*|| builder.context.tempRead === false*/) { + result = this.generate(builder) || ''; + + nodeData.snippet = result; + } + + result = builder.format(result, type, output); + } else { + result = this.generate(builder, output) || ''; + } + } + + builder.removeChain(this); + + return result; + } + + getSerializeChildren() { + return getNodeChildren(this); + } + + serialize(json) { + const nodeChildren = this.getSerializeChildren(); + + const inputNodes = {}; + + for (const { property, index, childNode } of nodeChildren) { + if (index !== undefined) { + if (inputNodes[property] === undefined) { + inputNodes[property] = Number.isInteger(index) ? [] : {}; + } + + inputNodes[property][index] = childNode.toJSON(json.meta).uuid; + } else { + inputNodes[property] = childNode.toJSON(json.meta).uuid; + } + } + + if (Object.keys(inputNodes).length > 0) { + json.inputNodes = inputNodes; + } + } + + deserialize(json) { + if (json.inputNodes !== undefined) { + const nodes = json.meta.nodes; + + for (const property in json.inputNodes) { + if (Array.isArray(json.inputNodes[property])) { + const inputArray = []; + + for (const uuid of json.inputNodes[property]) { + inputArray.push(nodes[uuid]); + } + + this[property] = inputArray; + } else if (typeof json.inputNodes[property] === 'object') { + const inputObject = {}; + + for (const subProperty in json.inputNodes[property]) { + const uuid = json.inputNodes[property][subProperty]; + + inputObject[subProperty] = nodes[uuid]; + } + + this[property] = inputObject; + } else { + const uuid = json.inputNodes[property]; + + this[property] = nodes[uuid]; + } + } + } + } + + toJSON(meta) { + const { uuid, type } = this; + const isRoot = meta === undefined || typeof meta === 'string'; + + if (isRoot) { + meta = { + textures: {}, + images: {}, + nodes: {}, + }; + } + + // serialize + + let data = meta.nodes[uuid]; + + if (data === undefined) { + data = { + uuid, + type, + meta, + metadata: { + version: 4.6, + type: 'Node', + generator: 'Node.toJSON', + }, + }; + + if (isRoot !== true) meta.nodes[data.uuid] = data; + + this.serialize(data); + + delete data.meta; + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache(cache) { + const values = []; + + for (const key in cache) { + const data = cache[key]; + delete data.metadata; + values.push(data); + } + + return values; + } + + if (isRoot) { + const textures = extractFromCache(meta.textures); + const images = extractFromCache(meta.images); + const nodes = extractFromCache(meta.nodes); + + if (textures.length > 0) data.textures = textures; + if (images.length > 0) data.images = images; + if (nodes.length > 0) data.nodes = nodes; + } + + return data; + } +} + +export default Node; + +export function addNodeClass(type, nodeClass) { + if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); + if (NodeClasses.has(type)) { + console.warn(`Redefinition of node class ${type}`); + return; + } + + NodeClasses.set(type, nodeClass); + nodeClass.type = type; +} + +export function createNodeFromType(type) { + const Class = NodeClasses.get(type); + + if (Class !== undefined) { + return new Class(); + } +} diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts new file mode 100644 index 000000000..ebdc13ff1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts @@ -0,0 +1,1011 @@ +import NodeUniform from './NodeUniform.js'; +import NodeAttribute from './NodeAttribute.js'; +import NodeVarying from './NodeVarying.js'; +import NodeVar from './NodeVar.js'; +import NodeCode from './NodeCode.js'; +import NodeKeywords from './NodeKeywords.js'; +import NodeCache from './NodeCache.js'; +import ParameterNode from './ParameterNode.js'; +import FunctionNode from '../code/FunctionNode.js'; +import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; +import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; + +import { + FloatNodeUniform, + Vector2NodeUniform, + Vector3NodeUniform, + Vector4NodeUniform, + ColorNodeUniform, + Matrix3NodeUniform, + Matrix4NodeUniform, +} from '../../renderers/common/nodes/NodeUniform.js'; + +import { + REVISION, + RenderTarget, + Color, + Vector2, + Vector3, + Vector4, + IntType, + UnsignedIntType, + Float16BufferAttribute, +} from 'three'; + +import { stack } from './StackNode.js'; +import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; + +import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; +import ChainMap from '../../renderers/common/ChainMap.js'; + +import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; + +const uniformsGroupCache = new ChainMap(); + +const typeFromLength = new Map([ + [2, 'vec2'], + [3, 'vec3'], + [4, 'vec4'], + [9, 'mat3'], + [16, 'mat4'], +]); + +const typeFromArray = new Map([ + [Int8Array, 'int'], + [Int16Array, 'int'], + [Int32Array, 'int'], + [Uint8Array, 'uint'], + [Uint16Array, 'uint'], + [Uint32Array, 'uint'], + [Float32Array, 'float'], +]); + +const toFloat = value => { + value = Number(value); + + return value + (value % 1 ? '' : '.0'); +}; + +class NodeBuilder { + constructor(object, renderer, parser, scene = null, material = null) { + this.object = object; + this.material = material || (object && object.material) || null; + this.geometry = (object && object.geometry) || null; + this.renderer = renderer; + this.parser = parser; + this.scene = scene; + + this.nodes = []; + this.updateNodes = []; + this.updateBeforeNodes = []; + this.hashNodes = {}; + + this.lightsNode = null; + this.environmentNode = null; + this.fogNode = null; + + this.clippingContext = null; + + this.vertexShader = null; + this.fragmentShader = null; + this.computeShader = null; + + this.flowNodes = { vertex: [], fragment: [], compute: [] }; + this.flowCode = { vertex: '', fragment: '', compute: [] }; + this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; + this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; + this.bindings = { vertex: [], fragment: [], compute: [] }; + this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; + this.bindingsArray = null; + this.attributes = []; + this.bufferAttributes = []; + this.varyings = []; + this.codes = {}; + this.vars = {}; + this.flow = { code: '' }; + this.chaining = []; + this.stack = stack(); + this.stacks = []; + this.tab = '\t'; + + this.currentFunctionNode = null; + + this.context = { + keywords: new NodeKeywords(), + material: this.material, + }; + + this.cache = new NodeCache(); + this.globalCache = this.cache; + + this.flowsData = new WeakMap(); + + this.shaderStage = null; + this.buildStage = null; + } + + createRenderTarget(width, height, options) { + return new RenderTarget(width, height, options); + } + + createCubeRenderTarget(size, options) { + return new CubeRenderTarget(size, options); + } + + createPMREMGenerator() { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return new PMREMGenerator(this.renderer); + } + + includes(node) { + return this.nodes.includes(node); + } + + _getSharedBindings(bindings) { + const shared = []; + + for (const binding of bindings) { + if (binding.shared === true) { + // nodes is the chainmap key + const nodes = binding.getNodes(); + + let sharedBinding = uniformsGroupCache.get(nodes); + + if (sharedBinding === undefined) { + uniformsGroupCache.set(nodes, binding); + + sharedBinding = binding; + } + + shared.push(sharedBinding); + } else { + shared.push(binding); + } + } + + return shared; + } + + getBindings() { + let bindingsArray = this.bindingsArray; + + if (bindingsArray === null) { + const bindings = this.bindings; + + this.bindingsArray = bindingsArray = this._getSharedBindings( + this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, + ); + } + + return bindingsArray; + } + + setHashNode(node, hash) { + this.hashNodes[hash] = node; + } + + addNode(node) { + if (this.nodes.includes(node) === false) { + this.nodes.push(node); + + this.setHashNode(node, node.getHash(this)); + } + } + + buildUpdateNodes() { + for (const node of this.nodes) { + const updateType = node.getUpdateType(); + const updateBeforeType = node.getUpdateBeforeType(); + + if (updateType !== NodeUpdateType.NONE) { + this.updateNodes.push(node.getSelf()); + } + + if (updateBeforeType !== NodeUpdateType.NONE) { + this.updateBeforeNodes.push(node); + } + } + } + + get currentNode() { + return this.chaining[this.chaining.length - 1]; + } + + addChain(node) { + /* + if ( this.chaining.indexOf( node ) !== - 1 ) { + + console.warn( 'Recursive node: ', node ); + + } + */ + + this.chaining.push(node); + } + + removeChain(node) { + const lastChain = this.chaining.pop(); + + if (lastChain !== node) { + throw new Error('NodeBuilder: Invalid node chaining!'); + } + } + + getMethod(method) { + return method; + } + + getNodeFromHash(hash) { + return this.hashNodes[hash]; + } + + addFlow(shaderStage, node) { + this.flowNodes[shaderStage].push(node); + + return node; + } + + setContext(context) { + this.context = context; + } + + getContext() { + return this.context; + } + + setCache(cache) { + this.cache = cache; + } + + getCache() { + return this.cache; + } + + isAvailable(/*name*/) { + return false; + } + + getVertexIndex() { + console.warn('Abstract function.'); + } + + getInstanceIndex() { + console.warn('Abstract function.'); + } + + getFrontFacing() { + console.warn('Abstract function.'); + } + + getFragCoord() { + console.warn('Abstract function.'); + } + + isFlipY() { + return false; + } + + generateTexture(/* texture, textureProperty, uvSnippet */) { + console.warn('Abstract function.'); + } + + generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { + console.warn('Abstract function.'); + } + + generateConst(type, value = null) { + if (value === null) { + if (type === 'float' || type === 'int' || type === 'uint') value = 0; + else if (type === 'bool') value = false; + else if (type === 'color') value = new Color(); + else if (type === 'vec2') value = new Vector2(); + else if (type === 'vec3') value = new Vector3(); + else if (type === 'vec4') value = new Vector4(); + } + + if (type === 'float') return toFloat(value); + if (type === 'int') return `${Math.round(value)}`; + if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; + if (type === 'bool') return value ? 'true' : 'false'; + if (type === 'color') + return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; + + const typeLength = this.getTypeLength(type); + + const componentType = this.getComponentType(type); + + const generateConst = value => this.generateConst(componentType, value); + + if (typeLength === 2) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; + } else if (typeLength === 3) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; + } else if (typeLength === 4) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; + } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { + return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; + } else if (typeLength > 4) { + return `${this.getType(type)}()`; + } + + throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); + } + + getType(type) { + if (type === 'color') return 'vec3'; + + return type; + } + + generateMethod(method) { + return method; + } + + hasGeometryAttribute(name) { + return this.geometry && this.geometry.getAttribute(name) !== undefined; + } + + getAttribute(name, type) { + const attributes = this.attributes; + + // find attribute + + for (const attribute of attributes) { + if (attribute.name === name) { + return attribute; + } + } + + // create a new if no exist + + const attribute = new NodeAttribute(name, type); + + attributes.push(attribute); + + return attribute; + } + + getPropertyName(node /*, shaderStage*/) { + return node.name; + } + + isVector(type) { + return /vec\d/.test(type); + } + + isMatrix(type) { + return /mat\d/.test(type); + } + + isReference(type) { + return ( + type === 'void' || + type === 'property' || + type === 'sampler' || + type === 'texture' || + type === 'cubeTexture' || + type === 'storageTexture' || + type === 'texture3D' + ); + } + + needsColorSpaceToLinear(/*texture*/) { + return false; + } + + getComponentTypeFromTexture(texture) { + const type = texture.type; + + if (texture.isDataTexture) { + if (type === IntType) return 'int'; + if (type === UnsignedIntType) return 'uint'; + } + + return 'float'; + } + + getElementType(type) { + if (type === 'mat2') return 'vec2'; + if (type === 'mat3') return 'vec3'; + if (type === 'mat4') return 'vec4'; + + return this.getComponentType(type); + } + + getComponentType(type) { + type = this.getVectorType(type); + + if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; + + const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); + + if (componentType === null) return null; + + if (componentType[1] === 'b') return 'bool'; + if (componentType[1] === 'i') return 'int'; + if (componentType[1] === 'u') return 'uint'; + + return 'float'; + } + + getVectorType(type) { + if (type === 'color') return 'vec3'; + if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') + return 'vec4'; + + return type; + } + + getTypeFromLength(length, componentType = 'float') { + if (length === 1) return componentType; + + const baseType = typeFromLength.get(length); + const prefix = componentType === 'float' ? '' : componentType[0]; + + return prefix + baseType; + } + + getTypeFromArray(array) { + return typeFromArray.get(array.constructor); + } + + getTypeFromAttribute(attribute) { + let dataAttribute = attribute; + + if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; + + const array = dataAttribute.array; + const itemSize = attribute.itemSize; + const normalized = attribute.normalized; + + let arrayType; + + if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { + arrayType = this.getTypeFromArray(array); + } + + return this.getTypeFromLength(itemSize, arrayType); + } + + getTypeLength(type) { + const vecType = this.getVectorType(type); + const vecNum = /vec([2-4])/.exec(vecType); + + if (vecNum !== null) return Number(vecNum[1]); + if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; + if (/mat2/.test(type) === true) return 4; + if (/mat3/.test(type) === true) return 9; + if (/mat4/.test(type) === true) return 16; + + return 0; + } + + getVectorFromMatrix(type) { + return type.replace('mat', 'vec'); + } + + changeComponentType(type, newComponentType) { + return this.getTypeFromLength(this.getTypeLength(type), newComponentType); + } + + getIntegerType(type) { + const componentType = this.getComponentType(type); + + if (componentType === 'int' || componentType === 'uint') return type; + + return this.changeComponentType(type, 'int'); + } + + addStack() { + this.stack = stack(this.stack); + + this.stacks.push(getCurrentStack() || this.stack); + setCurrentStack(this.stack); + + return this.stack; + } + + removeStack() { + const lastStack = this.stack; + this.stack = lastStack.parent; + + setCurrentStack(this.stacks.pop()); + + return lastStack; + } + + getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { + cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; + + let nodeData = cache.getNodeData(node); + + if (nodeData === undefined) { + nodeData = {}; + + cache.setNodeData(node, nodeData); + } + + if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; + + return nodeData[shaderStage]; + } + + getNodeProperties(node, shaderStage = 'any') { + const nodeData = this.getDataFromNode(node, shaderStage); + + return nodeData.properties || (nodeData.properties = { outputNode: null }); + } + + getBufferAttributeFromNode(node, type) { + const nodeData = this.getDataFromNode(node); + + let bufferAttribute = nodeData.bufferAttribute; + + if (bufferAttribute === undefined) { + const index = this.uniforms.index++; + + bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); + + this.bufferAttributes.push(bufferAttribute); + + nodeData.bufferAttribute = bufferAttribute; + } + + return bufferAttribute; + } + + getStructTypeFromNode(node, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + if (nodeData.structType === undefined) { + const index = this.structs.index++; + + node.name = `StructType${index}`; + this.structs[shaderStage].push(node); + + nodeData.structType = node; + } + + return node; + } + + getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { + const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); + + let nodeUniform = nodeData.uniform; + + if (nodeUniform === undefined) { + const index = this.uniforms.index++; + + nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); + + this.uniforms[shaderStage].push(nodeUniform); + + nodeData.uniform = nodeUniform; + } + + return nodeUniform; + } + + getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + let nodeVar = nodeData.variable; + + if (nodeVar === undefined) { + const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); + + if (name === null) name = 'nodeVar' + vars.length; + + nodeVar = new NodeVar(name, type); + + vars.push(nodeVar); + + nodeData.variable = nodeVar; + } + + return nodeVar; + } + + getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { + const nodeData = this.getDataFromNode(node, 'any'); + + let nodeVarying = nodeData.varying; + + if (nodeVarying === undefined) { + const varyings = this.varyings; + const index = varyings.length; + + if (name === null) name = 'nodeVarying' + index; + + nodeVarying = new NodeVarying(name, type); + + varyings.push(nodeVarying); + + nodeData.varying = nodeVarying; + } + + return nodeVarying; + } + + getCodeFromNode(node, type, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node); + + let nodeCode = nodeData.code; + + if (nodeCode === undefined) { + const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); + const index = codes.length; + + nodeCode = new NodeCode('nodeCode' + index, type); + + codes.push(nodeCode); + + nodeData.code = nodeCode; + } + + return nodeCode; + } + + addLineFlowCode(code) { + if (code === '') return this; + + code = this.tab + code; + + if (!/;\s*$/.test(code)) { + code = code + ';\n'; + } + + this.flow.code += code; + + return this; + } + + addFlowCode(code) { + this.flow.code += code; + + return this; + } + + addFlowTab() { + this.tab += '\t'; + + return this; + } + + removeFlowTab() { + this.tab = this.tab.slice(0, -1); + + return this; + } + + getFlowData(node /*, shaderStage*/) { + return this.flowsData.get(node); + } + + flowNode(node) { + const output = node.getNodeType(this); + + const flowData = this.flowChildNode(node, output); + + this.flowsData.set(node, flowData); + + return flowData; + } + + buildFunctionNode(shaderNode) { + const fn = new FunctionNode(); + + const previous = this.currentFunctionNode; + + this.currentFunctionNode = fn; + + fn.code = this.buildFunctionCode(shaderNode); + + this.currentFunctionNode = previous; + + return fn; + } + + flowShaderNode(shaderNode) { + const layout = shaderNode.layout; + + let inputs; + + if (shaderNode.isArrayInput) { + inputs = []; + + for (const input of layout.inputs) { + inputs.push(new ParameterNode(input.type, input.name)); + } + } else { + inputs = {}; + + for (const input of layout.inputs) { + inputs[input.name] = new ParameterNode(input.type, input.name); + } + } + + // + + shaderNode.layout = null; + + const callNode = shaderNode.call(inputs); + const flowData = this.flowStagesNode(callNode, layout.type); + + shaderNode.layout = layout; + + return flowData; + } + + flowStagesNode(node, output = null) { + const previousFlow = this.flow; + const previousVars = this.vars; + const previousBuildStage = this.buildStage; + + const flow = { + code: '', + }; + + this.flow = flow; + this.vars = {}; + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + flow.result = node.build(this, output); + } + + flow.vars = this.getVars(this.shaderStage); + + this.flow = previousFlow; + this.vars = previousVars; + this.setBuildStage(previousBuildStage); + + return flow; + } + + getFunctionOperator() { + return null; + } + + flowChildNode(node, output = null) { + const previousFlow = this.flow; + + const flow = { + code: '', + }; + + this.flow = flow; + + flow.result = node.build(this, output); + + this.flow = previousFlow; + + return flow; + } + + flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { + const previousShaderStage = this.shaderStage; + + this.setShaderStage(shaderStage); + + const flowData = this.flowChildNode(node, output); + + if (propertyName !== null) { + flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; + } + + this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; + + this.setShaderStage(previousShaderStage); + + return flowData; + } + + getAttributesArray() { + return this.attributes.concat(this.bufferAttributes); + } + + getAttributes(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVaryings(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVar(type, name) { + return `${this.getType(type)} ${name}`; + } + + getVars(shaderStage) { + let snippet = ''; + + const vars = this.vars[shaderStage]; + + if (vars !== undefined) { + for (const variable of vars) { + snippet += `${this.getVar(variable.type, variable.name)}; `; + } + } + + return snippet; + } + + getUniforms(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getCodes(shaderStage) { + const codes = this.codes[shaderStage]; + + let code = ''; + + if (codes !== undefined) { + for (const nodeCode of codes) { + code += nodeCode.code + '\n'; + } + } + + return code; + } + + getHash() { + return this.vertexShader + this.fragmentShader + this.computeShader; + } + + setShaderStage(shaderStage) { + this.shaderStage = shaderStage; + } + + getShaderStage() { + return this.shaderStage; + } + + setBuildStage(buildStage) { + this.buildStage = buildStage; + } + + getBuildStage() { + return this.buildStage; + } + + buildCode() { + console.warn('Abstract function.'); + } + + build() { + const { object, material } = this; + + if (material !== null) { + NodeMaterial.fromMaterial(material).build(this); + } else { + this.addFlow('compute', object); + } + + // setup() -> stage 1: create possible new nodes and returns an output reference node + // analyze() -> stage 2: analyze nodes to possible optimization and validation + // generate() -> stage 3: generate shader + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + if (this.context.vertex && this.context.vertex.isNode) { + this.flowNodeFromShaderStage('vertex', this.context.vertex); + } + + for (const shaderStage of shaderStages) { + this.setShaderStage(shaderStage); + + const flowNodes = this.flowNodes[shaderStage]; + + for (const node of flowNodes) { + if (buildStage === 'generate') { + this.flowNode(node); + } else { + node.build(this); + } + } + } + } + + this.setBuildStage(null); + this.setShaderStage(null); + + // stage 4: build code for a specific output + + this.buildCode(); + this.buildUpdateNodes(); + + return this; + } + + getNodeUniform(uniformNode, type) { + if (type === 'float') return new FloatNodeUniform(uniformNode); + if (type === 'vec2') return new Vector2NodeUniform(uniformNode); + if (type === 'vec3') return new Vector3NodeUniform(uniformNode); + if (type === 'vec4') return new Vector4NodeUniform(uniformNode); + if (type === 'color') return new ColorNodeUniform(uniformNode); + if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); + if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); + + throw new Error(`Uniform "${type}" not declared.`); + } + + createNodeMaterial(type = 'NodeMaterial') { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return createNodeMaterialFromType(type); + } + + format(snippet, fromType, toType) { + fromType = this.getVectorType(fromType); + toType = this.getVectorType(toType); + + if (fromType === toType || toType === null || this.isReference(toType)) { + return snippet; + } + + const fromTypeLength = this.getTypeLength(fromType); + const toTypeLength = this.getTypeLength(toType); + + if (fromTypeLength > 4) { + // fromType is matrix-like + + // @TODO: ignore for now + + return snippet; + } + + if (toTypeLength > 4 || toTypeLength === 0) { + // toType is matrix-like or unknown + + // @TODO: ignore for now + + return snippet; + } + + if (fromTypeLength === toTypeLength) { + return `${this.getType(toType)}( ${snippet} )`; + } + + if (fromTypeLength > toTypeLength) { + return this.format( + `${snippet}.${'xyz'.slice(0, toTypeLength)}`, + this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), + toType, + ); + } + + if (toTypeLength === 4 && fromTypeLength > 1) { + // toType is vec4-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; + } + + if (fromTypeLength === 2) { + // fromType is vec2-like and toType is vec3-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; + } + + if (fromTypeLength === 1 && toTypeLength > 1 && fromType[0] !== toType[0]) { + // fromType is float-like + + // convert a number value to vector type, e.g: + // vec3( 1u ) -> vec3( float( 1u ) ) + + snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; + } + + return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like + } + + getSignature() { + return `// Three.js r${REVISION} - NodeMaterial System\n`; + } +} + +export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts new file mode 100644 index 000000000..3b01a9a6d --- /dev/null +++ b/examples-jsm/examples/nodes/core/constants.ts @@ -0,0 +1,28 @@ +export const NodeShaderStage = { + VERTEX: 'vertex', + FRAGMENT: 'fragment', +}; + +export const NodeUpdateType = { + NONE: 'none', + FRAME: 'frame', + RENDER: 'render', + OBJECT: 'object', +}; + +export const NodeType = { + BOOLEAN: 'bool', + INTEGER: 'int', + FLOAT: 'float', + VECTOR2: 'vec2', + VECTOR3: 'vec3', + VECTOR4: 'vec4', + MATRIX2: 'mat2', + MATRIX3: 'mat3', + MATRIX4: 'mat4', +}; + +export const defaultShaderStages = ['fragment', 'vertex']; +export const defaultBuildStages = ['setup', 'analyze', 'generate']; +export const shaderStages = [...defaultShaderStages, 'compute']; +export const vectorComponents = ['x', 'y', 'z', 'w']; diff --git a/examples-jsm/index.js b/examples-jsm/index.js index 17c4921ad..71403c9d5 100644 --- a/examples-jsm/index.js +++ b/examples-jsm/index.js @@ -3,7 +3,7 @@ import * as path from 'node:path'; import prettier from 'prettier'; -const files = ['nodes/core/Node', 'nodes/core/constants']; +const files = ['nodes/core/Node', 'nodes/core/NodeBuilder', 'nodes/core/constants']; const inDir = '../three.js/examples/jsm'; const outDir = './examples'; From 3221f01e7042a2b874ad06baa9576db3207194f7 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:29:39 -0400 Subject: [PATCH 07/18] Update patch --- examples-jsm/changes.patch | 100 +++++++++++++++++++++++++++++++++++-- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index 9eb2a7057..9160813ba 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -1,8 +1,15 @@ diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts -index 438c44dd..eb111a83 100644 +index 438c44dd..d5115493 100644 --- a/examples-jsm/examples/nodes/core/Node.ts +++ b/examples-jsm/examples/nodes/core/Node.ts -@@ -8,6 +8,24 @@ const NodeClasses = new Map(); +@@ -2,12 +2,31 @@ import { EventDispatcher } from 'three'; + import { NodeUpdateType } from './constants.js'; + import { getNodeChildren, getCacheKey } from './NodeUtils.js'; + import { MathUtils } from 'three'; ++import NodeBuilder from './NodeBuilder.js'; + + const NodeClasses = new Map(); + let _nodeId = 0; class Node extends EventDispatcher { @@ -27,7 +34,7 @@ index 438c44dd..eb111a83 100644 constructor(nodeType = null) { super(); -@@ -28,7 +46,7 @@ class Node extends EventDispatcher { +@@ -28,7 +47,7 @@ class Node extends EventDispatcher { Object.defineProperty(this, 'id', { value: _nodeId++ }); } @@ -36,6 +43,93 @@ index 438c44dd..eb111a83 100644 if (value === true) { this.version++; } +@@ -73,7 +92,7 @@ class Node extends EventDispatcher { + return this; + } + +- isGlobal(/*builder*/) { ++ isGlobal(builder: NodeBuilder) { + return false; + } + +@@ -106,7 +125,7 @@ class Node extends EventDispatcher { + return this._cacheKey; + } + +- getHash(/*builder*/) { ++ getHash(builder: NodeBuilder) { + return this.uuid; + } + +@@ -118,14 +137,14 @@ class Node extends EventDispatcher { + return this.updateBeforeType; + } + +- getElementType(builder) { ++ getElementType(builder: NodeBuilder) { + const type = this.getNodeType(builder); + const elementType = builder.getElementType(type); + + return elementType; + } + +- getNodeType(builder) { ++ getNodeType(builder: NodeBuilder) { + const nodeProperties = builder.getNodeProperties(this); + + if (nodeProperties.outputNode) { +@@ -135,14 +154,14 @@ class Node extends EventDispatcher { + return this.nodeType; + } + +- getShared(builder) { ++ getShared(builder: NodeBuilder) { + const hash = this.getHash(builder); + const nodeFromHash = builder.getNodeFromHash(hash); + + return nodeFromHash || this; + } + +- setup(builder) { ++ setup(builder: NodeBuilder) { + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of this.getChildren()) { +@@ -153,7 +172,7 @@ class Node extends EventDispatcher { + return null; + } + +- construct(builder) { ++ construct(builder: NodeBuilder) { + // @deprecated, r157 + + console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); +@@ -161,14 +180,14 @@ class Node extends EventDispatcher { + return this.setup(builder); + } + +- increaseUsage(builder) { ++ increaseUsage(builder: NodeBuilder) { + const nodeData = builder.getDataFromNode(this); + nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; + + return nodeData.usageCount; + } + +- analyze(builder) { ++ analyze(builder: NodeBuilder) { + const usageCount = this.increaseUsage(builder); + + if (usageCount === 1) { +@@ -184,7 +203,7 @@ class Node extends EventDispatcher { + } + } + +- generate(builder, output) { ++ generate(builder: NodeBuilder, output?: string | null) { + const { outputNode } = builder.getNodeProperties(this); + + if (outputNode && outputNode.isNode === true) { diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts index 3b01a9a6..3391a4be 100644 --- a/examples-jsm/examples/nodes/core/constants.ts From c666f73e69c8a0c4d8a763cf0ccc59e8d29fa645 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:29:52 -0400 Subject: [PATCH 08/18] Delete examples --- examples-jsm/examples/nodes/core/Node.ts | 411 ------- .../examples/nodes/core/NodeBuilder.ts | 1011 ----------------- examples-jsm/examples/nodes/core/constants.ts | 28 - 3 files changed, 1450 deletions(-) delete mode 100644 examples-jsm/examples/nodes/core/Node.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts delete mode 100644 examples-jsm/examples/nodes/core/constants.ts diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts deleted file mode 100644 index 438c44dd1..000000000 --- a/examples-jsm/examples/nodes/core/Node.ts +++ /dev/null @@ -1,411 +0,0 @@ -import { EventDispatcher } from 'three'; -import { NodeUpdateType } from './constants.js'; -import { getNodeChildren, getCacheKey } from './NodeUtils.js'; -import { MathUtils } from 'three'; - -const NodeClasses = new Map(); - -let _nodeId = 0; - -class Node extends EventDispatcher { - constructor(nodeType = null) { - super(); - - this.nodeType = nodeType; - - this.updateType = NodeUpdateType.NONE; - this.updateBeforeType = NodeUpdateType.NONE; - - this.uuid = MathUtils.generateUUID(); - - this.version = 0; - - this._cacheKey = null; - this._cacheKeyVersion = 0; - - this.isNode = true; - - Object.defineProperty(this, 'id', { value: _nodeId++ }); - } - - set needsUpdate(value) { - if (value === true) { - this.version++; - } - } - - get type() { - return this.constructor.type; - } - - onUpdate(callback, updateType) { - this.updateType = updateType; - this.update = callback.bind(this.getSelf()); - - return this; - } - - onFrameUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.FRAME); - } - - onRenderUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.RENDER); - } - - onObjectUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.OBJECT); - } - - onReference(callback) { - this.updateReference = callback.bind(this.getSelf()); - - return this; - } - - getSelf() { - // Returns non-node object. - - return this.self || this; - } - - updateReference(/*state*/) { - return this; - } - - isGlobal(/*builder*/) { - return false; - } - - *getChildren() { - for (const { childNode } of getNodeChildren(this)) { - yield childNode; - } - } - - dispose() { - this.dispatchEvent({ type: 'dispose' }); - } - - traverse(callback) { - callback(this); - - for (const childNode of this.getChildren()) { - childNode.traverse(callback); - } - } - - getCacheKey(force = false) { - force = force || this.version !== this._cacheKeyVersion; - - if (force === true || this._cacheKey === null) { - this._cacheKey = getCacheKey(this, force); - this._cacheKeyVersion = this.version; - } - - return this._cacheKey; - } - - getHash(/*builder*/) { - return this.uuid; - } - - getUpdateType() { - return this.updateType; - } - - getUpdateBeforeType() { - return this.updateBeforeType; - } - - getElementType(builder) { - const type = this.getNodeType(builder); - const elementType = builder.getElementType(type); - - return elementType; - } - - getNodeType(builder) { - const nodeProperties = builder.getNodeProperties(this); - - if (nodeProperties.outputNode) { - return nodeProperties.outputNode.getNodeType(builder); - } - - return this.nodeType; - } - - getShared(builder) { - const hash = this.getHash(builder); - const nodeFromHash = builder.getNodeFromHash(hash); - - return nodeFromHash || this; - } - - setup(builder) { - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of this.getChildren()) { - nodeProperties['_node' + childNode.id] = childNode; - } - - // return a outputNode if exists - return null; - } - - construct(builder) { - // @deprecated, r157 - - console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); - - return this.setup(builder); - } - - increaseUsage(builder) { - const nodeData = builder.getDataFromNode(this); - nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; - - return nodeData.usageCount; - } - - analyze(builder) { - const usageCount = this.increaseUsage(builder); - - if (usageCount === 1) { - // node flow children - - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of Object.values(nodeProperties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } - - generate(builder, output) { - const { outputNode } = builder.getNodeProperties(this); - - if (outputNode && outputNode.isNode === true) { - return outputNode.build(builder, output); - } - } - - updateBefore(/*frame*/) { - console.warn('Abstract function.'); - } - - update(/*frame*/) { - console.warn('Abstract function.'); - } - - build(builder, output = null) { - const refNode = this.getShared(builder); - - if (this !== refNode) { - return refNode.build(builder, output); - } - - builder.addNode(this); - builder.addChain(this); - - /* Build stages expected results: - - "setup" -> Node - - "analyze" -> null - - "generate" -> String - */ - let result = null; - - const buildStage = builder.getBuildStage(); - - if (buildStage === 'setup') { - this.updateReference(builder); - - const properties = builder.getNodeProperties(this); - - if (properties.initialized !== true || builder.context.tempRead === false) { - const stackNodesBeforeSetup = builder.stack.nodes.length; - - properties.initialized = true; - properties.outputNode = this.setup(builder); - - if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { - properties.outputNode = builder.stack; - } - - for (const childNode of Object.values(properties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } else if (buildStage === 'analyze') { - this.analyze(builder); - } else if (buildStage === 'generate') { - const isGenerateOnce = this.generate.length === 1; - - if (isGenerateOnce) { - const type = this.getNodeType(builder); - const nodeData = builder.getDataFromNode(this); - - result = nodeData.snippet; - - if (result === undefined /*|| builder.context.tempRead === false*/) { - result = this.generate(builder) || ''; - - nodeData.snippet = result; - } - - result = builder.format(result, type, output); - } else { - result = this.generate(builder, output) || ''; - } - } - - builder.removeChain(this); - - return result; - } - - getSerializeChildren() { - return getNodeChildren(this); - } - - serialize(json) { - const nodeChildren = this.getSerializeChildren(); - - const inputNodes = {}; - - for (const { property, index, childNode } of nodeChildren) { - if (index !== undefined) { - if (inputNodes[property] === undefined) { - inputNodes[property] = Number.isInteger(index) ? [] : {}; - } - - inputNodes[property][index] = childNode.toJSON(json.meta).uuid; - } else { - inputNodes[property] = childNode.toJSON(json.meta).uuid; - } - } - - if (Object.keys(inputNodes).length > 0) { - json.inputNodes = inputNodes; - } - } - - deserialize(json) { - if (json.inputNodes !== undefined) { - const nodes = json.meta.nodes; - - for (const property in json.inputNodes) { - if (Array.isArray(json.inputNodes[property])) { - const inputArray = []; - - for (const uuid of json.inputNodes[property]) { - inputArray.push(nodes[uuid]); - } - - this[property] = inputArray; - } else if (typeof json.inputNodes[property] === 'object') { - const inputObject = {}; - - for (const subProperty in json.inputNodes[property]) { - const uuid = json.inputNodes[property][subProperty]; - - inputObject[subProperty] = nodes[uuid]; - } - - this[property] = inputObject; - } else { - const uuid = json.inputNodes[property]; - - this[property] = nodes[uuid]; - } - } - } - } - - toJSON(meta) { - const { uuid, type } = this; - const isRoot = meta === undefined || typeof meta === 'string'; - - if (isRoot) { - meta = { - textures: {}, - images: {}, - nodes: {}, - }; - } - - // serialize - - let data = meta.nodes[uuid]; - - if (data === undefined) { - data = { - uuid, - type, - meta, - metadata: { - version: 4.6, - type: 'Node', - generator: 'Node.toJSON', - }, - }; - - if (isRoot !== true) meta.nodes[data.uuid] = data; - - this.serialize(data); - - delete data.meta; - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache(cache) { - const values = []; - - for (const key in cache) { - const data = cache[key]; - delete data.metadata; - values.push(data); - } - - return values; - } - - if (isRoot) { - const textures = extractFromCache(meta.textures); - const images = extractFromCache(meta.images); - const nodes = extractFromCache(meta.nodes); - - if (textures.length > 0) data.textures = textures; - if (images.length > 0) data.images = images; - if (nodes.length > 0) data.nodes = nodes; - } - - return data; - } -} - -export default Node; - -export function addNodeClass(type, nodeClass) { - if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); - if (NodeClasses.has(type)) { - console.warn(`Redefinition of node class ${type}`); - return; - } - - NodeClasses.set(type, nodeClass); - nodeClass.type = type; -} - -export function createNodeFromType(type) { - const Class = NodeClasses.get(type); - - if (Class !== undefined) { - return new Class(); - } -} diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts deleted file mode 100644 index ebdc13ff1..000000000 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ /dev/null @@ -1,1011 +0,0 @@ -import NodeUniform from './NodeUniform.js'; -import NodeAttribute from './NodeAttribute.js'; -import NodeVarying from './NodeVarying.js'; -import NodeVar from './NodeVar.js'; -import NodeCode from './NodeCode.js'; -import NodeKeywords from './NodeKeywords.js'; -import NodeCache from './NodeCache.js'; -import ParameterNode from './ParameterNode.js'; -import FunctionNode from '../code/FunctionNode.js'; -import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; -import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; - -import { - FloatNodeUniform, - Vector2NodeUniform, - Vector3NodeUniform, - Vector4NodeUniform, - ColorNodeUniform, - Matrix3NodeUniform, - Matrix4NodeUniform, -} from '../../renderers/common/nodes/NodeUniform.js'; - -import { - REVISION, - RenderTarget, - Color, - Vector2, - Vector3, - Vector4, - IntType, - UnsignedIntType, - Float16BufferAttribute, -} from 'three'; - -import { stack } from './StackNode.js'; -import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; - -import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; -import ChainMap from '../../renderers/common/ChainMap.js'; - -import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; - -const uniformsGroupCache = new ChainMap(); - -const typeFromLength = new Map([ - [2, 'vec2'], - [3, 'vec3'], - [4, 'vec4'], - [9, 'mat3'], - [16, 'mat4'], -]); - -const typeFromArray = new Map([ - [Int8Array, 'int'], - [Int16Array, 'int'], - [Int32Array, 'int'], - [Uint8Array, 'uint'], - [Uint16Array, 'uint'], - [Uint32Array, 'uint'], - [Float32Array, 'float'], -]); - -const toFloat = value => { - value = Number(value); - - return value + (value % 1 ? '' : '.0'); -}; - -class NodeBuilder { - constructor(object, renderer, parser, scene = null, material = null) { - this.object = object; - this.material = material || (object && object.material) || null; - this.geometry = (object && object.geometry) || null; - this.renderer = renderer; - this.parser = parser; - this.scene = scene; - - this.nodes = []; - this.updateNodes = []; - this.updateBeforeNodes = []; - this.hashNodes = {}; - - this.lightsNode = null; - this.environmentNode = null; - this.fogNode = null; - - this.clippingContext = null; - - this.vertexShader = null; - this.fragmentShader = null; - this.computeShader = null; - - this.flowNodes = { vertex: [], fragment: [], compute: [] }; - this.flowCode = { vertex: '', fragment: '', compute: [] }; - this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; - this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; - this.bindings = { vertex: [], fragment: [], compute: [] }; - this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; - this.bindingsArray = null; - this.attributes = []; - this.bufferAttributes = []; - this.varyings = []; - this.codes = {}; - this.vars = {}; - this.flow = { code: '' }; - this.chaining = []; - this.stack = stack(); - this.stacks = []; - this.tab = '\t'; - - this.currentFunctionNode = null; - - this.context = { - keywords: new NodeKeywords(), - material: this.material, - }; - - this.cache = new NodeCache(); - this.globalCache = this.cache; - - this.flowsData = new WeakMap(); - - this.shaderStage = null; - this.buildStage = null; - } - - createRenderTarget(width, height, options) { - return new RenderTarget(width, height, options); - } - - createCubeRenderTarget(size, options) { - return new CubeRenderTarget(size, options); - } - - createPMREMGenerator() { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return new PMREMGenerator(this.renderer); - } - - includes(node) { - return this.nodes.includes(node); - } - - _getSharedBindings(bindings) { - const shared = []; - - for (const binding of bindings) { - if (binding.shared === true) { - // nodes is the chainmap key - const nodes = binding.getNodes(); - - let sharedBinding = uniformsGroupCache.get(nodes); - - if (sharedBinding === undefined) { - uniformsGroupCache.set(nodes, binding); - - sharedBinding = binding; - } - - shared.push(sharedBinding); - } else { - shared.push(binding); - } - } - - return shared; - } - - getBindings() { - let bindingsArray = this.bindingsArray; - - if (bindingsArray === null) { - const bindings = this.bindings; - - this.bindingsArray = bindingsArray = this._getSharedBindings( - this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, - ); - } - - return bindingsArray; - } - - setHashNode(node, hash) { - this.hashNodes[hash] = node; - } - - addNode(node) { - if (this.nodes.includes(node) === false) { - this.nodes.push(node); - - this.setHashNode(node, node.getHash(this)); - } - } - - buildUpdateNodes() { - for (const node of this.nodes) { - const updateType = node.getUpdateType(); - const updateBeforeType = node.getUpdateBeforeType(); - - if (updateType !== NodeUpdateType.NONE) { - this.updateNodes.push(node.getSelf()); - } - - if (updateBeforeType !== NodeUpdateType.NONE) { - this.updateBeforeNodes.push(node); - } - } - } - - get currentNode() { - return this.chaining[this.chaining.length - 1]; - } - - addChain(node) { - /* - if ( this.chaining.indexOf( node ) !== - 1 ) { - - console.warn( 'Recursive node: ', node ); - - } - */ - - this.chaining.push(node); - } - - removeChain(node) { - const lastChain = this.chaining.pop(); - - if (lastChain !== node) { - throw new Error('NodeBuilder: Invalid node chaining!'); - } - } - - getMethod(method) { - return method; - } - - getNodeFromHash(hash) { - return this.hashNodes[hash]; - } - - addFlow(shaderStage, node) { - this.flowNodes[shaderStage].push(node); - - return node; - } - - setContext(context) { - this.context = context; - } - - getContext() { - return this.context; - } - - setCache(cache) { - this.cache = cache; - } - - getCache() { - return this.cache; - } - - isAvailable(/*name*/) { - return false; - } - - getVertexIndex() { - console.warn('Abstract function.'); - } - - getInstanceIndex() { - console.warn('Abstract function.'); - } - - getFrontFacing() { - console.warn('Abstract function.'); - } - - getFragCoord() { - console.warn('Abstract function.'); - } - - isFlipY() { - return false; - } - - generateTexture(/* texture, textureProperty, uvSnippet */) { - console.warn('Abstract function.'); - } - - generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { - console.warn('Abstract function.'); - } - - generateConst(type, value = null) { - if (value === null) { - if (type === 'float' || type === 'int' || type === 'uint') value = 0; - else if (type === 'bool') value = false; - else if (type === 'color') value = new Color(); - else if (type === 'vec2') value = new Vector2(); - else if (type === 'vec3') value = new Vector3(); - else if (type === 'vec4') value = new Vector4(); - } - - if (type === 'float') return toFloat(value); - if (type === 'int') return `${Math.round(value)}`; - if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; - if (type === 'bool') return value ? 'true' : 'false'; - if (type === 'color') - return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; - - const typeLength = this.getTypeLength(type); - - const componentType = this.getComponentType(type); - - const generateConst = value => this.generateConst(componentType, value); - - if (typeLength === 2) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; - } else if (typeLength === 3) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; - } else if (typeLength === 4) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; - } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { - return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; - } else if (typeLength > 4) { - return `${this.getType(type)}()`; - } - - throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); - } - - getType(type) { - if (type === 'color') return 'vec3'; - - return type; - } - - generateMethod(method) { - return method; - } - - hasGeometryAttribute(name) { - return this.geometry && this.geometry.getAttribute(name) !== undefined; - } - - getAttribute(name, type) { - const attributes = this.attributes; - - // find attribute - - for (const attribute of attributes) { - if (attribute.name === name) { - return attribute; - } - } - - // create a new if no exist - - const attribute = new NodeAttribute(name, type); - - attributes.push(attribute); - - return attribute; - } - - getPropertyName(node /*, shaderStage*/) { - return node.name; - } - - isVector(type) { - return /vec\d/.test(type); - } - - isMatrix(type) { - return /mat\d/.test(type); - } - - isReference(type) { - return ( - type === 'void' || - type === 'property' || - type === 'sampler' || - type === 'texture' || - type === 'cubeTexture' || - type === 'storageTexture' || - type === 'texture3D' - ); - } - - needsColorSpaceToLinear(/*texture*/) { - return false; - } - - getComponentTypeFromTexture(texture) { - const type = texture.type; - - if (texture.isDataTexture) { - if (type === IntType) return 'int'; - if (type === UnsignedIntType) return 'uint'; - } - - return 'float'; - } - - getElementType(type) { - if (type === 'mat2') return 'vec2'; - if (type === 'mat3') return 'vec3'; - if (type === 'mat4') return 'vec4'; - - return this.getComponentType(type); - } - - getComponentType(type) { - type = this.getVectorType(type); - - if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; - - const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); - - if (componentType === null) return null; - - if (componentType[1] === 'b') return 'bool'; - if (componentType[1] === 'i') return 'int'; - if (componentType[1] === 'u') return 'uint'; - - return 'float'; - } - - getVectorType(type) { - if (type === 'color') return 'vec3'; - if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') - return 'vec4'; - - return type; - } - - getTypeFromLength(length, componentType = 'float') { - if (length === 1) return componentType; - - const baseType = typeFromLength.get(length); - const prefix = componentType === 'float' ? '' : componentType[0]; - - return prefix + baseType; - } - - getTypeFromArray(array) { - return typeFromArray.get(array.constructor); - } - - getTypeFromAttribute(attribute) { - let dataAttribute = attribute; - - if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; - - const array = dataAttribute.array; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; - - let arrayType; - - if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { - arrayType = this.getTypeFromArray(array); - } - - return this.getTypeFromLength(itemSize, arrayType); - } - - getTypeLength(type) { - const vecType = this.getVectorType(type); - const vecNum = /vec([2-4])/.exec(vecType); - - if (vecNum !== null) return Number(vecNum[1]); - if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; - if (/mat2/.test(type) === true) return 4; - if (/mat3/.test(type) === true) return 9; - if (/mat4/.test(type) === true) return 16; - - return 0; - } - - getVectorFromMatrix(type) { - return type.replace('mat', 'vec'); - } - - changeComponentType(type, newComponentType) { - return this.getTypeFromLength(this.getTypeLength(type), newComponentType); - } - - getIntegerType(type) { - const componentType = this.getComponentType(type); - - if (componentType === 'int' || componentType === 'uint') return type; - - return this.changeComponentType(type, 'int'); - } - - addStack() { - this.stack = stack(this.stack); - - this.stacks.push(getCurrentStack() || this.stack); - setCurrentStack(this.stack); - - return this.stack; - } - - removeStack() { - const lastStack = this.stack; - this.stack = lastStack.parent; - - setCurrentStack(this.stacks.pop()); - - return lastStack; - } - - getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { - cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; - - let nodeData = cache.getNodeData(node); - - if (nodeData === undefined) { - nodeData = {}; - - cache.setNodeData(node, nodeData); - } - - if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; - - return nodeData[shaderStage]; - } - - getNodeProperties(node, shaderStage = 'any') { - const nodeData = this.getDataFromNode(node, shaderStage); - - return nodeData.properties || (nodeData.properties = { outputNode: null }); - } - - getBufferAttributeFromNode(node, type) { - const nodeData = this.getDataFromNode(node); - - let bufferAttribute = nodeData.bufferAttribute; - - if (bufferAttribute === undefined) { - const index = this.uniforms.index++; - - bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); - - this.bufferAttributes.push(bufferAttribute); - - nodeData.bufferAttribute = bufferAttribute; - } - - return bufferAttribute; - } - - getStructTypeFromNode(node, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - if (nodeData.structType === undefined) { - const index = this.structs.index++; - - node.name = `StructType${index}`; - this.structs[shaderStage].push(node); - - nodeData.structType = node; - } - - return node; - } - - getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { - const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); - - let nodeUniform = nodeData.uniform; - - if (nodeUniform === undefined) { - const index = this.uniforms.index++; - - nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); - - this.uniforms[shaderStage].push(nodeUniform); - - nodeData.uniform = nodeUniform; - } - - return nodeUniform; - } - - getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - let nodeVar = nodeData.variable; - - if (nodeVar === undefined) { - const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); - - if (name === null) name = 'nodeVar' + vars.length; - - nodeVar = new NodeVar(name, type); - - vars.push(nodeVar); - - nodeData.variable = nodeVar; - } - - return nodeVar; - } - - getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { - const nodeData = this.getDataFromNode(node, 'any'); - - let nodeVarying = nodeData.varying; - - if (nodeVarying === undefined) { - const varyings = this.varyings; - const index = varyings.length; - - if (name === null) name = 'nodeVarying' + index; - - nodeVarying = new NodeVarying(name, type); - - varyings.push(nodeVarying); - - nodeData.varying = nodeVarying; - } - - return nodeVarying; - } - - getCodeFromNode(node, type, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node); - - let nodeCode = nodeData.code; - - if (nodeCode === undefined) { - const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); - const index = codes.length; - - nodeCode = new NodeCode('nodeCode' + index, type); - - codes.push(nodeCode); - - nodeData.code = nodeCode; - } - - return nodeCode; - } - - addLineFlowCode(code) { - if (code === '') return this; - - code = this.tab + code; - - if (!/;\s*$/.test(code)) { - code = code + ';\n'; - } - - this.flow.code += code; - - return this; - } - - addFlowCode(code) { - this.flow.code += code; - - return this; - } - - addFlowTab() { - this.tab += '\t'; - - return this; - } - - removeFlowTab() { - this.tab = this.tab.slice(0, -1); - - return this; - } - - getFlowData(node /*, shaderStage*/) { - return this.flowsData.get(node); - } - - flowNode(node) { - const output = node.getNodeType(this); - - const flowData = this.flowChildNode(node, output); - - this.flowsData.set(node, flowData); - - return flowData; - } - - buildFunctionNode(shaderNode) { - const fn = new FunctionNode(); - - const previous = this.currentFunctionNode; - - this.currentFunctionNode = fn; - - fn.code = this.buildFunctionCode(shaderNode); - - this.currentFunctionNode = previous; - - return fn; - } - - flowShaderNode(shaderNode) { - const layout = shaderNode.layout; - - let inputs; - - if (shaderNode.isArrayInput) { - inputs = []; - - for (const input of layout.inputs) { - inputs.push(new ParameterNode(input.type, input.name)); - } - } else { - inputs = {}; - - for (const input of layout.inputs) { - inputs[input.name] = new ParameterNode(input.type, input.name); - } - } - - // - - shaderNode.layout = null; - - const callNode = shaderNode.call(inputs); - const flowData = this.flowStagesNode(callNode, layout.type); - - shaderNode.layout = layout; - - return flowData; - } - - flowStagesNode(node, output = null) { - const previousFlow = this.flow; - const previousVars = this.vars; - const previousBuildStage = this.buildStage; - - const flow = { - code: '', - }; - - this.flow = flow; - this.vars = {}; - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - flow.result = node.build(this, output); - } - - flow.vars = this.getVars(this.shaderStage); - - this.flow = previousFlow; - this.vars = previousVars; - this.setBuildStage(previousBuildStage); - - return flow; - } - - getFunctionOperator() { - return null; - } - - flowChildNode(node, output = null) { - const previousFlow = this.flow; - - const flow = { - code: '', - }; - - this.flow = flow; - - flow.result = node.build(this, output); - - this.flow = previousFlow; - - return flow; - } - - flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { - const previousShaderStage = this.shaderStage; - - this.setShaderStage(shaderStage); - - const flowData = this.flowChildNode(node, output); - - if (propertyName !== null) { - flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; - } - - this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; - - this.setShaderStage(previousShaderStage); - - return flowData; - } - - getAttributesArray() { - return this.attributes.concat(this.bufferAttributes); - } - - getAttributes(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVaryings(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVar(type, name) { - return `${this.getType(type)} ${name}`; - } - - getVars(shaderStage) { - let snippet = ''; - - const vars = this.vars[shaderStage]; - - if (vars !== undefined) { - for (const variable of vars) { - snippet += `${this.getVar(variable.type, variable.name)}; `; - } - } - - return snippet; - } - - getUniforms(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getCodes(shaderStage) { - const codes = this.codes[shaderStage]; - - let code = ''; - - if (codes !== undefined) { - for (const nodeCode of codes) { - code += nodeCode.code + '\n'; - } - } - - return code; - } - - getHash() { - return this.vertexShader + this.fragmentShader + this.computeShader; - } - - setShaderStage(shaderStage) { - this.shaderStage = shaderStage; - } - - getShaderStage() { - return this.shaderStage; - } - - setBuildStage(buildStage) { - this.buildStage = buildStage; - } - - getBuildStage() { - return this.buildStage; - } - - buildCode() { - console.warn('Abstract function.'); - } - - build() { - const { object, material } = this; - - if (material !== null) { - NodeMaterial.fromMaterial(material).build(this); - } else { - this.addFlow('compute', object); - } - - // setup() -> stage 1: create possible new nodes and returns an output reference node - // analyze() -> stage 2: analyze nodes to possible optimization and validation - // generate() -> stage 3: generate shader - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - if (this.context.vertex && this.context.vertex.isNode) { - this.flowNodeFromShaderStage('vertex', this.context.vertex); - } - - for (const shaderStage of shaderStages) { - this.setShaderStage(shaderStage); - - const flowNodes = this.flowNodes[shaderStage]; - - for (const node of flowNodes) { - if (buildStage === 'generate') { - this.flowNode(node); - } else { - node.build(this); - } - } - } - } - - this.setBuildStage(null); - this.setShaderStage(null); - - // stage 4: build code for a specific output - - this.buildCode(); - this.buildUpdateNodes(); - - return this; - } - - getNodeUniform(uniformNode, type) { - if (type === 'float') return new FloatNodeUniform(uniformNode); - if (type === 'vec2') return new Vector2NodeUniform(uniformNode); - if (type === 'vec3') return new Vector3NodeUniform(uniformNode); - if (type === 'vec4') return new Vector4NodeUniform(uniformNode); - if (type === 'color') return new ColorNodeUniform(uniformNode); - if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); - if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); - - throw new Error(`Uniform "${type}" not declared.`); - } - - createNodeMaterial(type = 'NodeMaterial') { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return createNodeMaterialFromType(type); - } - - format(snippet, fromType, toType) { - fromType = this.getVectorType(fromType); - toType = this.getVectorType(toType); - - if (fromType === toType || toType === null || this.isReference(toType)) { - return snippet; - } - - const fromTypeLength = this.getTypeLength(fromType); - const toTypeLength = this.getTypeLength(toType); - - if (fromTypeLength > 4) { - // fromType is matrix-like - - // @TODO: ignore for now - - return snippet; - } - - if (toTypeLength > 4 || toTypeLength === 0) { - // toType is matrix-like or unknown - - // @TODO: ignore for now - - return snippet; - } - - if (fromTypeLength === toTypeLength) { - return `${this.getType(toType)}( ${snippet} )`; - } - - if (fromTypeLength > toTypeLength) { - return this.format( - `${snippet}.${'xyz'.slice(0, toTypeLength)}`, - this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), - toType, - ); - } - - if (toTypeLength === 4 && fromTypeLength > 1) { - // toType is vec4-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; - } - - if (fromTypeLength === 2) { - // fromType is vec2-like and toType is vec3-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; - } - - if (fromTypeLength === 1 && toTypeLength > 1 && fromType[0] !== toType[0]) { - // fromType is float-like - - // convert a number value to vector type, e.g: - // vec3( 1u ) -> vec3( float( 1u ) ) - - snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; - } - - return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like - } - - getSignature() { - return `// Three.js r${REVISION} - NodeMaterial System\n`; - } -} - -export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts deleted file mode 100644 index 3b01a9a6d..000000000 --- a/examples-jsm/examples/nodes/core/constants.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const NodeShaderStage = { - VERTEX: 'vertex', - FRAGMENT: 'fragment', -}; - -export const NodeUpdateType = { - NONE: 'none', - FRAME: 'frame', - RENDER: 'render', - OBJECT: 'object', -}; - -export const NodeType = { - BOOLEAN: 'bool', - INTEGER: 'int', - FLOAT: 'float', - VECTOR2: 'vec2', - VECTOR3: 'vec3', - VECTOR4: 'vec4', - MATRIX2: 'mat2', - MATRIX3: 'mat3', - MATRIX4: 'mat4', -}; - -export const defaultShaderStages = ['fragment', 'vertex']; -export const defaultBuildStages = ['setup', 'analyze', 'generate']; -export const shaderStages = [...defaultShaderStages, 'compute']; -export const vectorComponents = ['x', 'y', 'z', 'w']; From ecec9fd2d121f6c686080c5b74664a3841a1a0b0 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:30:33 -0400 Subject: [PATCH 09/18] Add examples --- examples-jsm/examples/nodes/core/Node.ts | 411 +++++++ .../examples/nodes/core/NodeBuilder.ts | 1011 +++++++++++++++++ examples-jsm/examples/nodes/core/NodeFrame.ts | 101 ++ examples-jsm/examples/nodes/core/constants.ts | 28 + examples-jsm/index.js | 2 +- 5 files changed, 1552 insertions(+), 1 deletion(-) create mode 100644 examples-jsm/examples/nodes/core/Node.ts create mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts create mode 100644 examples-jsm/examples/nodes/core/NodeFrame.ts create mode 100644 examples-jsm/examples/nodes/core/constants.ts diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts new file mode 100644 index 000000000..438c44dd1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/Node.ts @@ -0,0 +1,411 @@ +import { EventDispatcher } from 'three'; +import { NodeUpdateType } from './constants.js'; +import { getNodeChildren, getCacheKey } from './NodeUtils.js'; +import { MathUtils } from 'three'; + +const NodeClasses = new Map(); + +let _nodeId = 0; + +class Node extends EventDispatcher { + constructor(nodeType = null) { + super(); + + this.nodeType = nodeType; + + this.updateType = NodeUpdateType.NONE; + this.updateBeforeType = NodeUpdateType.NONE; + + this.uuid = MathUtils.generateUUID(); + + this.version = 0; + + this._cacheKey = null; + this._cacheKeyVersion = 0; + + this.isNode = true; + + Object.defineProperty(this, 'id', { value: _nodeId++ }); + } + + set needsUpdate(value) { + if (value === true) { + this.version++; + } + } + + get type() { + return this.constructor.type; + } + + onUpdate(callback, updateType) { + this.updateType = updateType; + this.update = callback.bind(this.getSelf()); + + return this; + } + + onFrameUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.FRAME); + } + + onRenderUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.RENDER); + } + + onObjectUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.OBJECT); + } + + onReference(callback) { + this.updateReference = callback.bind(this.getSelf()); + + return this; + } + + getSelf() { + // Returns non-node object. + + return this.self || this; + } + + updateReference(/*state*/) { + return this; + } + + isGlobal(/*builder*/) { + return false; + } + + *getChildren() { + for (const { childNode } of getNodeChildren(this)) { + yield childNode; + } + } + + dispose() { + this.dispatchEvent({ type: 'dispose' }); + } + + traverse(callback) { + callback(this); + + for (const childNode of this.getChildren()) { + childNode.traverse(callback); + } + } + + getCacheKey(force = false) { + force = force || this.version !== this._cacheKeyVersion; + + if (force === true || this._cacheKey === null) { + this._cacheKey = getCacheKey(this, force); + this._cacheKeyVersion = this.version; + } + + return this._cacheKey; + } + + getHash(/*builder*/) { + return this.uuid; + } + + getUpdateType() { + return this.updateType; + } + + getUpdateBeforeType() { + return this.updateBeforeType; + } + + getElementType(builder) { + const type = this.getNodeType(builder); + const elementType = builder.getElementType(type); + + return elementType; + } + + getNodeType(builder) { + const nodeProperties = builder.getNodeProperties(this); + + if (nodeProperties.outputNode) { + return nodeProperties.outputNode.getNodeType(builder); + } + + return this.nodeType; + } + + getShared(builder) { + const hash = this.getHash(builder); + const nodeFromHash = builder.getNodeFromHash(hash); + + return nodeFromHash || this; + } + + setup(builder) { + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of this.getChildren()) { + nodeProperties['_node' + childNode.id] = childNode; + } + + // return a outputNode if exists + return null; + } + + construct(builder) { + // @deprecated, r157 + + console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); + + return this.setup(builder); + } + + increaseUsage(builder) { + const nodeData = builder.getDataFromNode(this); + nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; + + return nodeData.usageCount; + } + + analyze(builder) { + const usageCount = this.increaseUsage(builder); + + if (usageCount === 1) { + // node flow children + + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of Object.values(nodeProperties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } + + generate(builder, output) { + const { outputNode } = builder.getNodeProperties(this); + + if (outputNode && outputNode.isNode === true) { + return outputNode.build(builder, output); + } + } + + updateBefore(/*frame*/) { + console.warn('Abstract function.'); + } + + update(/*frame*/) { + console.warn('Abstract function.'); + } + + build(builder, output = null) { + const refNode = this.getShared(builder); + + if (this !== refNode) { + return refNode.build(builder, output); + } + + builder.addNode(this); + builder.addChain(this); + + /* Build stages expected results: + - "setup" -> Node + - "analyze" -> null + - "generate" -> String + */ + let result = null; + + const buildStage = builder.getBuildStage(); + + if (buildStage === 'setup') { + this.updateReference(builder); + + const properties = builder.getNodeProperties(this); + + if (properties.initialized !== true || builder.context.tempRead === false) { + const stackNodesBeforeSetup = builder.stack.nodes.length; + + properties.initialized = true; + properties.outputNode = this.setup(builder); + + if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { + properties.outputNode = builder.stack; + } + + for (const childNode of Object.values(properties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } else if (buildStage === 'analyze') { + this.analyze(builder); + } else if (buildStage === 'generate') { + const isGenerateOnce = this.generate.length === 1; + + if (isGenerateOnce) { + const type = this.getNodeType(builder); + const nodeData = builder.getDataFromNode(this); + + result = nodeData.snippet; + + if (result === undefined /*|| builder.context.tempRead === false*/) { + result = this.generate(builder) || ''; + + nodeData.snippet = result; + } + + result = builder.format(result, type, output); + } else { + result = this.generate(builder, output) || ''; + } + } + + builder.removeChain(this); + + return result; + } + + getSerializeChildren() { + return getNodeChildren(this); + } + + serialize(json) { + const nodeChildren = this.getSerializeChildren(); + + const inputNodes = {}; + + for (const { property, index, childNode } of nodeChildren) { + if (index !== undefined) { + if (inputNodes[property] === undefined) { + inputNodes[property] = Number.isInteger(index) ? [] : {}; + } + + inputNodes[property][index] = childNode.toJSON(json.meta).uuid; + } else { + inputNodes[property] = childNode.toJSON(json.meta).uuid; + } + } + + if (Object.keys(inputNodes).length > 0) { + json.inputNodes = inputNodes; + } + } + + deserialize(json) { + if (json.inputNodes !== undefined) { + const nodes = json.meta.nodes; + + for (const property in json.inputNodes) { + if (Array.isArray(json.inputNodes[property])) { + const inputArray = []; + + for (const uuid of json.inputNodes[property]) { + inputArray.push(nodes[uuid]); + } + + this[property] = inputArray; + } else if (typeof json.inputNodes[property] === 'object') { + const inputObject = {}; + + for (const subProperty in json.inputNodes[property]) { + const uuid = json.inputNodes[property][subProperty]; + + inputObject[subProperty] = nodes[uuid]; + } + + this[property] = inputObject; + } else { + const uuid = json.inputNodes[property]; + + this[property] = nodes[uuid]; + } + } + } + } + + toJSON(meta) { + const { uuid, type } = this; + const isRoot = meta === undefined || typeof meta === 'string'; + + if (isRoot) { + meta = { + textures: {}, + images: {}, + nodes: {}, + }; + } + + // serialize + + let data = meta.nodes[uuid]; + + if (data === undefined) { + data = { + uuid, + type, + meta, + metadata: { + version: 4.6, + type: 'Node', + generator: 'Node.toJSON', + }, + }; + + if (isRoot !== true) meta.nodes[data.uuid] = data; + + this.serialize(data); + + delete data.meta; + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache(cache) { + const values = []; + + for (const key in cache) { + const data = cache[key]; + delete data.metadata; + values.push(data); + } + + return values; + } + + if (isRoot) { + const textures = extractFromCache(meta.textures); + const images = extractFromCache(meta.images); + const nodes = extractFromCache(meta.nodes); + + if (textures.length > 0) data.textures = textures; + if (images.length > 0) data.images = images; + if (nodes.length > 0) data.nodes = nodes; + } + + return data; + } +} + +export default Node; + +export function addNodeClass(type, nodeClass) { + if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); + if (NodeClasses.has(type)) { + console.warn(`Redefinition of node class ${type}`); + return; + } + + NodeClasses.set(type, nodeClass); + nodeClass.type = type; +} + +export function createNodeFromType(type) { + const Class = NodeClasses.get(type); + + if (Class !== undefined) { + return new Class(); + } +} diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts new file mode 100644 index 000000000..ebdc13ff1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts @@ -0,0 +1,1011 @@ +import NodeUniform from './NodeUniform.js'; +import NodeAttribute from './NodeAttribute.js'; +import NodeVarying from './NodeVarying.js'; +import NodeVar from './NodeVar.js'; +import NodeCode from './NodeCode.js'; +import NodeKeywords from './NodeKeywords.js'; +import NodeCache from './NodeCache.js'; +import ParameterNode from './ParameterNode.js'; +import FunctionNode from '../code/FunctionNode.js'; +import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; +import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; + +import { + FloatNodeUniform, + Vector2NodeUniform, + Vector3NodeUniform, + Vector4NodeUniform, + ColorNodeUniform, + Matrix3NodeUniform, + Matrix4NodeUniform, +} from '../../renderers/common/nodes/NodeUniform.js'; + +import { + REVISION, + RenderTarget, + Color, + Vector2, + Vector3, + Vector4, + IntType, + UnsignedIntType, + Float16BufferAttribute, +} from 'three'; + +import { stack } from './StackNode.js'; +import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; + +import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; +import ChainMap from '../../renderers/common/ChainMap.js'; + +import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; + +const uniformsGroupCache = new ChainMap(); + +const typeFromLength = new Map([ + [2, 'vec2'], + [3, 'vec3'], + [4, 'vec4'], + [9, 'mat3'], + [16, 'mat4'], +]); + +const typeFromArray = new Map([ + [Int8Array, 'int'], + [Int16Array, 'int'], + [Int32Array, 'int'], + [Uint8Array, 'uint'], + [Uint16Array, 'uint'], + [Uint32Array, 'uint'], + [Float32Array, 'float'], +]); + +const toFloat = value => { + value = Number(value); + + return value + (value % 1 ? '' : '.0'); +}; + +class NodeBuilder { + constructor(object, renderer, parser, scene = null, material = null) { + this.object = object; + this.material = material || (object && object.material) || null; + this.geometry = (object && object.geometry) || null; + this.renderer = renderer; + this.parser = parser; + this.scene = scene; + + this.nodes = []; + this.updateNodes = []; + this.updateBeforeNodes = []; + this.hashNodes = {}; + + this.lightsNode = null; + this.environmentNode = null; + this.fogNode = null; + + this.clippingContext = null; + + this.vertexShader = null; + this.fragmentShader = null; + this.computeShader = null; + + this.flowNodes = { vertex: [], fragment: [], compute: [] }; + this.flowCode = { vertex: '', fragment: '', compute: [] }; + this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; + this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; + this.bindings = { vertex: [], fragment: [], compute: [] }; + this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; + this.bindingsArray = null; + this.attributes = []; + this.bufferAttributes = []; + this.varyings = []; + this.codes = {}; + this.vars = {}; + this.flow = { code: '' }; + this.chaining = []; + this.stack = stack(); + this.stacks = []; + this.tab = '\t'; + + this.currentFunctionNode = null; + + this.context = { + keywords: new NodeKeywords(), + material: this.material, + }; + + this.cache = new NodeCache(); + this.globalCache = this.cache; + + this.flowsData = new WeakMap(); + + this.shaderStage = null; + this.buildStage = null; + } + + createRenderTarget(width, height, options) { + return new RenderTarget(width, height, options); + } + + createCubeRenderTarget(size, options) { + return new CubeRenderTarget(size, options); + } + + createPMREMGenerator() { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return new PMREMGenerator(this.renderer); + } + + includes(node) { + return this.nodes.includes(node); + } + + _getSharedBindings(bindings) { + const shared = []; + + for (const binding of bindings) { + if (binding.shared === true) { + // nodes is the chainmap key + const nodes = binding.getNodes(); + + let sharedBinding = uniformsGroupCache.get(nodes); + + if (sharedBinding === undefined) { + uniformsGroupCache.set(nodes, binding); + + sharedBinding = binding; + } + + shared.push(sharedBinding); + } else { + shared.push(binding); + } + } + + return shared; + } + + getBindings() { + let bindingsArray = this.bindingsArray; + + if (bindingsArray === null) { + const bindings = this.bindings; + + this.bindingsArray = bindingsArray = this._getSharedBindings( + this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, + ); + } + + return bindingsArray; + } + + setHashNode(node, hash) { + this.hashNodes[hash] = node; + } + + addNode(node) { + if (this.nodes.includes(node) === false) { + this.nodes.push(node); + + this.setHashNode(node, node.getHash(this)); + } + } + + buildUpdateNodes() { + for (const node of this.nodes) { + const updateType = node.getUpdateType(); + const updateBeforeType = node.getUpdateBeforeType(); + + if (updateType !== NodeUpdateType.NONE) { + this.updateNodes.push(node.getSelf()); + } + + if (updateBeforeType !== NodeUpdateType.NONE) { + this.updateBeforeNodes.push(node); + } + } + } + + get currentNode() { + return this.chaining[this.chaining.length - 1]; + } + + addChain(node) { + /* + if ( this.chaining.indexOf( node ) !== - 1 ) { + + console.warn( 'Recursive node: ', node ); + + } + */ + + this.chaining.push(node); + } + + removeChain(node) { + const lastChain = this.chaining.pop(); + + if (lastChain !== node) { + throw new Error('NodeBuilder: Invalid node chaining!'); + } + } + + getMethod(method) { + return method; + } + + getNodeFromHash(hash) { + return this.hashNodes[hash]; + } + + addFlow(shaderStage, node) { + this.flowNodes[shaderStage].push(node); + + return node; + } + + setContext(context) { + this.context = context; + } + + getContext() { + return this.context; + } + + setCache(cache) { + this.cache = cache; + } + + getCache() { + return this.cache; + } + + isAvailable(/*name*/) { + return false; + } + + getVertexIndex() { + console.warn('Abstract function.'); + } + + getInstanceIndex() { + console.warn('Abstract function.'); + } + + getFrontFacing() { + console.warn('Abstract function.'); + } + + getFragCoord() { + console.warn('Abstract function.'); + } + + isFlipY() { + return false; + } + + generateTexture(/* texture, textureProperty, uvSnippet */) { + console.warn('Abstract function.'); + } + + generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { + console.warn('Abstract function.'); + } + + generateConst(type, value = null) { + if (value === null) { + if (type === 'float' || type === 'int' || type === 'uint') value = 0; + else if (type === 'bool') value = false; + else if (type === 'color') value = new Color(); + else if (type === 'vec2') value = new Vector2(); + else if (type === 'vec3') value = new Vector3(); + else if (type === 'vec4') value = new Vector4(); + } + + if (type === 'float') return toFloat(value); + if (type === 'int') return `${Math.round(value)}`; + if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; + if (type === 'bool') return value ? 'true' : 'false'; + if (type === 'color') + return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; + + const typeLength = this.getTypeLength(type); + + const componentType = this.getComponentType(type); + + const generateConst = value => this.generateConst(componentType, value); + + if (typeLength === 2) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; + } else if (typeLength === 3) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; + } else if (typeLength === 4) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; + } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { + return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; + } else if (typeLength > 4) { + return `${this.getType(type)}()`; + } + + throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); + } + + getType(type) { + if (type === 'color') return 'vec3'; + + return type; + } + + generateMethod(method) { + return method; + } + + hasGeometryAttribute(name) { + return this.geometry && this.geometry.getAttribute(name) !== undefined; + } + + getAttribute(name, type) { + const attributes = this.attributes; + + // find attribute + + for (const attribute of attributes) { + if (attribute.name === name) { + return attribute; + } + } + + // create a new if no exist + + const attribute = new NodeAttribute(name, type); + + attributes.push(attribute); + + return attribute; + } + + getPropertyName(node /*, shaderStage*/) { + return node.name; + } + + isVector(type) { + return /vec\d/.test(type); + } + + isMatrix(type) { + return /mat\d/.test(type); + } + + isReference(type) { + return ( + type === 'void' || + type === 'property' || + type === 'sampler' || + type === 'texture' || + type === 'cubeTexture' || + type === 'storageTexture' || + type === 'texture3D' + ); + } + + needsColorSpaceToLinear(/*texture*/) { + return false; + } + + getComponentTypeFromTexture(texture) { + const type = texture.type; + + if (texture.isDataTexture) { + if (type === IntType) return 'int'; + if (type === UnsignedIntType) return 'uint'; + } + + return 'float'; + } + + getElementType(type) { + if (type === 'mat2') return 'vec2'; + if (type === 'mat3') return 'vec3'; + if (type === 'mat4') return 'vec4'; + + return this.getComponentType(type); + } + + getComponentType(type) { + type = this.getVectorType(type); + + if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; + + const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); + + if (componentType === null) return null; + + if (componentType[1] === 'b') return 'bool'; + if (componentType[1] === 'i') return 'int'; + if (componentType[1] === 'u') return 'uint'; + + return 'float'; + } + + getVectorType(type) { + if (type === 'color') return 'vec3'; + if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') + return 'vec4'; + + return type; + } + + getTypeFromLength(length, componentType = 'float') { + if (length === 1) return componentType; + + const baseType = typeFromLength.get(length); + const prefix = componentType === 'float' ? '' : componentType[0]; + + return prefix + baseType; + } + + getTypeFromArray(array) { + return typeFromArray.get(array.constructor); + } + + getTypeFromAttribute(attribute) { + let dataAttribute = attribute; + + if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; + + const array = dataAttribute.array; + const itemSize = attribute.itemSize; + const normalized = attribute.normalized; + + let arrayType; + + if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { + arrayType = this.getTypeFromArray(array); + } + + return this.getTypeFromLength(itemSize, arrayType); + } + + getTypeLength(type) { + const vecType = this.getVectorType(type); + const vecNum = /vec([2-4])/.exec(vecType); + + if (vecNum !== null) return Number(vecNum[1]); + if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; + if (/mat2/.test(type) === true) return 4; + if (/mat3/.test(type) === true) return 9; + if (/mat4/.test(type) === true) return 16; + + return 0; + } + + getVectorFromMatrix(type) { + return type.replace('mat', 'vec'); + } + + changeComponentType(type, newComponentType) { + return this.getTypeFromLength(this.getTypeLength(type), newComponentType); + } + + getIntegerType(type) { + const componentType = this.getComponentType(type); + + if (componentType === 'int' || componentType === 'uint') return type; + + return this.changeComponentType(type, 'int'); + } + + addStack() { + this.stack = stack(this.stack); + + this.stacks.push(getCurrentStack() || this.stack); + setCurrentStack(this.stack); + + return this.stack; + } + + removeStack() { + const lastStack = this.stack; + this.stack = lastStack.parent; + + setCurrentStack(this.stacks.pop()); + + return lastStack; + } + + getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { + cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; + + let nodeData = cache.getNodeData(node); + + if (nodeData === undefined) { + nodeData = {}; + + cache.setNodeData(node, nodeData); + } + + if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; + + return nodeData[shaderStage]; + } + + getNodeProperties(node, shaderStage = 'any') { + const nodeData = this.getDataFromNode(node, shaderStage); + + return nodeData.properties || (nodeData.properties = { outputNode: null }); + } + + getBufferAttributeFromNode(node, type) { + const nodeData = this.getDataFromNode(node); + + let bufferAttribute = nodeData.bufferAttribute; + + if (bufferAttribute === undefined) { + const index = this.uniforms.index++; + + bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); + + this.bufferAttributes.push(bufferAttribute); + + nodeData.bufferAttribute = bufferAttribute; + } + + return bufferAttribute; + } + + getStructTypeFromNode(node, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + if (nodeData.structType === undefined) { + const index = this.structs.index++; + + node.name = `StructType${index}`; + this.structs[shaderStage].push(node); + + nodeData.structType = node; + } + + return node; + } + + getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { + const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); + + let nodeUniform = nodeData.uniform; + + if (nodeUniform === undefined) { + const index = this.uniforms.index++; + + nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); + + this.uniforms[shaderStage].push(nodeUniform); + + nodeData.uniform = nodeUniform; + } + + return nodeUniform; + } + + getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + let nodeVar = nodeData.variable; + + if (nodeVar === undefined) { + const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); + + if (name === null) name = 'nodeVar' + vars.length; + + nodeVar = new NodeVar(name, type); + + vars.push(nodeVar); + + nodeData.variable = nodeVar; + } + + return nodeVar; + } + + getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { + const nodeData = this.getDataFromNode(node, 'any'); + + let nodeVarying = nodeData.varying; + + if (nodeVarying === undefined) { + const varyings = this.varyings; + const index = varyings.length; + + if (name === null) name = 'nodeVarying' + index; + + nodeVarying = new NodeVarying(name, type); + + varyings.push(nodeVarying); + + nodeData.varying = nodeVarying; + } + + return nodeVarying; + } + + getCodeFromNode(node, type, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node); + + let nodeCode = nodeData.code; + + if (nodeCode === undefined) { + const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); + const index = codes.length; + + nodeCode = new NodeCode('nodeCode' + index, type); + + codes.push(nodeCode); + + nodeData.code = nodeCode; + } + + return nodeCode; + } + + addLineFlowCode(code) { + if (code === '') return this; + + code = this.tab + code; + + if (!/;\s*$/.test(code)) { + code = code + ';\n'; + } + + this.flow.code += code; + + return this; + } + + addFlowCode(code) { + this.flow.code += code; + + return this; + } + + addFlowTab() { + this.tab += '\t'; + + return this; + } + + removeFlowTab() { + this.tab = this.tab.slice(0, -1); + + return this; + } + + getFlowData(node /*, shaderStage*/) { + return this.flowsData.get(node); + } + + flowNode(node) { + const output = node.getNodeType(this); + + const flowData = this.flowChildNode(node, output); + + this.flowsData.set(node, flowData); + + return flowData; + } + + buildFunctionNode(shaderNode) { + const fn = new FunctionNode(); + + const previous = this.currentFunctionNode; + + this.currentFunctionNode = fn; + + fn.code = this.buildFunctionCode(shaderNode); + + this.currentFunctionNode = previous; + + return fn; + } + + flowShaderNode(shaderNode) { + const layout = shaderNode.layout; + + let inputs; + + if (shaderNode.isArrayInput) { + inputs = []; + + for (const input of layout.inputs) { + inputs.push(new ParameterNode(input.type, input.name)); + } + } else { + inputs = {}; + + for (const input of layout.inputs) { + inputs[input.name] = new ParameterNode(input.type, input.name); + } + } + + // + + shaderNode.layout = null; + + const callNode = shaderNode.call(inputs); + const flowData = this.flowStagesNode(callNode, layout.type); + + shaderNode.layout = layout; + + return flowData; + } + + flowStagesNode(node, output = null) { + const previousFlow = this.flow; + const previousVars = this.vars; + const previousBuildStage = this.buildStage; + + const flow = { + code: '', + }; + + this.flow = flow; + this.vars = {}; + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + flow.result = node.build(this, output); + } + + flow.vars = this.getVars(this.shaderStage); + + this.flow = previousFlow; + this.vars = previousVars; + this.setBuildStage(previousBuildStage); + + return flow; + } + + getFunctionOperator() { + return null; + } + + flowChildNode(node, output = null) { + const previousFlow = this.flow; + + const flow = { + code: '', + }; + + this.flow = flow; + + flow.result = node.build(this, output); + + this.flow = previousFlow; + + return flow; + } + + flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { + const previousShaderStage = this.shaderStage; + + this.setShaderStage(shaderStage); + + const flowData = this.flowChildNode(node, output); + + if (propertyName !== null) { + flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; + } + + this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; + + this.setShaderStage(previousShaderStage); + + return flowData; + } + + getAttributesArray() { + return this.attributes.concat(this.bufferAttributes); + } + + getAttributes(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVaryings(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVar(type, name) { + return `${this.getType(type)} ${name}`; + } + + getVars(shaderStage) { + let snippet = ''; + + const vars = this.vars[shaderStage]; + + if (vars !== undefined) { + for (const variable of vars) { + snippet += `${this.getVar(variable.type, variable.name)}; `; + } + } + + return snippet; + } + + getUniforms(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getCodes(shaderStage) { + const codes = this.codes[shaderStage]; + + let code = ''; + + if (codes !== undefined) { + for (const nodeCode of codes) { + code += nodeCode.code + '\n'; + } + } + + return code; + } + + getHash() { + return this.vertexShader + this.fragmentShader + this.computeShader; + } + + setShaderStage(shaderStage) { + this.shaderStage = shaderStage; + } + + getShaderStage() { + return this.shaderStage; + } + + setBuildStage(buildStage) { + this.buildStage = buildStage; + } + + getBuildStage() { + return this.buildStage; + } + + buildCode() { + console.warn('Abstract function.'); + } + + build() { + const { object, material } = this; + + if (material !== null) { + NodeMaterial.fromMaterial(material).build(this); + } else { + this.addFlow('compute', object); + } + + // setup() -> stage 1: create possible new nodes and returns an output reference node + // analyze() -> stage 2: analyze nodes to possible optimization and validation + // generate() -> stage 3: generate shader + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + if (this.context.vertex && this.context.vertex.isNode) { + this.flowNodeFromShaderStage('vertex', this.context.vertex); + } + + for (const shaderStage of shaderStages) { + this.setShaderStage(shaderStage); + + const flowNodes = this.flowNodes[shaderStage]; + + for (const node of flowNodes) { + if (buildStage === 'generate') { + this.flowNode(node); + } else { + node.build(this); + } + } + } + } + + this.setBuildStage(null); + this.setShaderStage(null); + + // stage 4: build code for a specific output + + this.buildCode(); + this.buildUpdateNodes(); + + return this; + } + + getNodeUniform(uniformNode, type) { + if (type === 'float') return new FloatNodeUniform(uniformNode); + if (type === 'vec2') return new Vector2NodeUniform(uniformNode); + if (type === 'vec3') return new Vector3NodeUniform(uniformNode); + if (type === 'vec4') return new Vector4NodeUniform(uniformNode); + if (type === 'color') return new ColorNodeUniform(uniformNode); + if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); + if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); + + throw new Error(`Uniform "${type}" not declared.`); + } + + createNodeMaterial(type = 'NodeMaterial') { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return createNodeMaterialFromType(type); + } + + format(snippet, fromType, toType) { + fromType = this.getVectorType(fromType); + toType = this.getVectorType(toType); + + if (fromType === toType || toType === null || this.isReference(toType)) { + return snippet; + } + + const fromTypeLength = this.getTypeLength(fromType); + const toTypeLength = this.getTypeLength(toType); + + if (fromTypeLength > 4) { + // fromType is matrix-like + + // @TODO: ignore for now + + return snippet; + } + + if (toTypeLength > 4 || toTypeLength === 0) { + // toType is matrix-like or unknown + + // @TODO: ignore for now + + return snippet; + } + + if (fromTypeLength === toTypeLength) { + return `${this.getType(toType)}( ${snippet} )`; + } + + if (fromTypeLength > toTypeLength) { + return this.format( + `${snippet}.${'xyz'.slice(0, toTypeLength)}`, + this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), + toType, + ); + } + + if (toTypeLength === 4 && fromTypeLength > 1) { + // toType is vec4-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; + } + + if (fromTypeLength === 2) { + // fromType is vec2-like and toType is vec3-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; + } + + if (fromTypeLength === 1 && toTypeLength > 1 && fromType[0] !== toType[0]) { + // fromType is float-like + + // convert a number value to vector type, e.g: + // vec3( 1u ) -> vec3( float( 1u ) ) + + snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; + } + + return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like + } + + getSignature() { + return `// Three.js r${REVISION} - NodeMaterial System\n`; + } +} + +export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts new file mode 100644 index 000000000..b8e8d37b6 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeFrame.ts @@ -0,0 +1,101 @@ +import { NodeUpdateType } from './constants.js'; + +class NodeFrame { + constructor() { + this.time = 0; + this.deltaTime = 0; + + this.frameId = 0; + this.renderId = 0; + + this.startTime = null; + + this.updateMap = new WeakMap(); + this.updateBeforeMap = new WeakMap(); + + this.renderer = null; + this.material = null; + this.camera = null; + this.object = null; + this.scene = null; + } + + _getMaps(referenceMap, nodeRef) { + let maps = referenceMap.get(nodeRef); + + if (maps === undefined) { + maps = { + renderMap: new WeakMap(), + frameMap: new WeakMap(), + }; + + referenceMap.set(nodeRef, maps); + } + + return maps; + } + + updateBeforeNode(node) { + const updateType = node.getUpdateBeforeType(); + const reference = node.updateReference(this); + + if (updateType === NodeUpdateType.FRAME) { + const { frameMap } = this._getMaps(this.updateBeforeMap, reference); + + if (frameMap.get(reference) !== this.frameId) { + if (node.updateBefore(this) !== false) { + frameMap.set(reference, this.frameId); + } + } + } else if (updateType === NodeUpdateType.RENDER) { + const { renderMap } = this._getMaps(this.updateBeforeMap, reference); + + if (renderMap.get(reference) !== this.renderId) { + if (node.updateBefore(this) !== false) { + renderMap.set(reference, this.renderId); + } + } + } else if (updateType === NodeUpdateType.OBJECT) { + node.updateBefore(this); + } + } + + updateNode(node) { + const updateType = node.getUpdateType(); + const reference = node.updateReference(this); + + if (updateType === NodeUpdateType.FRAME) { + const { frameMap } = this._getMaps(this.updateMap, reference); + + if (frameMap.get(reference) !== this.frameId) { + if (node.update(this) !== false) { + frameMap.set(reference, this.frameId); + } + } + } else if (updateType === NodeUpdateType.RENDER) { + const { renderMap } = this._getMaps(this.updateMap, reference); + + if (renderMap.get(reference) !== this.renderId) { + if (node.update(this) !== false) { + renderMap.set(reference, this.renderId); + } + } + } else if (updateType === NodeUpdateType.OBJECT) { + node.update(this); + } + } + + update() { + this.frameId++; + + if (this.lastTime === undefined) this.lastTime = performance.now(); + + this.deltaTime = (performance.now() - this.lastTime) / 1000; + + this.lastTime = performance.now(); + + this.time += this.deltaTime; + } +} + +export default NodeFrame; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts new file mode 100644 index 000000000..3b01a9a6d --- /dev/null +++ b/examples-jsm/examples/nodes/core/constants.ts @@ -0,0 +1,28 @@ +export const NodeShaderStage = { + VERTEX: 'vertex', + FRAGMENT: 'fragment', +}; + +export const NodeUpdateType = { + NONE: 'none', + FRAME: 'frame', + RENDER: 'render', + OBJECT: 'object', +}; + +export const NodeType = { + BOOLEAN: 'bool', + INTEGER: 'int', + FLOAT: 'float', + VECTOR2: 'vec2', + VECTOR3: 'vec3', + VECTOR4: 'vec4', + MATRIX2: 'mat2', + MATRIX3: 'mat3', + MATRIX4: 'mat4', +}; + +export const defaultShaderStages = ['fragment', 'vertex']; +export const defaultBuildStages = ['setup', 'analyze', 'generate']; +export const shaderStages = [...defaultShaderStages, 'compute']; +export const vectorComponents = ['x', 'y', 'z', 'w']; diff --git a/examples-jsm/index.js b/examples-jsm/index.js index 71403c9d5..5c6497a74 100644 --- a/examples-jsm/index.js +++ b/examples-jsm/index.js @@ -3,7 +3,7 @@ import * as path from 'node:path'; import prettier from 'prettier'; -const files = ['nodes/core/Node', 'nodes/core/NodeBuilder', 'nodes/core/constants']; +const files = ['nodes/core/Node', 'nodes/core/NodeBuilder', 'nodes/core/NodeFrame', 'nodes/core/constants']; const inDir = '../three.js/examples/jsm'; const outDir = './examples'; From a6db3cfa2735066b4d8e1ca10d706f2a3ed61a29 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:40:08 -0400 Subject: [PATCH 10/18] Update patch and delete examples --- examples-jsm/changes.patch | 73 +- examples-jsm/examples/nodes/core/Node.ts | 411 ------- .../examples/nodes/core/NodeBuilder.ts | 1011 ----------------- examples-jsm/examples/nodes/core/NodeFrame.ts | 101 -- examples-jsm/examples/nodes/core/constants.ts | 28 - 5 files changed, 63 insertions(+), 1561 deletions(-) delete mode 100644 examples-jsm/examples/nodes/core/Node.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeFrame.ts delete mode 100644 examples-jsm/examples/nodes/core/constants.ts diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index 9160813ba..8fc30a3b1 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -1,12 +1,13 @@ diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts -index 438c44dd..d5115493 100644 +index 438c44dd..dd88fd34 100644 --- a/examples-jsm/examples/nodes/core/Node.ts +++ b/examples-jsm/examples/nodes/core/Node.ts -@@ -2,12 +2,31 @@ import { EventDispatcher } from 'three'; +@@ -2,12 +2,32 @@ import { EventDispatcher } from 'three'; import { NodeUpdateType } from './constants.js'; import { getNodeChildren, getCacheKey } from './NodeUtils.js'; import { MathUtils } from 'three'; +import NodeBuilder from './NodeBuilder.js'; ++import NodeFrame from './NodeFrame.js'; const NodeClasses = new Map(); @@ -34,7 +35,7 @@ index 438c44dd..d5115493 100644 constructor(nodeType = null) { super(); -@@ -28,7 +47,7 @@ class Node extends EventDispatcher { +@@ -28,7 +48,7 @@ class Node extends EventDispatcher { Object.defineProperty(this, 'id', { value: _nodeId++ }); } @@ -43,7 +44,7 @@ index 438c44dd..d5115493 100644 if (value === true) { this.version++; } -@@ -73,7 +92,7 @@ class Node extends EventDispatcher { +@@ -73,7 +93,7 @@ class Node extends EventDispatcher { return this; } @@ -52,7 +53,7 @@ index 438c44dd..d5115493 100644 return false; } -@@ -106,7 +125,7 @@ class Node extends EventDispatcher { +@@ -106,7 +126,7 @@ class Node extends EventDispatcher { return this._cacheKey; } @@ -61,7 +62,7 @@ index 438c44dd..d5115493 100644 return this.uuid; } -@@ -118,14 +137,14 @@ class Node extends EventDispatcher { +@@ -118,14 +138,14 @@ class Node extends EventDispatcher { return this.updateBeforeType; } @@ -78,7 +79,7 @@ index 438c44dd..d5115493 100644 const nodeProperties = builder.getNodeProperties(this); if (nodeProperties.outputNode) { -@@ -135,14 +154,14 @@ class Node extends EventDispatcher { +@@ -135,14 +155,14 @@ class Node extends EventDispatcher { return this.nodeType; } @@ -95,7 +96,7 @@ index 438c44dd..d5115493 100644 const nodeProperties = builder.getNodeProperties(this); for (const childNode of this.getChildren()) { -@@ -153,7 +172,7 @@ class Node extends EventDispatcher { +@@ -153,7 +173,7 @@ class Node extends EventDispatcher { return null; } @@ -104,7 +105,7 @@ index 438c44dd..d5115493 100644 // @deprecated, r157 console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); -@@ -161,14 +180,14 @@ class Node extends EventDispatcher { +@@ -161,14 +181,14 @@ class Node extends EventDispatcher { return this.setup(builder); } @@ -121,7 +122,7 @@ index 438c44dd..d5115493 100644 const usageCount = this.increaseUsage(builder); if (usageCount === 1) { -@@ -184,7 +203,7 @@ class Node extends EventDispatcher { +@@ -184,7 +204,7 @@ class Node extends EventDispatcher { } } @@ -130,6 +131,58 @@ index 438c44dd..d5115493 100644 const { outputNode } = builder.getNodeProperties(this); if (outputNode && outputNode.isNode === true) { +@@ -192,15 +212,15 @@ class Node extends EventDispatcher { + } + } + +- updateBefore(/*frame*/) { ++ updateBefore(frame: NodeFrame) { + console.warn('Abstract function.'); + } + +- update(/*frame*/) { ++ update(frame: NodeFrame) { + console.warn('Abstract function.'); + } + +- build(builder, output = null) { ++ build(builder: NodeBuilder, output: string | null = null) { + const refNode = this.getShared(builder); + + if (this !== refNode) { +diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts +index ebdc13ff..319b26eb 100644 +--- a/examples-jsm/examples/nodes/core/NodeBuilder.ts ++++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts +@@ -30,6 +30,10 @@ import { + IntType, + UnsignedIntType, + Float16BufferAttribute, ++ Object3D, ++ Material, ++ Mesh, ++ BufferGeometry, + } from 'three'; + + import { stack } from './StackNode.js'; +@@ -67,10 +71,14 @@ const toFloat = value => { + }; + + class NodeBuilder { +- constructor(object, renderer, parser, scene = null, material = null) { ++ object: Object3D; ++ material: Material | Material[]; ++ geometry: BufferGeometry; ++ ++ constructor(object: Object3D, renderer, parser, scene = null, material: Material | null = null) { + this.object = object; +- this.material = material || (object && object.material) || null; +- this.geometry = (object && object.geometry) || null; ++ this.material = material || (object && (object as Mesh).material) || null; ++ this.geometry = (object && (object as Mesh).geometry) || null; + this.renderer = renderer; + this.parser = parser; + this.scene = scene; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts index 3b01a9a6..3391a4be 100644 --- a/examples-jsm/examples/nodes/core/constants.ts diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts deleted file mode 100644 index 438c44dd1..000000000 --- a/examples-jsm/examples/nodes/core/Node.ts +++ /dev/null @@ -1,411 +0,0 @@ -import { EventDispatcher } from 'three'; -import { NodeUpdateType } from './constants.js'; -import { getNodeChildren, getCacheKey } from './NodeUtils.js'; -import { MathUtils } from 'three'; - -const NodeClasses = new Map(); - -let _nodeId = 0; - -class Node extends EventDispatcher { - constructor(nodeType = null) { - super(); - - this.nodeType = nodeType; - - this.updateType = NodeUpdateType.NONE; - this.updateBeforeType = NodeUpdateType.NONE; - - this.uuid = MathUtils.generateUUID(); - - this.version = 0; - - this._cacheKey = null; - this._cacheKeyVersion = 0; - - this.isNode = true; - - Object.defineProperty(this, 'id', { value: _nodeId++ }); - } - - set needsUpdate(value) { - if (value === true) { - this.version++; - } - } - - get type() { - return this.constructor.type; - } - - onUpdate(callback, updateType) { - this.updateType = updateType; - this.update = callback.bind(this.getSelf()); - - return this; - } - - onFrameUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.FRAME); - } - - onRenderUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.RENDER); - } - - onObjectUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.OBJECT); - } - - onReference(callback) { - this.updateReference = callback.bind(this.getSelf()); - - return this; - } - - getSelf() { - // Returns non-node object. - - return this.self || this; - } - - updateReference(/*state*/) { - return this; - } - - isGlobal(/*builder*/) { - return false; - } - - *getChildren() { - for (const { childNode } of getNodeChildren(this)) { - yield childNode; - } - } - - dispose() { - this.dispatchEvent({ type: 'dispose' }); - } - - traverse(callback) { - callback(this); - - for (const childNode of this.getChildren()) { - childNode.traverse(callback); - } - } - - getCacheKey(force = false) { - force = force || this.version !== this._cacheKeyVersion; - - if (force === true || this._cacheKey === null) { - this._cacheKey = getCacheKey(this, force); - this._cacheKeyVersion = this.version; - } - - return this._cacheKey; - } - - getHash(/*builder*/) { - return this.uuid; - } - - getUpdateType() { - return this.updateType; - } - - getUpdateBeforeType() { - return this.updateBeforeType; - } - - getElementType(builder) { - const type = this.getNodeType(builder); - const elementType = builder.getElementType(type); - - return elementType; - } - - getNodeType(builder) { - const nodeProperties = builder.getNodeProperties(this); - - if (nodeProperties.outputNode) { - return nodeProperties.outputNode.getNodeType(builder); - } - - return this.nodeType; - } - - getShared(builder) { - const hash = this.getHash(builder); - const nodeFromHash = builder.getNodeFromHash(hash); - - return nodeFromHash || this; - } - - setup(builder) { - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of this.getChildren()) { - nodeProperties['_node' + childNode.id] = childNode; - } - - // return a outputNode if exists - return null; - } - - construct(builder) { - // @deprecated, r157 - - console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); - - return this.setup(builder); - } - - increaseUsage(builder) { - const nodeData = builder.getDataFromNode(this); - nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; - - return nodeData.usageCount; - } - - analyze(builder) { - const usageCount = this.increaseUsage(builder); - - if (usageCount === 1) { - // node flow children - - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of Object.values(nodeProperties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } - - generate(builder, output) { - const { outputNode } = builder.getNodeProperties(this); - - if (outputNode && outputNode.isNode === true) { - return outputNode.build(builder, output); - } - } - - updateBefore(/*frame*/) { - console.warn('Abstract function.'); - } - - update(/*frame*/) { - console.warn('Abstract function.'); - } - - build(builder, output = null) { - const refNode = this.getShared(builder); - - if (this !== refNode) { - return refNode.build(builder, output); - } - - builder.addNode(this); - builder.addChain(this); - - /* Build stages expected results: - - "setup" -> Node - - "analyze" -> null - - "generate" -> String - */ - let result = null; - - const buildStage = builder.getBuildStage(); - - if (buildStage === 'setup') { - this.updateReference(builder); - - const properties = builder.getNodeProperties(this); - - if (properties.initialized !== true || builder.context.tempRead === false) { - const stackNodesBeforeSetup = builder.stack.nodes.length; - - properties.initialized = true; - properties.outputNode = this.setup(builder); - - if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { - properties.outputNode = builder.stack; - } - - for (const childNode of Object.values(properties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } else if (buildStage === 'analyze') { - this.analyze(builder); - } else if (buildStage === 'generate') { - const isGenerateOnce = this.generate.length === 1; - - if (isGenerateOnce) { - const type = this.getNodeType(builder); - const nodeData = builder.getDataFromNode(this); - - result = nodeData.snippet; - - if (result === undefined /*|| builder.context.tempRead === false*/) { - result = this.generate(builder) || ''; - - nodeData.snippet = result; - } - - result = builder.format(result, type, output); - } else { - result = this.generate(builder, output) || ''; - } - } - - builder.removeChain(this); - - return result; - } - - getSerializeChildren() { - return getNodeChildren(this); - } - - serialize(json) { - const nodeChildren = this.getSerializeChildren(); - - const inputNodes = {}; - - for (const { property, index, childNode } of nodeChildren) { - if (index !== undefined) { - if (inputNodes[property] === undefined) { - inputNodes[property] = Number.isInteger(index) ? [] : {}; - } - - inputNodes[property][index] = childNode.toJSON(json.meta).uuid; - } else { - inputNodes[property] = childNode.toJSON(json.meta).uuid; - } - } - - if (Object.keys(inputNodes).length > 0) { - json.inputNodes = inputNodes; - } - } - - deserialize(json) { - if (json.inputNodes !== undefined) { - const nodes = json.meta.nodes; - - for (const property in json.inputNodes) { - if (Array.isArray(json.inputNodes[property])) { - const inputArray = []; - - for (const uuid of json.inputNodes[property]) { - inputArray.push(nodes[uuid]); - } - - this[property] = inputArray; - } else if (typeof json.inputNodes[property] === 'object') { - const inputObject = {}; - - for (const subProperty in json.inputNodes[property]) { - const uuid = json.inputNodes[property][subProperty]; - - inputObject[subProperty] = nodes[uuid]; - } - - this[property] = inputObject; - } else { - const uuid = json.inputNodes[property]; - - this[property] = nodes[uuid]; - } - } - } - } - - toJSON(meta) { - const { uuid, type } = this; - const isRoot = meta === undefined || typeof meta === 'string'; - - if (isRoot) { - meta = { - textures: {}, - images: {}, - nodes: {}, - }; - } - - // serialize - - let data = meta.nodes[uuid]; - - if (data === undefined) { - data = { - uuid, - type, - meta, - metadata: { - version: 4.6, - type: 'Node', - generator: 'Node.toJSON', - }, - }; - - if (isRoot !== true) meta.nodes[data.uuid] = data; - - this.serialize(data); - - delete data.meta; - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache(cache) { - const values = []; - - for (const key in cache) { - const data = cache[key]; - delete data.metadata; - values.push(data); - } - - return values; - } - - if (isRoot) { - const textures = extractFromCache(meta.textures); - const images = extractFromCache(meta.images); - const nodes = extractFromCache(meta.nodes); - - if (textures.length > 0) data.textures = textures; - if (images.length > 0) data.images = images; - if (nodes.length > 0) data.nodes = nodes; - } - - return data; - } -} - -export default Node; - -export function addNodeClass(type, nodeClass) { - if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); - if (NodeClasses.has(type)) { - console.warn(`Redefinition of node class ${type}`); - return; - } - - NodeClasses.set(type, nodeClass); - nodeClass.type = type; -} - -export function createNodeFromType(type) { - const Class = NodeClasses.get(type); - - if (Class !== undefined) { - return new Class(); - } -} diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts deleted file mode 100644 index ebdc13ff1..000000000 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ /dev/null @@ -1,1011 +0,0 @@ -import NodeUniform from './NodeUniform.js'; -import NodeAttribute from './NodeAttribute.js'; -import NodeVarying from './NodeVarying.js'; -import NodeVar from './NodeVar.js'; -import NodeCode from './NodeCode.js'; -import NodeKeywords from './NodeKeywords.js'; -import NodeCache from './NodeCache.js'; -import ParameterNode from './ParameterNode.js'; -import FunctionNode from '../code/FunctionNode.js'; -import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; -import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; - -import { - FloatNodeUniform, - Vector2NodeUniform, - Vector3NodeUniform, - Vector4NodeUniform, - ColorNodeUniform, - Matrix3NodeUniform, - Matrix4NodeUniform, -} from '../../renderers/common/nodes/NodeUniform.js'; - -import { - REVISION, - RenderTarget, - Color, - Vector2, - Vector3, - Vector4, - IntType, - UnsignedIntType, - Float16BufferAttribute, -} from 'three'; - -import { stack } from './StackNode.js'; -import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; - -import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; -import ChainMap from '../../renderers/common/ChainMap.js'; - -import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; - -const uniformsGroupCache = new ChainMap(); - -const typeFromLength = new Map([ - [2, 'vec2'], - [3, 'vec3'], - [4, 'vec4'], - [9, 'mat3'], - [16, 'mat4'], -]); - -const typeFromArray = new Map([ - [Int8Array, 'int'], - [Int16Array, 'int'], - [Int32Array, 'int'], - [Uint8Array, 'uint'], - [Uint16Array, 'uint'], - [Uint32Array, 'uint'], - [Float32Array, 'float'], -]); - -const toFloat = value => { - value = Number(value); - - return value + (value % 1 ? '' : '.0'); -}; - -class NodeBuilder { - constructor(object, renderer, parser, scene = null, material = null) { - this.object = object; - this.material = material || (object && object.material) || null; - this.geometry = (object && object.geometry) || null; - this.renderer = renderer; - this.parser = parser; - this.scene = scene; - - this.nodes = []; - this.updateNodes = []; - this.updateBeforeNodes = []; - this.hashNodes = {}; - - this.lightsNode = null; - this.environmentNode = null; - this.fogNode = null; - - this.clippingContext = null; - - this.vertexShader = null; - this.fragmentShader = null; - this.computeShader = null; - - this.flowNodes = { vertex: [], fragment: [], compute: [] }; - this.flowCode = { vertex: '', fragment: '', compute: [] }; - this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; - this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; - this.bindings = { vertex: [], fragment: [], compute: [] }; - this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; - this.bindingsArray = null; - this.attributes = []; - this.bufferAttributes = []; - this.varyings = []; - this.codes = {}; - this.vars = {}; - this.flow = { code: '' }; - this.chaining = []; - this.stack = stack(); - this.stacks = []; - this.tab = '\t'; - - this.currentFunctionNode = null; - - this.context = { - keywords: new NodeKeywords(), - material: this.material, - }; - - this.cache = new NodeCache(); - this.globalCache = this.cache; - - this.flowsData = new WeakMap(); - - this.shaderStage = null; - this.buildStage = null; - } - - createRenderTarget(width, height, options) { - return new RenderTarget(width, height, options); - } - - createCubeRenderTarget(size, options) { - return new CubeRenderTarget(size, options); - } - - createPMREMGenerator() { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return new PMREMGenerator(this.renderer); - } - - includes(node) { - return this.nodes.includes(node); - } - - _getSharedBindings(bindings) { - const shared = []; - - for (const binding of bindings) { - if (binding.shared === true) { - // nodes is the chainmap key - const nodes = binding.getNodes(); - - let sharedBinding = uniformsGroupCache.get(nodes); - - if (sharedBinding === undefined) { - uniformsGroupCache.set(nodes, binding); - - sharedBinding = binding; - } - - shared.push(sharedBinding); - } else { - shared.push(binding); - } - } - - return shared; - } - - getBindings() { - let bindingsArray = this.bindingsArray; - - if (bindingsArray === null) { - const bindings = this.bindings; - - this.bindingsArray = bindingsArray = this._getSharedBindings( - this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, - ); - } - - return bindingsArray; - } - - setHashNode(node, hash) { - this.hashNodes[hash] = node; - } - - addNode(node) { - if (this.nodes.includes(node) === false) { - this.nodes.push(node); - - this.setHashNode(node, node.getHash(this)); - } - } - - buildUpdateNodes() { - for (const node of this.nodes) { - const updateType = node.getUpdateType(); - const updateBeforeType = node.getUpdateBeforeType(); - - if (updateType !== NodeUpdateType.NONE) { - this.updateNodes.push(node.getSelf()); - } - - if (updateBeforeType !== NodeUpdateType.NONE) { - this.updateBeforeNodes.push(node); - } - } - } - - get currentNode() { - return this.chaining[this.chaining.length - 1]; - } - - addChain(node) { - /* - if ( this.chaining.indexOf( node ) !== - 1 ) { - - console.warn( 'Recursive node: ', node ); - - } - */ - - this.chaining.push(node); - } - - removeChain(node) { - const lastChain = this.chaining.pop(); - - if (lastChain !== node) { - throw new Error('NodeBuilder: Invalid node chaining!'); - } - } - - getMethod(method) { - return method; - } - - getNodeFromHash(hash) { - return this.hashNodes[hash]; - } - - addFlow(shaderStage, node) { - this.flowNodes[shaderStage].push(node); - - return node; - } - - setContext(context) { - this.context = context; - } - - getContext() { - return this.context; - } - - setCache(cache) { - this.cache = cache; - } - - getCache() { - return this.cache; - } - - isAvailable(/*name*/) { - return false; - } - - getVertexIndex() { - console.warn('Abstract function.'); - } - - getInstanceIndex() { - console.warn('Abstract function.'); - } - - getFrontFacing() { - console.warn('Abstract function.'); - } - - getFragCoord() { - console.warn('Abstract function.'); - } - - isFlipY() { - return false; - } - - generateTexture(/* texture, textureProperty, uvSnippet */) { - console.warn('Abstract function.'); - } - - generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { - console.warn('Abstract function.'); - } - - generateConst(type, value = null) { - if (value === null) { - if (type === 'float' || type === 'int' || type === 'uint') value = 0; - else if (type === 'bool') value = false; - else if (type === 'color') value = new Color(); - else if (type === 'vec2') value = new Vector2(); - else if (type === 'vec3') value = new Vector3(); - else if (type === 'vec4') value = new Vector4(); - } - - if (type === 'float') return toFloat(value); - if (type === 'int') return `${Math.round(value)}`; - if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; - if (type === 'bool') return value ? 'true' : 'false'; - if (type === 'color') - return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; - - const typeLength = this.getTypeLength(type); - - const componentType = this.getComponentType(type); - - const generateConst = value => this.generateConst(componentType, value); - - if (typeLength === 2) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; - } else if (typeLength === 3) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; - } else if (typeLength === 4) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; - } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { - return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; - } else if (typeLength > 4) { - return `${this.getType(type)}()`; - } - - throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); - } - - getType(type) { - if (type === 'color') return 'vec3'; - - return type; - } - - generateMethod(method) { - return method; - } - - hasGeometryAttribute(name) { - return this.geometry && this.geometry.getAttribute(name) !== undefined; - } - - getAttribute(name, type) { - const attributes = this.attributes; - - // find attribute - - for (const attribute of attributes) { - if (attribute.name === name) { - return attribute; - } - } - - // create a new if no exist - - const attribute = new NodeAttribute(name, type); - - attributes.push(attribute); - - return attribute; - } - - getPropertyName(node /*, shaderStage*/) { - return node.name; - } - - isVector(type) { - return /vec\d/.test(type); - } - - isMatrix(type) { - return /mat\d/.test(type); - } - - isReference(type) { - return ( - type === 'void' || - type === 'property' || - type === 'sampler' || - type === 'texture' || - type === 'cubeTexture' || - type === 'storageTexture' || - type === 'texture3D' - ); - } - - needsColorSpaceToLinear(/*texture*/) { - return false; - } - - getComponentTypeFromTexture(texture) { - const type = texture.type; - - if (texture.isDataTexture) { - if (type === IntType) return 'int'; - if (type === UnsignedIntType) return 'uint'; - } - - return 'float'; - } - - getElementType(type) { - if (type === 'mat2') return 'vec2'; - if (type === 'mat3') return 'vec3'; - if (type === 'mat4') return 'vec4'; - - return this.getComponentType(type); - } - - getComponentType(type) { - type = this.getVectorType(type); - - if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; - - const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); - - if (componentType === null) return null; - - if (componentType[1] === 'b') return 'bool'; - if (componentType[1] === 'i') return 'int'; - if (componentType[1] === 'u') return 'uint'; - - return 'float'; - } - - getVectorType(type) { - if (type === 'color') return 'vec3'; - if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') - return 'vec4'; - - return type; - } - - getTypeFromLength(length, componentType = 'float') { - if (length === 1) return componentType; - - const baseType = typeFromLength.get(length); - const prefix = componentType === 'float' ? '' : componentType[0]; - - return prefix + baseType; - } - - getTypeFromArray(array) { - return typeFromArray.get(array.constructor); - } - - getTypeFromAttribute(attribute) { - let dataAttribute = attribute; - - if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; - - const array = dataAttribute.array; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; - - let arrayType; - - if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { - arrayType = this.getTypeFromArray(array); - } - - return this.getTypeFromLength(itemSize, arrayType); - } - - getTypeLength(type) { - const vecType = this.getVectorType(type); - const vecNum = /vec([2-4])/.exec(vecType); - - if (vecNum !== null) return Number(vecNum[1]); - if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; - if (/mat2/.test(type) === true) return 4; - if (/mat3/.test(type) === true) return 9; - if (/mat4/.test(type) === true) return 16; - - return 0; - } - - getVectorFromMatrix(type) { - return type.replace('mat', 'vec'); - } - - changeComponentType(type, newComponentType) { - return this.getTypeFromLength(this.getTypeLength(type), newComponentType); - } - - getIntegerType(type) { - const componentType = this.getComponentType(type); - - if (componentType === 'int' || componentType === 'uint') return type; - - return this.changeComponentType(type, 'int'); - } - - addStack() { - this.stack = stack(this.stack); - - this.stacks.push(getCurrentStack() || this.stack); - setCurrentStack(this.stack); - - return this.stack; - } - - removeStack() { - const lastStack = this.stack; - this.stack = lastStack.parent; - - setCurrentStack(this.stacks.pop()); - - return lastStack; - } - - getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { - cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; - - let nodeData = cache.getNodeData(node); - - if (nodeData === undefined) { - nodeData = {}; - - cache.setNodeData(node, nodeData); - } - - if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; - - return nodeData[shaderStage]; - } - - getNodeProperties(node, shaderStage = 'any') { - const nodeData = this.getDataFromNode(node, shaderStage); - - return nodeData.properties || (nodeData.properties = { outputNode: null }); - } - - getBufferAttributeFromNode(node, type) { - const nodeData = this.getDataFromNode(node); - - let bufferAttribute = nodeData.bufferAttribute; - - if (bufferAttribute === undefined) { - const index = this.uniforms.index++; - - bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); - - this.bufferAttributes.push(bufferAttribute); - - nodeData.bufferAttribute = bufferAttribute; - } - - return bufferAttribute; - } - - getStructTypeFromNode(node, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - if (nodeData.structType === undefined) { - const index = this.structs.index++; - - node.name = `StructType${index}`; - this.structs[shaderStage].push(node); - - nodeData.structType = node; - } - - return node; - } - - getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { - const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); - - let nodeUniform = nodeData.uniform; - - if (nodeUniform === undefined) { - const index = this.uniforms.index++; - - nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); - - this.uniforms[shaderStage].push(nodeUniform); - - nodeData.uniform = nodeUniform; - } - - return nodeUniform; - } - - getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - let nodeVar = nodeData.variable; - - if (nodeVar === undefined) { - const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); - - if (name === null) name = 'nodeVar' + vars.length; - - nodeVar = new NodeVar(name, type); - - vars.push(nodeVar); - - nodeData.variable = nodeVar; - } - - return nodeVar; - } - - getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { - const nodeData = this.getDataFromNode(node, 'any'); - - let nodeVarying = nodeData.varying; - - if (nodeVarying === undefined) { - const varyings = this.varyings; - const index = varyings.length; - - if (name === null) name = 'nodeVarying' + index; - - nodeVarying = new NodeVarying(name, type); - - varyings.push(nodeVarying); - - nodeData.varying = nodeVarying; - } - - return nodeVarying; - } - - getCodeFromNode(node, type, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node); - - let nodeCode = nodeData.code; - - if (nodeCode === undefined) { - const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); - const index = codes.length; - - nodeCode = new NodeCode('nodeCode' + index, type); - - codes.push(nodeCode); - - nodeData.code = nodeCode; - } - - return nodeCode; - } - - addLineFlowCode(code) { - if (code === '') return this; - - code = this.tab + code; - - if (!/;\s*$/.test(code)) { - code = code + ';\n'; - } - - this.flow.code += code; - - return this; - } - - addFlowCode(code) { - this.flow.code += code; - - return this; - } - - addFlowTab() { - this.tab += '\t'; - - return this; - } - - removeFlowTab() { - this.tab = this.tab.slice(0, -1); - - return this; - } - - getFlowData(node /*, shaderStage*/) { - return this.flowsData.get(node); - } - - flowNode(node) { - const output = node.getNodeType(this); - - const flowData = this.flowChildNode(node, output); - - this.flowsData.set(node, flowData); - - return flowData; - } - - buildFunctionNode(shaderNode) { - const fn = new FunctionNode(); - - const previous = this.currentFunctionNode; - - this.currentFunctionNode = fn; - - fn.code = this.buildFunctionCode(shaderNode); - - this.currentFunctionNode = previous; - - return fn; - } - - flowShaderNode(shaderNode) { - const layout = shaderNode.layout; - - let inputs; - - if (shaderNode.isArrayInput) { - inputs = []; - - for (const input of layout.inputs) { - inputs.push(new ParameterNode(input.type, input.name)); - } - } else { - inputs = {}; - - for (const input of layout.inputs) { - inputs[input.name] = new ParameterNode(input.type, input.name); - } - } - - // - - shaderNode.layout = null; - - const callNode = shaderNode.call(inputs); - const flowData = this.flowStagesNode(callNode, layout.type); - - shaderNode.layout = layout; - - return flowData; - } - - flowStagesNode(node, output = null) { - const previousFlow = this.flow; - const previousVars = this.vars; - const previousBuildStage = this.buildStage; - - const flow = { - code: '', - }; - - this.flow = flow; - this.vars = {}; - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - flow.result = node.build(this, output); - } - - flow.vars = this.getVars(this.shaderStage); - - this.flow = previousFlow; - this.vars = previousVars; - this.setBuildStage(previousBuildStage); - - return flow; - } - - getFunctionOperator() { - return null; - } - - flowChildNode(node, output = null) { - const previousFlow = this.flow; - - const flow = { - code: '', - }; - - this.flow = flow; - - flow.result = node.build(this, output); - - this.flow = previousFlow; - - return flow; - } - - flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { - const previousShaderStage = this.shaderStage; - - this.setShaderStage(shaderStage); - - const flowData = this.flowChildNode(node, output); - - if (propertyName !== null) { - flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; - } - - this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; - - this.setShaderStage(previousShaderStage); - - return flowData; - } - - getAttributesArray() { - return this.attributes.concat(this.bufferAttributes); - } - - getAttributes(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVaryings(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVar(type, name) { - return `${this.getType(type)} ${name}`; - } - - getVars(shaderStage) { - let snippet = ''; - - const vars = this.vars[shaderStage]; - - if (vars !== undefined) { - for (const variable of vars) { - snippet += `${this.getVar(variable.type, variable.name)}; `; - } - } - - return snippet; - } - - getUniforms(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getCodes(shaderStage) { - const codes = this.codes[shaderStage]; - - let code = ''; - - if (codes !== undefined) { - for (const nodeCode of codes) { - code += nodeCode.code + '\n'; - } - } - - return code; - } - - getHash() { - return this.vertexShader + this.fragmentShader + this.computeShader; - } - - setShaderStage(shaderStage) { - this.shaderStage = shaderStage; - } - - getShaderStage() { - return this.shaderStage; - } - - setBuildStage(buildStage) { - this.buildStage = buildStage; - } - - getBuildStage() { - return this.buildStage; - } - - buildCode() { - console.warn('Abstract function.'); - } - - build() { - const { object, material } = this; - - if (material !== null) { - NodeMaterial.fromMaterial(material).build(this); - } else { - this.addFlow('compute', object); - } - - // setup() -> stage 1: create possible new nodes and returns an output reference node - // analyze() -> stage 2: analyze nodes to possible optimization and validation - // generate() -> stage 3: generate shader - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - if (this.context.vertex && this.context.vertex.isNode) { - this.flowNodeFromShaderStage('vertex', this.context.vertex); - } - - for (const shaderStage of shaderStages) { - this.setShaderStage(shaderStage); - - const flowNodes = this.flowNodes[shaderStage]; - - for (const node of flowNodes) { - if (buildStage === 'generate') { - this.flowNode(node); - } else { - node.build(this); - } - } - } - } - - this.setBuildStage(null); - this.setShaderStage(null); - - // stage 4: build code for a specific output - - this.buildCode(); - this.buildUpdateNodes(); - - return this; - } - - getNodeUniform(uniformNode, type) { - if (type === 'float') return new FloatNodeUniform(uniformNode); - if (type === 'vec2') return new Vector2NodeUniform(uniformNode); - if (type === 'vec3') return new Vector3NodeUniform(uniformNode); - if (type === 'vec4') return new Vector4NodeUniform(uniformNode); - if (type === 'color') return new ColorNodeUniform(uniformNode); - if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); - if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); - - throw new Error(`Uniform "${type}" not declared.`); - } - - createNodeMaterial(type = 'NodeMaterial') { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return createNodeMaterialFromType(type); - } - - format(snippet, fromType, toType) { - fromType = this.getVectorType(fromType); - toType = this.getVectorType(toType); - - if (fromType === toType || toType === null || this.isReference(toType)) { - return snippet; - } - - const fromTypeLength = this.getTypeLength(fromType); - const toTypeLength = this.getTypeLength(toType); - - if (fromTypeLength > 4) { - // fromType is matrix-like - - // @TODO: ignore for now - - return snippet; - } - - if (toTypeLength > 4 || toTypeLength === 0) { - // toType is matrix-like or unknown - - // @TODO: ignore for now - - return snippet; - } - - if (fromTypeLength === toTypeLength) { - return `${this.getType(toType)}( ${snippet} )`; - } - - if (fromTypeLength > toTypeLength) { - return this.format( - `${snippet}.${'xyz'.slice(0, toTypeLength)}`, - this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), - toType, - ); - } - - if (toTypeLength === 4 && fromTypeLength > 1) { - // toType is vec4-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; - } - - if (fromTypeLength === 2) { - // fromType is vec2-like and toType is vec3-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; - } - - if (fromTypeLength === 1 && toTypeLength > 1 && fromType[0] !== toType[0]) { - // fromType is float-like - - // convert a number value to vector type, e.g: - // vec3( 1u ) -> vec3( float( 1u ) ) - - snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; - } - - return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like - } - - getSignature() { - return `// Three.js r${REVISION} - NodeMaterial System\n`; - } -} - -export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts deleted file mode 100644 index b8e8d37b6..000000000 --- a/examples-jsm/examples/nodes/core/NodeFrame.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { NodeUpdateType } from './constants.js'; - -class NodeFrame { - constructor() { - this.time = 0; - this.deltaTime = 0; - - this.frameId = 0; - this.renderId = 0; - - this.startTime = null; - - this.updateMap = new WeakMap(); - this.updateBeforeMap = new WeakMap(); - - this.renderer = null; - this.material = null; - this.camera = null; - this.object = null; - this.scene = null; - } - - _getMaps(referenceMap, nodeRef) { - let maps = referenceMap.get(nodeRef); - - if (maps === undefined) { - maps = { - renderMap: new WeakMap(), - frameMap: new WeakMap(), - }; - - referenceMap.set(nodeRef, maps); - } - - return maps; - } - - updateBeforeNode(node) { - const updateType = node.getUpdateBeforeType(); - const reference = node.updateReference(this); - - if (updateType === NodeUpdateType.FRAME) { - const { frameMap } = this._getMaps(this.updateBeforeMap, reference); - - if (frameMap.get(reference) !== this.frameId) { - if (node.updateBefore(this) !== false) { - frameMap.set(reference, this.frameId); - } - } - } else if (updateType === NodeUpdateType.RENDER) { - const { renderMap } = this._getMaps(this.updateBeforeMap, reference); - - if (renderMap.get(reference) !== this.renderId) { - if (node.updateBefore(this) !== false) { - renderMap.set(reference, this.renderId); - } - } - } else if (updateType === NodeUpdateType.OBJECT) { - node.updateBefore(this); - } - } - - updateNode(node) { - const updateType = node.getUpdateType(); - const reference = node.updateReference(this); - - if (updateType === NodeUpdateType.FRAME) { - const { frameMap } = this._getMaps(this.updateMap, reference); - - if (frameMap.get(reference) !== this.frameId) { - if (node.update(this) !== false) { - frameMap.set(reference, this.frameId); - } - } - } else if (updateType === NodeUpdateType.RENDER) { - const { renderMap } = this._getMaps(this.updateMap, reference); - - if (renderMap.get(reference) !== this.renderId) { - if (node.update(this) !== false) { - renderMap.set(reference, this.renderId); - } - } - } else if (updateType === NodeUpdateType.OBJECT) { - node.update(this); - } - } - - update() { - this.frameId++; - - if (this.lastTime === undefined) this.lastTime = performance.now(); - - this.deltaTime = (performance.now() - this.lastTime) / 1000; - - this.lastTime = performance.now(); - - this.time += this.deltaTime; - } -} - -export default NodeFrame; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts deleted file mode 100644 index 3b01a9a6d..000000000 --- a/examples-jsm/examples/nodes/core/constants.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const NodeShaderStage = { - VERTEX: 'vertex', - FRAGMENT: 'fragment', -}; - -export const NodeUpdateType = { - NONE: 'none', - FRAME: 'frame', - RENDER: 'render', - OBJECT: 'object', -}; - -export const NodeType = { - BOOLEAN: 'bool', - INTEGER: 'int', - FLOAT: 'float', - VECTOR2: 'vec2', - VECTOR3: 'vec3', - VECTOR4: 'vec4', - MATRIX2: 'mat2', - MATRIX3: 'mat3', - MATRIX4: 'mat4', -}; - -export const defaultShaderStages = ['fragment', 'vertex']; -export const defaultBuildStages = ['setup', 'analyze', 'generate']; -export const shaderStages = [...defaultShaderStages, 'compute']; -export const vectorComponents = ['x', 'y', 'z', 'w']; From 4445d8ca5b519a7b38352ec83bd025373965a040 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:41:55 -0400 Subject: [PATCH 11/18] Add examples --- examples-jsm/examples/nodes/core/Node.ts | 411 ++++++ .../examples/nodes/core/NodeBuilder.ts | 1011 ++++++++++++++ examples-jsm/examples/nodes/core/NodeFrame.ts | 101 ++ .../examples/nodes/core/NodeParser.ts | 7 + examples-jsm/examples/nodes/core/constants.ts | 28 + .../examples/renderers/common/Renderer.ts | 1225 +++++++++++++++++ examples-jsm/index.js | 9 +- 7 files changed, 2791 insertions(+), 1 deletion(-) create mode 100644 examples-jsm/examples/nodes/core/Node.ts create mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts create mode 100644 examples-jsm/examples/nodes/core/NodeFrame.ts create mode 100644 examples-jsm/examples/nodes/core/NodeParser.ts create mode 100644 examples-jsm/examples/nodes/core/constants.ts create mode 100644 examples-jsm/examples/renderers/common/Renderer.ts diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts new file mode 100644 index 000000000..438c44dd1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/Node.ts @@ -0,0 +1,411 @@ +import { EventDispatcher } from 'three'; +import { NodeUpdateType } from './constants.js'; +import { getNodeChildren, getCacheKey } from './NodeUtils.js'; +import { MathUtils } from 'three'; + +const NodeClasses = new Map(); + +let _nodeId = 0; + +class Node extends EventDispatcher { + constructor(nodeType = null) { + super(); + + this.nodeType = nodeType; + + this.updateType = NodeUpdateType.NONE; + this.updateBeforeType = NodeUpdateType.NONE; + + this.uuid = MathUtils.generateUUID(); + + this.version = 0; + + this._cacheKey = null; + this._cacheKeyVersion = 0; + + this.isNode = true; + + Object.defineProperty(this, 'id', { value: _nodeId++ }); + } + + set needsUpdate(value) { + if (value === true) { + this.version++; + } + } + + get type() { + return this.constructor.type; + } + + onUpdate(callback, updateType) { + this.updateType = updateType; + this.update = callback.bind(this.getSelf()); + + return this; + } + + onFrameUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.FRAME); + } + + onRenderUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.RENDER); + } + + onObjectUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.OBJECT); + } + + onReference(callback) { + this.updateReference = callback.bind(this.getSelf()); + + return this; + } + + getSelf() { + // Returns non-node object. + + return this.self || this; + } + + updateReference(/*state*/) { + return this; + } + + isGlobal(/*builder*/) { + return false; + } + + *getChildren() { + for (const { childNode } of getNodeChildren(this)) { + yield childNode; + } + } + + dispose() { + this.dispatchEvent({ type: 'dispose' }); + } + + traverse(callback) { + callback(this); + + for (const childNode of this.getChildren()) { + childNode.traverse(callback); + } + } + + getCacheKey(force = false) { + force = force || this.version !== this._cacheKeyVersion; + + if (force === true || this._cacheKey === null) { + this._cacheKey = getCacheKey(this, force); + this._cacheKeyVersion = this.version; + } + + return this._cacheKey; + } + + getHash(/*builder*/) { + return this.uuid; + } + + getUpdateType() { + return this.updateType; + } + + getUpdateBeforeType() { + return this.updateBeforeType; + } + + getElementType(builder) { + const type = this.getNodeType(builder); + const elementType = builder.getElementType(type); + + return elementType; + } + + getNodeType(builder) { + const nodeProperties = builder.getNodeProperties(this); + + if (nodeProperties.outputNode) { + return nodeProperties.outputNode.getNodeType(builder); + } + + return this.nodeType; + } + + getShared(builder) { + const hash = this.getHash(builder); + const nodeFromHash = builder.getNodeFromHash(hash); + + return nodeFromHash || this; + } + + setup(builder) { + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of this.getChildren()) { + nodeProperties['_node' + childNode.id] = childNode; + } + + // return a outputNode if exists + return null; + } + + construct(builder) { + // @deprecated, r157 + + console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); + + return this.setup(builder); + } + + increaseUsage(builder) { + const nodeData = builder.getDataFromNode(this); + nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; + + return nodeData.usageCount; + } + + analyze(builder) { + const usageCount = this.increaseUsage(builder); + + if (usageCount === 1) { + // node flow children + + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of Object.values(nodeProperties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } + + generate(builder, output) { + const { outputNode } = builder.getNodeProperties(this); + + if (outputNode && outputNode.isNode === true) { + return outputNode.build(builder, output); + } + } + + updateBefore(/*frame*/) { + console.warn('Abstract function.'); + } + + update(/*frame*/) { + console.warn('Abstract function.'); + } + + build(builder, output = null) { + const refNode = this.getShared(builder); + + if (this !== refNode) { + return refNode.build(builder, output); + } + + builder.addNode(this); + builder.addChain(this); + + /* Build stages expected results: + - "setup" -> Node + - "analyze" -> null + - "generate" -> String + */ + let result = null; + + const buildStage = builder.getBuildStage(); + + if (buildStage === 'setup') { + this.updateReference(builder); + + const properties = builder.getNodeProperties(this); + + if (properties.initialized !== true || builder.context.tempRead === false) { + const stackNodesBeforeSetup = builder.stack.nodes.length; + + properties.initialized = true; + properties.outputNode = this.setup(builder); + + if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { + properties.outputNode = builder.stack; + } + + for (const childNode of Object.values(properties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } else if (buildStage === 'analyze') { + this.analyze(builder); + } else if (buildStage === 'generate') { + const isGenerateOnce = this.generate.length === 1; + + if (isGenerateOnce) { + const type = this.getNodeType(builder); + const nodeData = builder.getDataFromNode(this); + + result = nodeData.snippet; + + if (result === undefined /*|| builder.context.tempRead === false*/) { + result = this.generate(builder) || ''; + + nodeData.snippet = result; + } + + result = builder.format(result, type, output); + } else { + result = this.generate(builder, output) || ''; + } + } + + builder.removeChain(this); + + return result; + } + + getSerializeChildren() { + return getNodeChildren(this); + } + + serialize(json) { + const nodeChildren = this.getSerializeChildren(); + + const inputNodes = {}; + + for (const { property, index, childNode } of nodeChildren) { + if (index !== undefined) { + if (inputNodes[property] === undefined) { + inputNodes[property] = Number.isInteger(index) ? [] : {}; + } + + inputNodes[property][index] = childNode.toJSON(json.meta).uuid; + } else { + inputNodes[property] = childNode.toJSON(json.meta).uuid; + } + } + + if (Object.keys(inputNodes).length > 0) { + json.inputNodes = inputNodes; + } + } + + deserialize(json) { + if (json.inputNodes !== undefined) { + const nodes = json.meta.nodes; + + for (const property in json.inputNodes) { + if (Array.isArray(json.inputNodes[property])) { + const inputArray = []; + + for (const uuid of json.inputNodes[property]) { + inputArray.push(nodes[uuid]); + } + + this[property] = inputArray; + } else if (typeof json.inputNodes[property] === 'object') { + const inputObject = {}; + + for (const subProperty in json.inputNodes[property]) { + const uuid = json.inputNodes[property][subProperty]; + + inputObject[subProperty] = nodes[uuid]; + } + + this[property] = inputObject; + } else { + const uuid = json.inputNodes[property]; + + this[property] = nodes[uuid]; + } + } + } + } + + toJSON(meta) { + const { uuid, type } = this; + const isRoot = meta === undefined || typeof meta === 'string'; + + if (isRoot) { + meta = { + textures: {}, + images: {}, + nodes: {}, + }; + } + + // serialize + + let data = meta.nodes[uuid]; + + if (data === undefined) { + data = { + uuid, + type, + meta, + metadata: { + version: 4.6, + type: 'Node', + generator: 'Node.toJSON', + }, + }; + + if (isRoot !== true) meta.nodes[data.uuid] = data; + + this.serialize(data); + + delete data.meta; + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache(cache) { + const values = []; + + for (const key in cache) { + const data = cache[key]; + delete data.metadata; + values.push(data); + } + + return values; + } + + if (isRoot) { + const textures = extractFromCache(meta.textures); + const images = extractFromCache(meta.images); + const nodes = extractFromCache(meta.nodes); + + if (textures.length > 0) data.textures = textures; + if (images.length > 0) data.images = images; + if (nodes.length > 0) data.nodes = nodes; + } + + return data; + } +} + +export default Node; + +export function addNodeClass(type, nodeClass) { + if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); + if (NodeClasses.has(type)) { + console.warn(`Redefinition of node class ${type}`); + return; + } + + NodeClasses.set(type, nodeClass); + nodeClass.type = type; +} + +export function createNodeFromType(type) { + const Class = NodeClasses.get(type); + + if (Class !== undefined) { + return new Class(); + } +} diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts new file mode 100644 index 000000000..ebdc13ff1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts @@ -0,0 +1,1011 @@ +import NodeUniform from './NodeUniform.js'; +import NodeAttribute from './NodeAttribute.js'; +import NodeVarying from './NodeVarying.js'; +import NodeVar from './NodeVar.js'; +import NodeCode from './NodeCode.js'; +import NodeKeywords from './NodeKeywords.js'; +import NodeCache from './NodeCache.js'; +import ParameterNode from './ParameterNode.js'; +import FunctionNode from '../code/FunctionNode.js'; +import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; +import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; + +import { + FloatNodeUniform, + Vector2NodeUniform, + Vector3NodeUniform, + Vector4NodeUniform, + ColorNodeUniform, + Matrix3NodeUniform, + Matrix4NodeUniform, +} from '../../renderers/common/nodes/NodeUniform.js'; + +import { + REVISION, + RenderTarget, + Color, + Vector2, + Vector3, + Vector4, + IntType, + UnsignedIntType, + Float16BufferAttribute, +} from 'three'; + +import { stack } from './StackNode.js'; +import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; + +import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; +import ChainMap from '../../renderers/common/ChainMap.js'; + +import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; + +const uniformsGroupCache = new ChainMap(); + +const typeFromLength = new Map([ + [2, 'vec2'], + [3, 'vec3'], + [4, 'vec4'], + [9, 'mat3'], + [16, 'mat4'], +]); + +const typeFromArray = new Map([ + [Int8Array, 'int'], + [Int16Array, 'int'], + [Int32Array, 'int'], + [Uint8Array, 'uint'], + [Uint16Array, 'uint'], + [Uint32Array, 'uint'], + [Float32Array, 'float'], +]); + +const toFloat = value => { + value = Number(value); + + return value + (value % 1 ? '' : '.0'); +}; + +class NodeBuilder { + constructor(object, renderer, parser, scene = null, material = null) { + this.object = object; + this.material = material || (object && object.material) || null; + this.geometry = (object && object.geometry) || null; + this.renderer = renderer; + this.parser = parser; + this.scene = scene; + + this.nodes = []; + this.updateNodes = []; + this.updateBeforeNodes = []; + this.hashNodes = {}; + + this.lightsNode = null; + this.environmentNode = null; + this.fogNode = null; + + this.clippingContext = null; + + this.vertexShader = null; + this.fragmentShader = null; + this.computeShader = null; + + this.flowNodes = { vertex: [], fragment: [], compute: [] }; + this.flowCode = { vertex: '', fragment: '', compute: [] }; + this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; + this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; + this.bindings = { vertex: [], fragment: [], compute: [] }; + this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; + this.bindingsArray = null; + this.attributes = []; + this.bufferAttributes = []; + this.varyings = []; + this.codes = {}; + this.vars = {}; + this.flow = { code: '' }; + this.chaining = []; + this.stack = stack(); + this.stacks = []; + this.tab = '\t'; + + this.currentFunctionNode = null; + + this.context = { + keywords: new NodeKeywords(), + material: this.material, + }; + + this.cache = new NodeCache(); + this.globalCache = this.cache; + + this.flowsData = new WeakMap(); + + this.shaderStage = null; + this.buildStage = null; + } + + createRenderTarget(width, height, options) { + return new RenderTarget(width, height, options); + } + + createCubeRenderTarget(size, options) { + return new CubeRenderTarget(size, options); + } + + createPMREMGenerator() { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return new PMREMGenerator(this.renderer); + } + + includes(node) { + return this.nodes.includes(node); + } + + _getSharedBindings(bindings) { + const shared = []; + + for (const binding of bindings) { + if (binding.shared === true) { + // nodes is the chainmap key + const nodes = binding.getNodes(); + + let sharedBinding = uniformsGroupCache.get(nodes); + + if (sharedBinding === undefined) { + uniformsGroupCache.set(nodes, binding); + + sharedBinding = binding; + } + + shared.push(sharedBinding); + } else { + shared.push(binding); + } + } + + return shared; + } + + getBindings() { + let bindingsArray = this.bindingsArray; + + if (bindingsArray === null) { + const bindings = this.bindings; + + this.bindingsArray = bindingsArray = this._getSharedBindings( + this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, + ); + } + + return bindingsArray; + } + + setHashNode(node, hash) { + this.hashNodes[hash] = node; + } + + addNode(node) { + if (this.nodes.includes(node) === false) { + this.nodes.push(node); + + this.setHashNode(node, node.getHash(this)); + } + } + + buildUpdateNodes() { + for (const node of this.nodes) { + const updateType = node.getUpdateType(); + const updateBeforeType = node.getUpdateBeforeType(); + + if (updateType !== NodeUpdateType.NONE) { + this.updateNodes.push(node.getSelf()); + } + + if (updateBeforeType !== NodeUpdateType.NONE) { + this.updateBeforeNodes.push(node); + } + } + } + + get currentNode() { + return this.chaining[this.chaining.length - 1]; + } + + addChain(node) { + /* + if ( this.chaining.indexOf( node ) !== - 1 ) { + + console.warn( 'Recursive node: ', node ); + + } + */ + + this.chaining.push(node); + } + + removeChain(node) { + const lastChain = this.chaining.pop(); + + if (lastChain !== node) { + throw new Error('NodeBuilder: Invalid node chaining!'); + } + } + + getMethod(method) { + return method; + } + + getNodeFromHash(hash) { + return this.hashNodes[hash]; + } + + addFlow(shaderStage, node) { + this.flowNodes[shaderStage].push(node); + + return node; + } + + setContext(context) { + this.context = context; + } + + getContext() { + return this.context; + } + + setCache(cache) { + this.cache = cache; + } + + getCache() { + return this.cache; + } + + isAvailable(/*name*/) { + return false; + } + + getVertexIndex() { + console.warn('Abstract function.'); + } + + getInstanceIndex() { + console.warn('Abstract function.'); + } + + getFrontFacing() { + console.warn('Abstract function.'); + } + + getFragCoord() { + console.warn('Abstract function.'); + } + + isFlipY() { + return false; + } + + generateTexture(/* texture, textureProperty, uvSnippet */) { + console.warn('Abstract function.'); + } + + generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { + console.warn('Abstract function.'); + } + + generateConst(type, value = null) { + if (value === null) { + if (type === 'float' || type === 'int' || type === 'uint') value = 0; + else if (type === 'bool') value = false; + else if (type === 'color') value = new Color(); + else if (type === 'vec2') value = new Vector2(); + else if (type === 'vec3') value = new Vector3(); + else if (type === 'vec4') value = new Vector4(); + } + + if (type === 'float') return toFloat(value); + if (type === 'int') return `${Math.round(value)}`; + if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; + if (type === 'bool') return value ? 'true' : 'false'; + if (type === 'color') + return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; + + const typeLength = this.getTypeLength(type); + + const componentType = this.getComponentType(type); + + const generateConst = value => this.generateConst(componentType, value); + + if (typeLength === 2) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; + } else if (typeLength === 3) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; + } else if (typeLength === 4) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; + } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { + return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; + } else if (typeLength > 4) { + return `${this.getType(type)}()`; + } + + throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); + } + + getType(type) { + if (type === 'color') return 'vec3'; + + return type; + } + + generateMethod(method) { + return method; + } + + hasGeometryAttribute(name) { + return this.geometry && this.geometry.getAttribute(name) !== undefined; + } + + getAttribute(name, type) { + const attributes = this.attributes; + + // find attribute + + for (const attribute of attributes) { + if (attribute.name === name) { + return attribute; + } + } + + // create a new if no exist + + const attribute = new NodeAttribute(name, type); + + attributes.push(attribute); + + return attribute; + } + + getPropertyName(node /*, shaderStage*/) { + return node.name; + } + + isVector(type) { + return /vec\d/.test(type); + } + + isMatrix(type) { + return /mat\d/.test(type); + } + + isReference(type) { + return ( + type === 'void' || + type === 'property' || + type === 'sampler' || + type === 'texture' || + type === 'cubeTexture' || + type === 'storageTexture' || + type === 'texture3D' + ); + } + + needsColorSpaceToLinear(/*texture*/) { + return false; + } + + getComponentTypeFromTexture(texture) { + const type = texture.type; + + if (texture.isDataTexture) { + if (type === IntType) return 'int'; + if (type === UnsignedIntType) return 'uint'; + } + + return 'float'; + } + + getElementType(type) { + if (type === 'mat2') return 'vec2'; + if (type === 'mat3') return 'vec3'; + if (type === 'mat4') return 'vec4'; + + return this.getComponentType(type); + } + + getComponentType(type) { + type = this.getVectorType(type); + + if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; + + const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); + + if (componentType === null) return null; + + if (componentType[1] === 'b') return 'bool'; + if (componentType[1] === 'i') return 'int'; + if (componentType[1] === 'u') return 'uint'; + + return 'float'; + } + + getVectorType(type) { + if (type === 'color') return 'vec3'; + if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') + return 'vec4'; + + return type; + } + + getTypeFromLength(length, componentType = 'float') { + if (length === 1) return componentType; + + const baseType = typeFromLength.get(length); + const prefix = componentType === 'float' ? '' : componentType[0]; + + return prefix + baseType; + } + + getTypeFromArray(array) { + return typeFromArray.get(array.constructor); + } + + getTypeFromAttribute(attribute) { + let dataAttribute = attribute; + + if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; + + const array = dataAttribute.array; + const itemSize = attribute.itemSize; + const normalized = attribute.normalized; + + let arrayType; + + if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { + arrayType = this.getTypeFromArray(array); + } + + return this.getTypeFromLength(itemSize, arrayType); + } + + getTypeLength(type) { + const vecType = this.getVectorType(type); + const vecNum = /vec([2-4])/.exec(vecType); + + if (vecNum !== null) return Number(vecNum[1]); + if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; + if (/mat2/.test(type) === true) return 4; + if (/mat3/.test(type) === true) return 9; + if (/mat4/.test(type) === true) return 16; + + return 0; + } + + getVectorFromMatrix(type) { + return type.replace('mat', 'vec'); + } + + changeComponentType(type, newComponentType) { + return this.getTypeFromLength(this.getTypeLength(type), newComponentType); + } + + getIntegerType(type) { + const componentType = this.getComponentType(type); + + if (componentType === 'int' || componentType === 'uint') return type; + + return this.changeComponentType(type, 'int'); + } + + addStack() { + this.stack = stack(this.stack); + + this.stacks.push(getCurrentStack() || this.stack); + setCurrentStack(this.stack); + + return this.stack; + } + + removeStack() { + const lastStack = this.stack; + this.stack = lastStack.parent; + + setCurrentStack(this.stacks.pop()); + + return lastStack; + } + + getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { + cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; + + let nodeData = cache.getNodeData(node); + + if (nodeData === undefined) { + nodeData = {}; + + cache.setNodeData(node, nodeData); + } + + if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; + + return nodeData[shaderStage]; + } + + getNodeProperties(node, shaderStage = 'any') { + const nodeData = this.getDataFromNode(node, shaderStage); + + return nodeData.properties || (nodeData.properties = { outputNode: null }); + } + + getBufferAttributeFromNode(node, type) { + const nodeData = this.getDataFromNode(node); + + let bufferAttribute = nodeData.bufferAttribute; + + if (bufferAttribute === undefined) { + const index = this.uniforms.index++; + + bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); + + this.bufferAttributes.push(bufferAttribute); + + nodeData.bufferAttribute = bufferAttribute; + } + + return bufferAttribute; + } + + getStructTypeFromNode(node, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + if (nodeData.structType === undefined) { + const index = this.structs.index++; + + node.name = `StructType${index}`; + this.structs[shaderStage].push(node); + + nodeData.structType = node; + } + + return node; + } + + getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { + const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); + + let nodeUniform = nodeData.uniform; + + if (nodeUniform === undefined) { + const index = this.uniforms.index++; + + nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); + + this.uniforms[shaderStage].push(nodeUniform); + + nodeData.uniform = nodeUniform; + } + + return nodeUniform; + } + + getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + let nodeVar = nodeData.variable; + + if (nodeVar === undefined) { + const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); + + if (name === null) name = 'nodeVar' + vars.length; + + nodeVar = new NodeVar(name, type); + + vars.push(nodeVar); + + nodeData.variable = nodeVar; + } + + return nodeVar; + } + + getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { + const nodeData = this.getDataFromNode(node, 'any'); + + let nodeVarying = nodeData.varying; + + if (nodeVarying === undefined) { + const varyings = this.varyings; + const index = varyings.length; + + if (name === null) name = 'nodeVarying' + index; + + nodeVarying = new NodeVarying(name, type); + + varyings.push(nodeVarying); + + nodeData.varying = nodeVarying; + } + + return nodeVarying; + } + + getCodeFromNode(node, type, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node); + + let nodeCode = nodeData.code; + + if (nodeCode === undefined) { + const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); + const index = codes.length; + + nodeCode = new NodeCode('nodeCode' + index, type); + + codes.push(nodeCode); + + nodeData.code = nodeCode; + } + + return nodeCode; + } + + addLineFlowCode(code) { + if (code === '') return this; + + code = this.tab + code; + + if (!/;\s*$/.test(code)) { + code = code + ';\n'; + } + + this.flow.code += code; + + return this; + } + + addFlowCode(code) { + this.flow.code += code; + + return this; + } + + addFlowTab() { + this.tab += '\t'; + + return this; + } + + removeFlowTab() { + this.tab = this.tab.slice(0, -1); + + return this; + } + + getFlowData(node /*, shaderStage*/) { + return this.flowsData.get(node); + } + + flowNode(node) { + const output = node.getNodeType(this); + + const flowData = this.flowChildNode(node, output); + + this.flowsData.set(node, flowData); + + return flowData; + } + + buildFunctionNode(shaderNode) { + const fn = new FunctionNode(); + + const previous = this.currentFunctionNode; + + this.currentFunctionNode = fn; + + fn.code = this.buildFunctionCode(shaderNode); + + this.currentFunctionNode = previous; + + return fn; + } + + flowShaderNode(shaderNode) { + const layout = shaderNode.layout; + + let inputs; + + if (shaderNode.isArrayInput) { + inputs = []; + + for (const input of layout.inputs) { + inputs.push(new ParameterNode(input.type, input.name)); + } + } else { + inputs = {}; + + for (const input of layout.inputs) { + inputs[input.name] = new ParameterNode(input.type, input.name); + } + } + + // + + shaderNode.layout = null; + + const callNode = shaderNode.call(inputs); + const flowData = this.flowStagesNode(callNode, layout.type); + + shaderNode.layout = layout; + + return flowData; + } + + flowStagesNode(node, output = null) { + const previousFlow = this.flow; + const previousVars = this.vars; + const previousBuildStage = this.buildStage; + + const flow = { + code: '', + }; + + this.flow = flow; + this.vars = {}; + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + flow.result = node.build(this, output); + } + + flow.vars = this.getVars(this.shaderStage); + + this.flow = previousFlow; + this.vars = previousVars; + this.setBuildStage(previousBuildStage); + + return flow; + } + + getFunctionOperator() { + return null; + } + + flowChildNode(node, output = null) { + const previousFlow = this.flow; + + const flow = { + code: '', + }; + + this.flow = flow; + + flow.result = node.build(this, output); + + this.flow = previousFlow; + + return flow; + } + + flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { + const previousShaderStage = this.shaderStage; + + this.setShaderStage(shaderStage); + + const flowData = this.flowChildNode(node, output); + + if (propertyName !== null) { + flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; + } + + this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; + + this.setShaderStage(previousShaderStage); + + return flowData; + } + + getAttributesArray() { + return this.attributes.concat(this.bufferAttributes); + } + + getAttributes(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVaryings(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVar(type, name) { + return `${this.getType(type)} ${name}`; + } + + getVars(shaderStage) { + let snippet = ''; + + const vars = this.vars[shaderStage]; + + if (vars !== undefined) { + for (const variable of vars) { + snippet += `${this.getVar(variable.type, variable.name)}; `; + } + } + + return snippet; + } + + getUniforms(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getCodes(shaderStage) { + const codes = this.codes[shaderStage]; + + let code = ''; + + if (codes !== undefined) { + for (const nodeCode of codes) { + code += nodeCode.code + '\n'; + } + } + + return code; + } + + getHash() { + return this.vertexShader + this.fragmentShader + this.computeShader; + } + + setShaderStage(shaderStage) { + this.shaderStage = shaderStage; + } + + getShaderStage() { + return this.shaderStage; + } + + setBuildStage(buildStage) { + this.buildStage = buildStage; + } + + getBuildStage() { + return this.buildStage; + } + + buildCode() { + console.warn('Abstract function.'); + } + + build() { + const { object, material } = this; + + if (material !== null) { + NodeMaterial.fromMaterial(material).build(this); + } else { + this.addFlow('compute', object); + } + + // setup() -> stage 1: create possible new nodes and returns an output reference node + // analyze() -> stage 2: analyze nodes to possible optimization and validation + // generate() -> stage 3: generate shader + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + if (this.context.vertex && this.context.vertex.isNode) { + this.flowNodeFromShaderStage('vertex', this.context.vertex); + } + + for (const shaderStage of shaderStages) { + this.setShaderStage(shaderStage); + + const flowNodes = this.flowNodes[shaderStage]; + + for (const node of flowNodes) { + if (buildStage === 'generate') { + this.flowNode(node); + } else { + node.build(this); + } + } + } + } + + this.setBuildStage(null); + this.setShaderStage(null); + + // stage 4: build code for a specific output + + this.buildCode(); + this.buildUpdateNodes(); + + return this; + } + + getNodeUniform(uniformNode, type) { + if (type === 'float') return new FloatNodeUniform(uniformNode); + if (type === 'vec2') return new Vector2NodeUniform(uniformNode); + if (type === 'vec3') return new Vector3NodeUniform(uniformNode); + if (type === 'vec4') return new Vector4NodeUniform(uniformNode); + if (type === 'color') return new ColorNodeUniform(uniformNode); + if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); + if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); + + throw new Error(`Uniform "${type}" not declared.`); + } + + createNodeMaterial(type = 'NodeMaterial') { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return createNodeMaterialFromType(type); + } + + format(snippet, fromType, toType) { + fromType = this.getVectorType(fromType); + toType = this.getVectorType(toType); + + if (fromType === toType || toType === null || this.isReference(toType)) { + return snippet; + } + + const fromTypeLength = this.getTypeLength(fromType); + const toTypeLength = this.getTypeLength(toType); + + if (fromTypeLength > 4) { + // fromType is matrix-like + + // @TODO: ignore for now + + return snippet; + } + + if (toTypeLength > 4 || toTypeLength === 0) { + // toType is matrix-like or unknown + + // @TODO: ignore for now + + return snippet; + } + + if (fromTypeLength === toTypeLength) { + return `${this.getType(toType)}( ${snippet} )`; + } + + if (fromTypeLength > toTypeLength) { + return this.format( + `${snippet}.${'xyz'.slice(0, toTypeLength)}`, + this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), + toType, + ); + } + + if (toTypeLength === 4 && fromTypeLength > 1) { + // toType is vec4-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; + } + + if (fromTypeLength === 2) { + // fromType is vec2-like and toType is vec3-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; + } + + if (fromTypeLength === 1 && toTypeLength > 1 && fromType[0] !== toType[0]) { + // fromType is float-like + + // convert a number value to vector type, e.g: + // vec3( 1u ) -> vec3( float( 1u ) ) + + snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; + } + + return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like + } + + getSignature() { + return `// Three.js r${REVISION} - NodeMaterial System\n`; + } +} + +export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts new file mode 100644 index 000000000..b8e8d37b6 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeFrame.ts @@ -0,0 +1,101 @@ +import { NodeUpdateType } from './constants.js'; + +class NodeFrame { + constructor() { + this.time = 0; + this.deltaTime = 0; + + this.frameId = 0; + this.renderId = 0; + + this.startTime = null; + + this.updateMap = new WeakMap(); + this.updateBeforeMap = new WeakMap(); + + this.renderer = null; + this.material = null; + this.camera = null; + this.object = null; + this.scene = null; + } + + _getMaps(referenceMap, nodeRef) { + let maps = referenceMap.get(nodeRef); + + if (maps === undefined) { + maps = { + renderMap: new WeakMap(), + frameMap: new WeakMap(), + }; + + referenceMap.set(nodeRef, maps); + } + + return maps; + } + + updateBeforeNode(node) { + const updateType = node.getUpdateBeforeType(); + const reference = node.updateReference(this); + + if (updateType === NodeUpdateType.FRAME) { + const { frameMap } = this._getMaps(this.updateBeforeMap, reference); + + if (frameMap.get(reference) !== this.frameId) { + if (node.updateBefore(this) !== false) { + frameMap.set(reference, this.frameId); + } + } + } else if (updateType === NodeUpdateType.RENDER) { + const { renderMap } = this._getMaps(this.updateBeforeMap, reference); + + if (renderMap.get(reference) !== this.renderId) { + if (node.updateBefore(this) !== false) { + renderMap.set(reference, this.renderId); + } + } + } else if (updateType === NodeUpdateType.OBJECT) { + node.updateBefore(this); + } + } + + updateNode(node) { + const updateType = node.getUpdateType(); + const reference = node.updateReference(this); + + if (updateType === NodeUpdateType.FRAME) { + const { frameMap } = this._getMaps(this.updateMap, reference); + + if (frameMap.get(reference) !== this.frameId) { + if (node.update(this) !== false) { + frameMap.set(reference, this.frameId); + } + } + } else if (updateType === NodeUpdateType.RENDER) { + const { renderMap } = this._getMaps(this.updateMap, reference); + + if (renderMap.get(reference) !== this.renderId) { + if (node.update(this) !== false) { + renderMap.set(reference, this.renderId); + } + } + } else if (updateType === NodeUpdateType.OBJECT) { + node.update(this); + } + } + + update() { + this.frameId++; + + if (this.lastTime === undefined) this.lastTime = performance.now(); + + this.deltaTime = (performance.now() - this.lastTime) / 1000; + + this.lastTime = performance.now(); + + this.time += this.deltaTime; + } +} + +export default NodeFrame; diff --git a/examples-jsm/examples/nodes/core/NodeParser.ts b/examples-jsm/examples/nodes/core/NodeParser.ts new file mode 100644 index 000000000..9849452f1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeParser.ts @@ -0,0 +1,7 @@ +class NodeParser { + parseFunction(/*source*/) { + console.warn('Abstract function.'); + } +} + +export default NodeParser; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts new file mode 100644 index 000000000..3b01a9a6d --- /dev/null +++ b/examples-jsm/examples/nodes/core/constants.ts @@ -0,0 +1,28 @@ +export const NodeShaderStage = { + VERTEX: 'vertex', + FRAGMENT: 'fragment', +}; + +export const NodeUpdateType = { + NONE: 'none', + FRAME: 'frame', + RENDER: 'render', + OBJECT: 'object', +}; + +export const NodeType = { + BOOLEAN: 'bool', + INTEGER: 'int', + FLOAT: 'float', + VECTOR2: 'vec2', + VECTOR3: 'vec3', + VECTOR4: 'vec4', + MATRIX2: 'mat2', + MATRIX3: 'mat3', + MATRIX4: 'mat4', +}; + +export const defaultShaderStages = ['fragment', 'vertex']; +export const defaultBuildStages = ['setup', 'analyze', 'generate']; +export const shaderStages = [...defaultShaderStages, 'compute']; +export const vectorComponents = ['x', 'y', 'z', 'w']; diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts new file mode 100644 index 000000000..85713cfe6 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Renderer.ts @@ -0,0 +1,1225 @@ +import Animation from './Animation.js'; +import RenderObjects from './RenderObjects.js'; +import Attributes from './Attributes.js'; +import Geometries from './Geometries.js'; +import Info from './Info.js'; +import Pipelines from './Pipelines.js'; +import Bindings from './Bindings.js'; +import RenderLists from './RenderLists.js'; +import RenderContexts from './RenderContexts.js'; +import Textures from './Textures.js'; +import Background from './Background.js'; +import Nodes from './nodes/Nodes.js'; +import Color4 from './Color4.js'; +import ClippingContext from './ClippingContext.js'; +import { + Scene, + Frustum, + Matrix4, + Vector2, + Vector3, + Vector4, + DoubleSide, + BackSide, + FrontSide, + SRGBColorSpace, + NoColorSpace, + NoToneMapping, + LinearFilter, + LinearSRGBColorSpace, + RenderTarget, + HalfFloatType, + RGBAFormat, +} from 'three'; +import { NodeMaterial } from '../../nodes/Nodes.js'; +import QuadMesh from '../../objects/QuadMesh.js'; + +const _scene = new Scene(); +const _drawingBufferSize = new Vector2(); +const _screen = new Vector4(); +const _frustum = new Frustum(); +const _projScreenMatrix = new Matrix4(); +const _vector3 = new Vector3(); +const _quad = new QuadMesh(new NodeMaterial()); + +class Renderer { + constructor(backend, parameters = {}) { + this.isRenderer = true; + + // + + const { logarithmicDepthBuffer = false, alpha = true } = parameters; + + // public + + this.domElement = backend.getDomElement(); + + this.backend = backend; + + this.autoClear = true; + this.autoClearColor = true; + this.autoClearDepth = true; + this.autoClearStencil = true; + + this.alpha = alpha; + + this.logarithmicDepthBuffer = logarithmicDepthBuffer; + + this.outputColorSpace = SRGBColorSpace; + + this.toneMapping = NoToneMapping; + this.toneMappingExposure = 1.0; + + this.sortObjects = true; + + this.depth = true; + this.stencil = false; + + this.clippingPlanes = []; + + this.info = new Info(); + + // nodes + + this.toneMappingNode = null; + + // internals + + this._pixelRatio = 1; + this._width = this.domElement.width; + this._height = this.domElement.height; + + this._viewport = new Vector4(0, 0, this._width, this._height); + this._scissor = new Vector4(0, 0, this._width, this._height); + this._scissorTest = false; + + this._attributes = null; + this._geometries = null; + this._nodes = null; + this._animation = null; + this._bindings = null; + this._objects = null; + this._pipelines = null; + this._renderLists = null; + this._renderContexts = null; + this._textures = null; + this._background = null; + + this._currentRenderContext = null; + + this._opaqueSort = null; + this._transparentSort = null; + + this._frameBufferTarget = null; + + const alphaClear = this.alpha === true ? 0 : 1; + + this._clearColor = new Color4(0, 0, 0, alphaClear); + this._clearDepth = 1; + this._clearStencil = 0; + + this._renderTarget = null; + this._activeCubeFace = 0; + this._activeMipmapLevel = 0; + + this._renderObjectFunction = null; + this._currentRenderObjectFunction = null; + + this._handleObjectFunction = this._renderObjectDirect; + + this._initialized = false; + this._initPromise = null; + + this._compilationPromises = null; + + // backwards compatibility + + this.shadowMap = { + enabled: false, + type: null, + }; + + this.xr = { + enabled: false, + }; + } + + async init() { + if (this._initialized) { + throw new Error('Renderer: Backend has already been initialized.'); + } + + if (this._initPromise !== null) { + return this._initPromise; + } + + this._initPromise = new Promise(async (resolve, reject) => { + const backend = this.backend; + + try { + await backend.init(this); + } catch (error) { + reject(error); + return; + } + + this._nodes = new Nodes(this, backend); + this._animation = new Animation(this._nodes, this.info); + this._attributes = new Attributes(backend); + this._background = new Background(this, this._nodes); + this._geometries = new Geometries(this._attributes, this.info); + this._textures = new Textures(this, backend, this.info); + this._pipelines = new Pipelines(backend, this._nodes); + this._bindings = new Bindings( + backend, + this._nodes, + this._textures, + this._attributes, + this._pipelines, + this.info, + ); + this._objects = new RenderObjects( + this, + this._nodes, + this._geometries, + this._pipelines, + this._bindings, + this.info, + ); + this._renderLists = new RenderLists(); + this._renderContexts = new RenderContexts(); + + // + + this._initialized = true; + + resolve(); + }); + + return this._initPromise; + } + + get coordinateSystem() { + return this.backend.coordinateSystem; + } + + async compileAsync(scene, camera, targetScene = null) { + if (this._initialized === false) await this.init(); + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + const previousCompilationPromises = this._compilationPromises; + + // + + const sceneRef = scene.isScene === true ? scene : _scene; + + if (targetScene === null) targetScene = scene; + + const renderTarget = this._renderTarget; + const renderContext = this._renderContexts.get(targetScene, camera, renderTarget); + const activeMipmapLevel = this._activeMipmapLevel; + + const compilationPromises = []; + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this.renderObject; + + this._handleObjectFunction = this._createObjectPipeline; + + this._compilationPromises = compilationPromises; + + nodeFrame.renderId++; + + // + + nodeFrame.update(); + + // + + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + + if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal(this, camera); + + // + + sceneRef.onBeforeRender(this, scene, camera, renderTarget); + + // + + const renderList = this._renderLists.get(scene, camera); + renderList.begin(); + + this._projectObject(scene, camera, 0, renderList); + + // include lights from target scene + if (targetScene !== scene) { + targetScene.traverseVisible(function (object) { + if (object.isLight && object.layers.test(camera.layers)) { + renderList.pushLight(object); + } + }); + } + + renderList.finish(); + + // + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); + + const renderTargetData = this._textures.get(renderTarget); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + } else { + renderContext.textures = null; + renderContext.depthTexture = null; + } + + // + + this._nodes.updateScene(sceneRef); + + // + + this._background.update(sceneRef, renderList, renderContext); + + // process render lists + + const opaqueObjects = renderList.opaque; + const transparentObjects = renderList.transparent; + const lightsNode = renderList.lightsNode; + + if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); + if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + this._compilationPromises = previousCompilationPromises; + + this._handleObjectFunction = this._renderObjectDirect; + + // wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete + + await Promise.all(compilationPromises); + } + + async renderAsync(scene, camera) { + if (this._initialized === false) await this.init(); + + const renderContext = this._renderScene(scene, camera); + + await this.backend.resolveTimestampAsync(renderContext, 'render'); + } + + render(scene, camera) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', + ); + + return this.renderAsync(scene, camera); + } + + this._renderScene(scene, camera); + } + + _getFrameBufferTarget() { + const { currentColorSpace } = this; + + const useToneMapping = + this._renderTarget === null && (this.toneMapping !== NoToneMapping || this.toneMappingNode !== null); + const useColorSpace = currentColorSpace !== LinearSRGBColorSpace && currentColorSpace !== NoColorSpace; + + if (useToneMapping === false && useColorSpace === false) return null; + + const { width, height } = this.getDrawingBufferSize(_drawingBufferSize); + const { depth, stencil } = this; + + let frameBufferTarget = this._frameBufferTarget; + + if (frameBufferTarget === null) { + frameBufferTarget = new RenderTarget(width, height, { + depthBuffer: depth, + stencilBuffer: stencil, + type: HalfFloatType, // FloatType + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + generateMipmaps: false, + minFilter: LinearFilter, + magFilter: LinearFilter, + samples: this.backend.parameters.antialias ? 4 : 0, + }); + + frameBufferTarget.isPostProcessingRenderTarget = true; + + this._frameBufferTarget = frameBufferTarget; + } + + frameBufferTarget.depthBuffer = depth; + frameBufferTarget.stencilBuffer = stencil; + frameBufferTarget.setSize(width, height); + frameBufferTarget.viewport.copy(this._viewport); + frameBufferTarget.scissor.copy(this._scissor); + frameBufferTarget.viewport.multiplyScalar(this._pixelRatio); + frameBufferTarget.scissor.multiplyScalar(this._pixelRatio); + frameBufferTarget.scissorTest = this._scissorTest; + + return frameBufferTarget; + } + + _renderScene(scene, camera, useFrameBufferTarget = true) { + const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + + // + + const sceneRef = scene.isScene === true ? scene : _scene; + + const outputRenderTarget = this._renderTarget; + + const activeCubeFace = this._activeCubeFace; + const activeMipmapLevel = this._activeMipmapLevel; + + // + + let renderTarget; + + if (frameBufferTarget !== null) { + renderTarget = frameBufferTarget; + + this.setRenderTarget(renderTarget); + } else { + renderTarget = outputRenderTarget; + } + + // + + const renderContext = this._renderContexts.get(scene, camera, renderTarget); + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; + + // + + this.info.calls++; + this.info.render.calls++; + + nodeFrame.renderId = this.info.calls; + + // + + const coordinateSystem = this.coordinateSystem; + + if (camera.coordinateSystem !== coordinateSystem) { + camera.coordinateSystem = coordinateSystem; + + camera.updateProjectionMatrix(); + } + + // + + if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld(); + + if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld(); + + // + + let viewport = this._viewport; + let scissor = this._scissor; + let pixelRatio = this._pixelRatio; + + if (renderTarget !== null) { + viewport = renderTarget.viewport; + scissor = renderTarget.scissor; + pixelRatio = 1; + } + + this.getDrawingBufferSize(_drawingBufferSize); + + _screen.set(0, 0, _drawingBufferSize.width, _drawingBufferSize.height); + + const minDepth = viewport.minDepth === undefined ? 0 : viewport.minDepth; + const maxDepth = viewport.maxDepth === undefined ? 1 : viewport.maxDepth; + + renderContext.viewportValue.copy(viewport).multiplyScalar(pixelRatio).floor(); + renderContext.viewportValue.width >>= activeMipmapLevel; + renderContext.viewportValue.height >>= activeMipmapLevel; + renderContext.viewportValue.minDepth = minDepth; + renderContext.viewportValue.maxDepth = maxDepth; + renderContext.viewport = renderContext.viewportValue.equals(_screen) === false; + + renderContext.scissorValue.copy(scissor).multiplyScalar(pixelRatio).floor(); + renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals(_screen) === false; + renderContext.scissorValue.width >>= activeMipmapLevel; + renderContext.scissorValue.height >>= activeMipmapLevel; + + if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal(this, camera); + + // + + sceneRef.onBeforeRender(this, scene, camera, renderTarget); + + // + + _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + _frustum.setFromProjectionMatrix(_projScreenMatrix, coordinateSystem); + + const renderList = this._renderLists.get(scene, camera); + renderList.begin(); + + this._projectObject(scene, camera, 0, renderList); + + renderList.finish(); + + if (this.sortObjects === true) { + renderList.sort(this._opaqueSort, this._transparentSort); + } + + // + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); + + const renderTargetData = this._textures.get(renderTarget); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + renderContext.width = renderTargetData.width; + renderContext.height = renderTargetData.height; + renderContext.renderTarget = renderTarget; + renderContext.depth = renderTarget.depthBuffer; + renderContext.stencil = renderTarget.stencilBuffer; + } else { + renderContext.textures = null; + renderContext.depthTexture = null; + renderContext.width = this.domElement.width; + renderContext.height = this.domElement.height; + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + } + + renderContext.width >>= activeMipmapLevel; + renderContext.height >>= activeMipmapLevel; + renderContext.activeCubeFace = activeCubeFace; + renderContext.activeMipmapLevel = activeMipmapLevel; + renderContext.occlusionQueryCount = renderList.occlusionQueryCount; + + // + + this._nodes.updateScene(sceneRef); + + // + + this._background.update(sceneRef, renderList, renderContext); + + // + + this.backend.beginRender(renderContext); + + // process render lists + + const opaqueObjects = renderList.opaque; + const transparentObjects = renderList.transparent; + const lightsNode = renderList.lightsNode; + + if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); + if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); + + // finish render pass + + this.backend.finishRender(renderContext); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + + // + + if (frameBufferTarget !== null) { + this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); + + _quad.material.fragmentNode = this._nodes.getOutputNode(renderTarget.texture); + + this._renderScene(_quad, _quad.camera, false); + } + + // + + sceneRef.onAfterRender(this, scene, camera, renderTarget); + + // + + return renderContext; + } + + getMaxAnisotropy() { + return this.backend.getMaxAnisotropy(); + } + + getActiveCubeFace() { + return this._activeCubeFace; + } + + getActiveMipmapLevel() { + return this._activeMipmapLevel; + } + + async setAnimationLoop(callback) { + if (this._initialized === false) await this.init(); + + this._animation.setAnimationLoop(callback); + } + + getArrayBuffer(attribute) { + // @deprecated, r155 + + console.warn('THREE.Renderer: getArrayBuffer() is deprecated. Use getArrayBufferAsync() instead.'); + + return this.getArrayBufferAsync(attribute); + } + + async getArrayBufferAsync(attribute) { + return await this.backend.getArrayBufferAsync(attribute); + } + + getContext() { + return this.backend.getContext(); + } + + getPixelRatio() { + return this._pixelRatio; + } + + getDrawingBufferSize(target) { + return target.set(this._width * this._pixelRatio, this._height * this._pixelRatio).floor(); + } + + getSize(target) { + return target.set(this._width, this._height); + } + + setPixelRatio(value = 1) { + this._pixelRatio = value; + + this.setSize(this._width, this._height, false); + } + + setDrawingBufferSize(width, height, pixelRatio) { + this._width = width; + this._height = height; + + this._pixelRatio = pixelRatio; + + this.domElement.width = Math.floor(width * pixelRatio); + this.domElement.height = Math.floor(height * pixelRatio); + + this.setViewport(0, 0, width, height); + + if (this._initialized) this.backend.updateSize(); + } + + setSize(width, height, updateStyle = true) { + this._width = width; + this._height = height; + + this.domElement.width = Math.floor(width * this._pixelRatio); + this.domElement.height = Math.floor(height * this._pixelRatio); + + if (updateStyle === true) { + this.domElement.style.width = width + 'px'; + this.domElement.style.height = height + 'px'; + } + + this.setViewport(0, 0, width, height); + + if (this._initialized) this.backend.updateSize(); + } + + setOpaqueSort(method) { + this._opaqueSort = method; + } + + setTransparentSort(method) { + this._transparentSort = method; + } + + getScissor(target) { + const scissor = this._scissor; + + target.x = scissor.x; + target.y = scissor.y; + target.width = scissor.width; + target.height = scissor.height; + + return target; + } + + setScissor(x, y, width, height) { + const scissor = this._scissor; + + if (x.isVector4) { + scissor.copy(x); + } else { + scissor.set(x, y, width, height); + } + } + + getScissorTest() { + return this._scissorTest; + } + + setScissorTest(boolean) { + this._scissorTest = boolean; + + this.backend.setScissorTest(boolean); + } + + getViewport(target) { + return target.copy(this._viewport); + } + + setViewport(x, y, width, height, minDepth = 0, maxDepth = 1) { + const viewport = this._viewport; + + if (x.isVector4) { + viewport.copy(x); + } else { + viewport.set(x, y, width, height); + } + + viewport.minDepth = minDepth; + viewport.maxDepth = maxDepth; + } + + getClearColor(target) { + return target.copy(this._clearColor); + } + + setClearColor(color, alpha = 1) { + this._clearColor.set(color); + this._clearColor.a = alpha; + } + + getClearAlpha() { + return this._clearColor.a; + } + + setClearAlpha(alpha) { + this._clearColor.a = alpha; + } + + getClearDepth() { + return this._clearDepth; + } + + setClearDepth(depth) { + this._clearDepth = depth; + } + + getClearStencil() { + return this._clearStencil; + } + + setClearStencil(stencil) { + this._clearStencil = stencil; + } + + isOccluded(object) { + const renderContext = this._currentRenderContext; + + return renderContext && this.backend.isOccluded(renderContext, object); + } + + clear(color = true, depth = true, stencil = true) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead.', + ); + + return this.clearAsync(color, depth, stencil); + } + + const renderTarget = this._renderTarget || this._getFrameBufferTarget(); + + let renderTargetData = null; + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget); + + renderTargetData = this._textures.get(renderTarget); + } + + this.backend.clear(color, depth, stencil, renderTargetData); + } + + clearColor() { + return this.clear(true, false, false); + } + + clearDepth() { + return this.clear(false, true, false); + } + + clearStencil() { + return this.clear(false, false, true); + } + + async clearAsync(color = true, depth = true, stencil = true) { + if (this._initialized === false) await this.init(); + + this.clear(color, depth, stencil); + } + + clearColorAsync() { + return this.clearAsync(true, false, false); + } + + clearDepthAsync() { + return this.clearAsync(false, true, false); + } + + clearStencilAsync() { + return this.clearAsync(false, false, true); + } + + get currentColorSpace() { + const renderTarget = this._renderTarget; + + if (renderTarget !== null) { + const texture = renderTarget.texture; + + return (Array.isArray(texture) ? texture[0] : texture).colorSpace; + } + + return this.outputColorSpace; + } + + dispose() { + this.info.dispose(); + + this._animation.dispose(); + this._objects.dispose(); + this._pipelines.dispose(); + this._nodes.dispose(); + this._bindings.dispose(); + this._renderLists.dispose(); + this._renderContexts.dispose(); + this._textures.dispose(); + + this.setRenderTarget(null); + this.setAnimationLoop(null); + } + + setRenderTarget(renderTarget, activeCubeFace = 0, activeMipmapLevel = 0) { + this._renderTarget = renderTarget; + this._activeCubeFace = activeCubeFace; + this._activeMipmapLevel = activeMipmapLevel; + } + + getRenderTarget() { + return this._renderTarget; + } + + setRenderObjectFunction(renderObjectFunction) { + this._renderObjectFunction = renderObjectFunction; + } + + getRenderObjectFunction() { + return this._renderObjectFunction; + } + + async computeAsync(computeNodes) { + if (this._initialized === false) await this.init(); + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + + // + + this.info.calls++; + this.info.compute.calls++; + this.info.compute.computeCalls++; + + nodeFrame.renderId = this.info.calls; + + // + + const backend = this.backend; + const pipelines = this._pipelines; + const bindings = this._bindings; + const nodes = this._nodes; + const computeList = Array.isArray(computeNodes) ? computeNodes : [computeNodes]; + + if (computeList[0] === undefined || computeList[0].isComputeNode !== true) { + throw new Error('THREE.Renderer: .compute() expects a ComputeNode.'); + } + + backend.beginCompute(computeNodes); + + for (const computeNode of computeList) { + // onInit + + if (pipelines.has(computeNode) === false) { + const dispose = () => { + computeNode.removeEventListener('dispose', dispose); + + pipelines.delete(computeNode); + bindings.delete(computeNode); + nodes.delete(computeNode); + }; + + computeNode.addEventListener('dispose', dispose); + + // + + computeNode.onInit({ renderer: this }); + } + + nodes.updateForCompute(computeNode); + bindings.updateForCompute(computeNode); + + const computeBindings = bindings.getForCompute(computeNode); + const computePipeline = pipelines.getForCompute(computeNode, computeBindings); + + backend.compute(computeNodes, computeNode, computeBindings, computePipeline); + } + + backend.finishCompute(computeNodes); + + await this.backend.resolveTimestampAsync(computeNodes, 'compute'); + + // + + nodeFrame.renderId = previousRenderId; + } + + async hasFeatureAsync(name) { + if (this._initialized === false) await this.init(); + + return this.backend.hasFeature(name); + } + + hasFeature(name) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', + ); + + return false; + } + + return this.backend.hasFeature(name); + } + + copyFramebufferToTexture(framebufferTexture) { + const renderContext = this._currentRenderContext; + + this._textures.updateTexture(framebufferTexture); + + this.backend.copyFramebufferToTexture(framebufferTexture, renderContext); + } + + copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0) { + this._textures.updateTexture(srcTexture); + this._textures.updateTexture(dstTexture); + + this.backend.copyTextureToTexture(srcTexture, dstTexture, srcRegion, dstPosition, level); + } + + readRenderTargetPixelsAsync(renderTarget, x, y, width, height, index = 0) { + return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); + } + + _projectObject(object, camera, groupOrder, renderList) { + if (object.visible === false) return; + + const visible = object.layers.test(camera.layers); + + if (visible) { + if (object.isGroup) { + groupOrder = object.renderOrder; + } else if (object.isLOD) { + if (object.autoUpdate === true) object.update(camera); + } else if (object.isLight) { + renderList.pushLight(object); + } else if (object.isSprite) { + if (!object.frustumCulled || _frustum.intersectsSprite(object)) { + if (this.sortObjects === true) { + _vector3.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix); + } + + const geometry = object.geometry; + const material = object.material; + + if (material.visible) { + renderList.push(object, geometry, material, groupOrder, _vector3.z, null); + } + } + } else if (object.isLineLoop) { + console.error( + 'THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.', + ); + } else if (object.isMesh || object.isLine || object.isPoints) { + if (!object.frustumCulled || _frustum.intersectsObject(object)) { + const geometry = object.geometry; + const material = object.material; + + if (this.sortObjects === true) { + if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); + + _vector3 + .copy(geometry.boundingSphere.center) + .applyMatrix4(object.matrixWorld) + .applyMatrix4(_projScreenMatrix); + } + + if (Array.isArray(material)) { + const groups = geometry.groups; + + for (let i = 0, l = groups.length; i < l; i++) { + const group = groups[i]; + const groupMaterial = material[group.materialIndex]; + + if (groupMaterial && groupMaterial.visible) { + renderList.push(object, geometry, groupMaterial, groupOrder, _vector3.z, group); + } + } + } else if (material.visible) { + renderList.push(object, geometry, material, groupOrder, _vector3.z, null); + } + } + } + } + + const children = object.children; + + for (let i = 0, l = children.length; i < l; i++) { + this._projectObject(children[i], camera, groupOrder, renderList); + } + } + + _renderObjects(renderList, camera, scene, lightsNode) { + // process renderable objects + + for (let i = 0, il = renderList.length; i < il; i++) { + const renderItem = renderList[i]; + + // @TODO: Add support for multiple materials per object. This will require to extract + // the material from the renderItem object and pass it with its group data to renderObject(). + + const { object, geometry, material, group } = renderItem; + + if (camera.isArrayCamera) { + const cameras = camera.cameras; + + for (let j = 0, jl = cameras.length; j < jl; j++) { + const camera2 = cameras[j]; + + if (object.layers.test(camera2.layers)) { + const vp = camera2.viewport; + const minDepth = vp.minDepth === undefined ? 0 : vp.minDepth; + const maxDepth = vp.maxDepth === undefined ? 1 : vp.maxDepth; + + const viewportValue = this._currentRenderContext.viewportValue; + viewportValue.copy(vp).multiplyScalar(this._pixelRatio).floor(); + viewportValue.minDepth = minDepth; + viewportValue.maxDepth = maxDepth; + + this.backend.updateViewport(this._currentRenderContext); + + this._currentRenderObjectFunction( + object, + scene, + camera2, + geometry, + material, + group, + lightsNode, + ); + } + } + } else { + this._currentRenderObjectFunction(object, scene, camera, geometry, material, group, lightsNode); + } + } + } + + renderObject(object, scene, camera, geometry, material, group, lightsNode) { + let overridePositionNode; + let overrideFragmentNode; + let overrideDepthNode; + + // + + object.onBeforeRender(this, scene, camera, geometry, material, group); + + material.onBeforeRender(this, scene, camera, geometry, material, group); + + // + + if (scene.overrideMaterial !== null) { + const overrideMaterial = scene.overrideMaterial; + + if (material.positionNode && material.positionNode.isNode) { + overridePositionNode = overrideMaterial.positionNode; + overrideMaterial.positionNode = material.positionNode; + } + + if (overrideMaterial.isShadowNodeMaterial) { + overrideMaterial.side = material.shadowSide === null ? material.side : material.shadowSide; + + if (material.depthNode && material.depthNode.isNode) { + overrideDepthNode = overrideMaterial.depthNode; + overrideMaterial.depthNode = material.depthNode; + } + + if (material.shadowNode && material.shadowNode.isNode) { + overrideFragmentNode = overrideMaterial.fragmentNode; + overrideMaterial.fragmentNode = material.shadowNode; + } + + if (this.localClippingEnabled) { + if (material.clipShadows) { + if (overrideMaterial.clippingPlanes !== material.clippingPlanes) { + overrideMaterial.clippingPlanes = material.clippingPlanes; + overrideMaterial.needsUpdate = true; + } + + if (overrideMaterial.clipIntersection !== material.clipIntersection) { + overrideMaterial.clipIntersection = material.clipIntersection; + } + } else if (Array.isArray(overrideMaterial.clippingPlanes)) { + overrideMaterial.clippingPlanes = null; + overrideMaterial.needsUpdate = true; + } + } + } + + material = overrideMaterial; + } + + // + + if (material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false) { + material.side = BackSide; + this._handleObjectFunction(object, material, scene, camera, lightsNode, group, 'backSide'); // create backSide pass id + + material.side = FrontSide; + this._handleObjectFunction(object, material, scene, camera, lightsNode, group); // use default pass id + + material.side = DoubleSide; + } else { + this._handleObjectFunction(object, material, scene, camera, lightsNode, group); + } + + // + + if (overridePositionNode !== undefined) { + scene.overrideMaterial.positionNode = overridePositionNode; + } + + if (overrideDepthNode !== undefined) { + scene.overrideMaterial.depthNode = overrideDepthNode; + } + + if (overrideFragmentNode !== undefined) { + scene.overrideMaterial.fragmentNode = overrideFragmentNode; + } + + // + + object.onAfterRender(this, scene, camera, geometry, material, group); + } + + _renderObjectDirect(object, material, scene, camera, lightsNode, group, passId) { + const renderObject = this._objects.get( + object, + material, + scene, + camera, + lightsNode, + this._currentRenderContext, + passId, + ); + renderObject.drawRange = group || object.geometry.drawRange; + + // + + this._nodes.updateBefore(renderObject); + + // + + object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld); + object.normalMatrix.getNormalMatrix(object.modelViewMatrix); + + // + + this._nodes.updateForRender(renderObject); + this._geometries.updateForRender(renderObject); + this._bindings.updateForRender(renderObject); + this._pipelines.updateForRender(renderObject); + + // + + this.backend.draw(renderObject, this.info); + } + + _createObjectPipeline(object, material, scene, camera, lightsNode, passId) { + const renderObject = this._objects.get( + object, + material, + scene, + camera, + lightsNode, + this._currentRenderContext, + passId, + ); + + // + + this._nodes.updateBefore(renderObject); + + // + + this._nodes.updateForRender(renderObject); + this._geometries.updateForRender(renderObject); + this._bindings.updateForRender(renderObject); + + this._pipelines.getForRender(renderObject, this._compilationPromises); + } + + get compute() { + return this.computeAsync; + } + + get compile() { + return this.compileAsync; + } +} + +export default Renderer; diff --git a/examples-jsm/index.js b/examples-jsm/index.js index 5c6497a74..ad6e990dc 100644 --- a/examples-jsm/index.js +++ b/examples-jsm/index.js @@ -3,7 +3,14 @@ import * as path from 'node:path'; import prettier from 'prettier'; -const files = ['nodes/core/Node', 'nodes/core/NodeBuilder', 'nodes/core/NodeFrame', 'nodes/core/constants']; +const files = [ + 'nodes/core/Node', + 'nodes/core/NodeBuilder', + 'nodes/core/NodeFrame', + 'nodes/core/NodeParser', + 'nodes/core/constants', + 'renderers/common/Renderer', +]; const inDir = '../three.js/examples/jsm'; const outDir = './examples'; From 14872085cfadd8f9f98cb9d6f2fe37d199bb8573 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:46:48 -0400 Subject: [PATCH 12/18] Update and delete examples --- examples-jsm/changes.patch | 33 +- examples-jsm/examples/nodes/core/Node.ts | 411 ------ .../examples/nodes/core/NodeBuilder.ts | 1011 -------------- examples-jsm/examples/nodes/core/NodeFrame.ts | 101 -- .../examples/nodes/core/NodeParser.ts | 7 - examples-jsm/examples/nodes/core/constants.ts | 28 - .../examples/renderers/common/Renderer.ts | 1225 ----------------- 7 files changed, 29 insertions(+), 2787 deletions(-) delete mode 100644 examples-jsm/examples/nodes/core/Node.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeFrame.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeParser.ts delete mode 100644 examples-jsm/examples/nodes/core/constants.ts delete mode 100644 examples-jsm/examples/renderers/common/Renderer.ts diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index 8fc30a3b1..ae4371476 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -151,10 +151,10 @@ index 438c44dd..dd88fd34 100644 if (this !== refNode) { diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts -index ebdc13ff..319b26eb 100644 +index ebdc13ff..179f68ec 100644 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts -@@ -30,6 +30,10 @@ import { +@@ -30,6 +30,11 @@ import { IntType, UnsignedIntType, Float16BufferAttribute, @@ -162,10 +162,21 @@ index ebdc13ff..319b26eb 100644 + Material, + Mesh, + BufferGeometry, ++ Scene, } from 'three'; import { stack } from './StackNode.js'; -@@ -67,10 +71,14 @@ const toFloat = value => { +@@ -39,6 +44,9 @@ import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; + import ChainMap from '../../renderers/common/ChainMap.js'; + + import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; ++import Renderer from '../../renderers/common/Renderer.js'; ++import NodeParser from './NodeParser.js'; ++import Node from './Node.js'; + + const uniformsGroupCache = new ChainMap(); + +@@ -67,10 +75,28 @@ const toFloat = value => { }; class NodeBuilder { @@ -173,8 +184,22 @@ index ebdc13ff..319b26eb 100644 + object: Object3D; + material: Material | Material[]; + geometry: BufferGeometry; ++ renderer: Renderer; ++ parser: NodeParser; ++ scene: Scene | null; ++ ++ nodes: Node[]; ++ updateNodes: Node[]; ++ updateBeforeNodes: Node[]; ++ hashNodes: { [hash: string]: Node }; + -+ constructor(object: Object3D, renderer, parser, scene = null, material: Material | null = null) { ++ constructor( ++ object: Object3D, ++ renderer: Renderer, ++ parser: NodeParser, ++ scene: Scene | null = null, ++ material: Material | null = null, ++ ) { this.object = object; - this.material = material || (object && object.material) || null; - this.geometry = (object && object.geometry) || null; diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts deleted file mode 100644 index 438c44dd1..000000000 --- a/examples-jsm/examples/nodes/core/Node.ts +++ /dev/null @@ -1,411 +0,0 @@ -import { EventDispatcher } from 'three'; -import { NodeUpdateType } from './constants.js'; -import { getNodeChildren, getCacheKey } from './NodeUtils.js'; -import { MathUtils } from 'three'; - -const NodeClasses = new Map(); - -let _nodeId = 0; - -class Node extends EventDispatcher { - constructor(nodeType = null) { - super(); - - this.nodeType = nodeType; - - this.updateType = NodeUpdateType.NONE; - this.updateBeforeType = NodeUpdateType.NONE; - - this.uuid = MathUtils.generateUUID(); - - this.version = 0; - - this._cacheKey = null; - this._cacheKeyVersion = 0; - - this.isNode = true; - - Object.defineProperty(this, 'id', { value: _nodeId++ }); - } - - set needsUpdate(value) { - if (value === true) { - this.version++; - } - } - - get type() { - return this.constructor.type; - } - - onUpdate(callback, updateType) { - this.updateType = updateType; - this.update = callback.bind(this.getSelf()); - - return this; - } - - onFrameUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.FRAME); - } - - onRenderUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.RENDER); - } - - onObjectUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.OBJECT); - } - - onReference(callback) { - this.updateReference = callback.bind(this.getSelf()); - - return this; - } - - getSelf() { - // Returns non-node object. - - return this.self || this; - } - - updateReference(/*state*/) { - return this; - } - - isGlobal(/*builder*/) { - return false; - } - - *getChildren() { - for (const { childNode } of getNodeChildren(this)) { - yield childNode; - } - } - - dispose() { - this.dispatchEvent({ type: 'dispose' }); - } - - traverse(callback) { - callback(this); - - for (const childNode of this.getChildren()) { - childNode.traverse(callback); - } - } - - getCacheKey(force = false) { - force = force || this.version !== this._cacheKeyVersion; - - if (force === true || this._cacheKey === null) { - this._cacheKey = getCacheKey(this, force); - this._cacheKeyVersion = this.version; - } - - return this._cacheKey; - } - - getHash(/*builder*/) { - return this.uuid; - } - - getUpdateType() { - return this.updateType; - } - - getUpdateBeforeType() { - return this.updateBeforeType; - } - - getElementType(builder) { - const type = this.getNodeType(builder); - const elementType = builder.getElementType(type); - - return elementType; - } - - getNodeType(builder) { - const nodeProperties = builder.getNodeProperties(this); - - if (nodeProperties.outputNode) { - return nodeProperties.outputNode.getNodeType(builder); - } - - return this.nodeType; - } - - getShared(builder) { - const hash = this.getHash(builder); - const nodeFromHash = builder.getNodeFromHash(hash); - - return nodeFromHash || this; - } - - setup(builder) { - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of this.getChildren()) { - nodeProperties['_node' + childNode.id] = childNode; - } - - // return a outputNode if exists - return null; - } - - construct(builder) { - // @deprecated, r157 - - console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); - - return this.setup(builder); - } - - increaseUsage(builder) { - const nodeData = builder.getDataFromNode(this); - nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; - - return nodeData.usageCount; - } - - analyze(builder) { - const usageCount = this.increaseUsage(builder); - - if (usageCount === 1) { - // node flow children - - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of Object.values(nodeProperties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } - - generate(builder, output) { - const { outputNode } = builder.getNodeProperties(this); - - if (outputNode && outputNode.isNode === true) { - return outputNode.build(builder, output); - } - } - - updateBefore(/*frame*/) { - console.warn('Abstract function.'); - } - - update(/*frame*/) { - console.warn('Abstract function.'); - } - - build(builder, output = null) { - const refNode = this.getShared(builder); - - if (this !== refNode) { - return refNode.build(builder, output); - } - - builder.addNode(this); - builder.addChain(this); - - /* Build stages expected results: - - "setup" -> Node - - "analyze" -> null - - "generate" -> String - */ - let result = null; - - const buildStage = builder.getBuildStage(); - - if (buildStage === 'setup') { - this.updateReference(builder); - - const properties = builder.getNodeProperties(this); - - if (properties.initialized !== true || builder.context.tempRead === false) { - const stackNodesBeforeSetup = builder.stack.nodes.length; - - properties.initialized = true; - properties.outputNode = this.setup(builder); - - if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { - properties.outputNode = builder.stack; - } - - for (const childNode of Object.values(properties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } else if (buildStage === 'analyze') { - this.analyze(builder); - } else if (buildStage === 'generate') { - const isGenerateOnce = this.generate.length === 1; - - if (isGenerateOnce) { - const type = this.getNodeType(builder); - const nodeData = builder.getDataFromNode(this); - - result = nodeData.snippet; - - if (result === undefined /*|| builder.context.tempRead === false*/) { - result = this.generate(builder) || ''; - - nodeData.snippet = result; - } - - result = builder.format(result, type, output); - } else { - result = this.generate(builder, output) || ''; - } - } - - builder.removeChain(this); - - return result; - } - - getSerializeChildren() { - return getNodeChildren(this); - } - - serialize(json) { - const nodeChildren = this.getSerializeChildren(); - - const inputNodes = {}; - - for (const { property, index, childNode } of nodeChildren) { - if (index !== undefined) { - if (inputNodes[property] === undefined) { - inputNodes[property] = Number.isInteger(index) ? [] : {}; - } - - inputNodes[property][index] = childNode.toJSON(json.meta).uuid; - } else { - inputNodes[property] = childNode.toJSON(json.meta).uuid; - } - } - - if (Object.keys(inputNodes).length > 0) { - json.inputNodes = inputNodes; - } - } - - deserialize(json) { - if (json.inputNodes !== undefined) { - const nodes = json.meta.nodes; - - for (const property in json.inputNodes) { - if (Array.isArray(json.inputNodes[property])) { - const inputArray = []; - - for (const uuid of json.inputNodes[property]) { - inputArray.push(nodes[uuid]); - } - - this[property] = inputArray; - } else if (typeof json.inputNodes[property] === 'object') { - const inputObject = {}; - - for (const subProperty in json.inputNodes[property]) { - const uuid = json.inputNodes[property][subProperty]; - - inputObject[subProperty] = nodes[uuid]; - } - - this[property] = inputObject; - } else { - const uuid = json.inputNodes[property]; - - this[property] = nodes[uuid]; - } - } - } - } - - toJSON(meta) { - const { uuid, type } = this; - const isRoot = meta === undefined || typeof meta === 'string'; - - if (isRoot) { - meta = { - textures: {}, - images: {}, - nodes: {}, - }; - } - - // serialize - - let data = meta.nodes[uuid]; - - if (data === undefined) { - data = { - uuid, - type, - meta, - metadata: { - version: 4.6, - type: 'Node', - generator: 'Node.toJSON', - }, - }; - - if (isRoot !== true) meta.nodes[data.uuid] = data; - - this.serialize(data); - - delete data.meta; - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache(cache) { - const values = []; - - for (const key in cache) { - const data = cache[key]; - delete data.metadata; - values.push(data); - } - - return values; - } - - if (isRoot) { - const textures = extractFromCache(meta.textures); - const images = extractFromCache(meta.images); - const nodes = extractFromCache(meta.nodes); - - if (textures.length > 0) data.textures = textures; - if (images.length > 0) data.images = images; - if (nodes.length > 0) data.nodes = nodes; - } - - return data; - } -} - -export default Node; - -export function addNodeClass(type, nodeClass) { - if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); - if (NodeClasses.has(type)) { - console.warn(`Redefinition of node class ${type}`); - return; - } - - NodeClasses.set(type, nodeClass); - nodeClass.type = type; -} - -export function createNodeFromType(type) { - const Class = NodeClasses.get(type); - - if (Class !== undefined) { - return new Class(); - } -} diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts deleted file mode 100644 index ebdc13ff1..000000000 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ /dev/null @@ -1,1011 +0,0 @@ -import NodeUniform from './NodeUniform.js'; -import NodeAttribute from './NodeAttribute.js'; -import NodeVarying from './NodeVarying.js'; -import NodeVar from './NodeVar.js'; -import NodeCode from './NodeCode.js'; -import NodeKeywords from './NodeKeywords.js'; -import NodeCache from './NodeCache.js'; -import ParameterNode from './ParameterNode.js'; -import FunctionNode from '../code/FunctionNode.js'; -import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; -import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; - -import { - FloatNodeUniform, - Vector2NodeUniform, - Vector3NodeUniform, - Vector4NodeUniform, - ColorNodeUniform, - Matrix3NodeUniform, - Matrix4NodeUniform, -} from '../../renderers/common/nodes/NodeUniform.js'; - -import { - REVISION, - RenderTarget, - Color, - Vector2, - Vector3, - Vector4, - IntType, - UnsignedIntType, - Float16BufferAttribute, -} from 'three'; - -import { stack } from './StackNode.js'; -import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; - -import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; -import ChainMap from '../../renderers/common/ChainMap.js'; - -import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; - -const uniformsGroupCache = new ChainMap(); - -const typeFromLength = new Map([ - [2, 'vec2'], - [3, 'vec3'], - [4, 'vec4'], - [9, 'mat3'], - [16, 'mat4'], -]); - -const typeFromArray = new Map([ - [Int8Array, 'int'], - [Int16Array, 'int'], - [Int32Array, 'int'], - [Uint8Array, 'uint'], - [Uint16Array, 'uint'], - [Uint32Array, 'uint'], - [Float32Array, 'float'], -]); - -const toFloat = value => { - value = Number(value); - - return value + (value % 1 ? '' : '.0'); -}; - -class NodeBuilder { - constructor(object, renderer, parser, scene = null, material = null) { - this.object = object; - this.material = material || (object && object.material) || null; - this.geometry = (object && object.geometry) || null; - this.renderer = renderer; - this.parser = parser; - this.scene = scene; - - this.nodes = []; - this.updateNodes = []; - this.updateBeforeNodes = []; - this.hashNodes = {}; - - this.lightsNode = null; - this.environmentNode = null; - this.fogNode = null; - - this.clippingContext = null; - - this.vertexShader = null; - this.fragmentShader = null; - this.computeShader = null; - - this.flowNodes = { vertex: [], fragment: [], compute: [] }; - this.flowCode = { vertex: '', fragment: '', compute: [] }; - this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; - this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; - this.bindings = { vertex: [], fragment: [], compute: [] }; - this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; - this.bindingsArray = null; - this.attributes = []; - this.bufferAttributes = []; - this.varyings = []; - this.codes = {}; - this.vars = {}; - this.flow = { code: '' }; - this.chaining = []; - this.stack = stack(); - this.stacks = []; - this.tab = '\t'; - - this.currentFunctionNode = null; - - this.context = { - keywords: new NodeKeywords(), - material: this.material, - }; - - this.cache = new NodeCache(); - this.globalCache = this.cache; - - this.flowsData = new WeakMap(); - - this.shaderStage = null; - this.buildStage = null; - } - - createRenderTarget(width, height, options) { - return new RenderTarget(width, height, options); - } - - createCubeRenderTarget(size, options) { - return new CubeRenderTarget(size, options); - } - - createPMREMGenerator() { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return new PMREMGenerator(this.renderer); - } - - includes(node) { - return this.nodes.includes(node); - } - - _getSharedBindings(bindings) { - const shared = []; - - for (const binding of bindings) { - if (binding.shared === true) { - // nodes is the chainmap key - const nodes = binding.getNodes(); - - let sharedBinding = uniformsGroupCache.get(nodes); - - if (sharedBinding === undefined) { - uniformsGroupCache.set(nodes, binding); - - sharedBinding = binding; - } - - shared.push(sharedBinding); - } else { - shared.push(binding); - } - } - - return shared; - } - - getBindings() { - let bindingsArray = this.bindingsArray; - - if (bindingsArray === null) { - const bindings = this.bindings; - - this.bindingsArray = bindingsArray = this._getSharedBindings( - this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, - ); - } - - return bindingsArray; - } - - setHashNode(node, hash) { - this.hashNodes[hash] = node; - } - - addNode(node) { - if (this.nodes.includes(node) === false) { - this.nodes.push(node); - - this.setHashNode(node, node.getHash(this)); - } - } - - buildUpdateNodes() { - for (const node of this.nodes) { - const updateType = node.getUpdateType(); - const updateBeforeType = node.getUpdateBeforeType(); - - if (updateType !== NodeUpdateType.NONE) { - this.updateNodes.push(node.getSelf()); - } - - if (updateBeforeType !== NodeUpdateType.NONE) { - this.updateBeforeNodes.push(node); - } - } - } - - get currentNode() { - return this.chaining[this.chaining.length - 1]; - } - - addChain(node) { - /* - if ( this.chaining.indexOf( node ) !== - 1 ) { - - console.warn( 'Recursive node: ', node ); - - } - */ - - this.chaining.push(node); - } - - removeChain(node) { - const lastChain = this.chaining.pop(); - - if (lastChain !== node) { - throw new Error('NodeBuilder: Invalid node chaining!'); - } - } - - getMethod(method) { - return method; - } - - getNodeFromHash(hash) { - return this.hashNodes[hash]; - } - - addFlow(shaderStage, node) { - this.flowNodes[shaderStage].push(node); - - return node; - } - - setContext(context) { - this.context = context; - } - - getContext() { - return this.context; - } - - setCache(cache) { - this.cache = cache; - } - - getCache() { - return this.cache; - } - - isAvailable(/*name*/) { - return false; - } - - getVertexIndex() { - console.warn('Abstract function.'); - } - - getInstanceIndex() { - console.warn('Abstract function.'); - } - - getFrontFacing() { - console.warn('Abstract function.'); - } - - getFragCoord() { - console.warn('Abstract function.'); - } - - isFlipY() { - return false; - } - - generateTexture(/* texture, textureProperty, uvSnippet */) { - console.warn('Abstract function.'); - } - - generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { - console.warn('Abstract function.'); - } - - generateConst(type, value = null) { - if (value === null) { - if (type === 'float' || type === 'int' || type === 'uint') value = 0; - else if (type === 'bool') value = false; - else if (type === 'color') value = new Color(); - else if (type === 'vec2') value = new Vector2(); - else if (type === 'vec3') value = new Vector3(); - else if (type === 'vec4') value = new Vector4(); - } - - if (type === 'float') return toFloat(value); - if (type === 'int') return `${Math.round(value)}`; - if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; - if (type === 'bool') return value ? 'true' : 'false'; - if (type === 'color') - return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; - - const typeLength = this.getTypeLength(type); - - const componentType = this.getComponentType(type); - - const generateConst = value => this.generateConst(componentType, value); - - if (typeLength === 2) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; - } else if (typeLength === 3) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; - } else if (typeLength === 4) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; - } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { - return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; - } else if (typeLength > 4) { - return `${this.getType(type)}()`; - } - - throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); - } - - getType(type) { - if (type === 'color') return 'vec3'; - - return type; - } - - generateMethod(method) { - return method; - } - - hasGeometryAttribute(name) { - return this.geometry && this.geometry.getAttribute(name) !== undefined; - } - - getAttribute(name, type) { - const attributes = this.attributes; - - // find attribute - - for (const attribute of attributes) { - if (attribute.name === name) { - return attribute; - } - } - - // create a new if no exist - - const attribute = new NodeAttribute(name, type); - - attributes.push(attribute); - - return attribute; - } - - getPropertyName(node /*, shaderStage*/) { - return node.name; - } - - isVector(type) { - return /vec\d/.test(type); - } - - isMatrix(type) { - return /mat\d/.test(type); - } - - isReference(type) { - return ( - type === 'void' || - type === 'property' || - type === 'sampler' || - type === 'texture' || - type === 'cubeTexture' || - type === 'storageTexture' || - type === 'texture3D' - ); - } - - needsColorSpaceToLinear(/*texture*/) { - return false; - } - - getComponentTypeFromTexture(texture) { - const type = texture.type; - - if (texture.isDataTexture) { - if (type === IntType) return 'int'; - if (type === UnsignedIntType) return 'uint'; - } - - return 'float'; - } - - getElementType(type) { - if (type === 'mat2') return 'vec2'; - if (type === 'mat3') return 'vec3'; - if (type === 'mat4') return 'vec4'; - - return this.getComponentType(type); - } - - getComponentType(type) { - type = this.getVectorType(type); - - if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; - - const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); - - if (componentType === null) return null; - - if (componentType[1] === 'b') return 'bool'; - if (componentType[1] === 'i') return 'int'; - if (componentType[1] === 'u') return 'uint'; - - return 'float'; - } - - getVectorType(type) { - if (type === 'color') return 'vec3'; - if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') - return 'vec4'; - - return type; - } - - getTypeFromLength(length, componentType = 'float') { - if (length === 1) return componentType; - - const baseType = typeFromLength.get(length); - const prefix = componentType === 'float' ? '' : componentType[0]; - - return prefix + baseType; - } - - getTypeFromArray(array) { - return typeFromArray.get(array.constructor); - } - - getTypeFromAttribute(attribute) { - let dataAttribute = attribute; - - if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; - - const array = dataAttribute.array; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; - - let arrayType; - - if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { - arrayType = this.getTypeFromArray(array); - } - - return this.getTypeFromLength(itemSize, arrayType); - } - - getTypeLength(type) { - const vecType = this.getVectorType(type); - const vecNum = /vec([2-4])/.exec(vecType); - - if (vecNum !== null) return Number(vecNum[1]); - if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; - if (/mat2/.test(type) === true) return 4; - if (/mat3/.test(type) === true) return 9; - if (/mat4/.test(type) === true) return 16; - - return 0; - } - - getVectorFromMatrix(type) { - return type.replace('mat', 'vec'); - } - - changeComponentType(type, newComponentType) { - return this.getTypeFromLength(this.getTypeLength(type), newComponentType); - } - - getIntegerType(type) { - const componentType = this.getComponentType(type); - - if (componentType === 'int' || componentType === 'uint') return type; - - return this.changeComponentType(type, 'int'); - } - - addStack() { - this.stack = stack(this.stack); - - this.stacks.push(getCurrentStack() || this.stack); - setCurrentStack(this.stack); - - return this.stack; - } - - removeStack() { - const lastStack = this.stack; - this.stack = lastStack.parent; - - setCurrentStack(this.stacks.pop()); - - return lastStack; - } - - getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { - cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; - - let nodeData = cache.getNodeData(node); - - if (nodeData === undefined) { - nodeData = {}; - - cache.setNodeData(node, nodeData); - } - - if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; - - return nodeData[shaderStage]; - } - - getNodeProperties(node, shaderStage = 'any') { - const nodeData = this.getDataFromNode(node, shaderStage); - - return nodeData.properties || (nodeData.properties = { outputNode: null }); - } - - getBufferAttributeFromNode(node, type) { - const nodeData = this.getDataFromNode(node); - - let bufferAttribute = nodeData.bufferAttribute; - - if (bufferAttribute === undefined) { - const index = this.uniforms.index++; - - bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); - - this.bufferAttributes.push(bufferAttribute); - - nodeData.bufferAttribute = bufferAttribute; - } - - return bufferAttribute; - } - - getStructTypeFromNode(node, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - if (nodeData.structType === undefined) { - const index = this.structs.index++; - - node.name = `StructType${index}`; - this.structs[shaderStage].push(node); - - nodeData.structType = node; - } - - return node; - } - - getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { - const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); - - let nodeUniform = nodeData.uniform; - - if (nodeUniform === undefined) { - const index = this.uniforms.index++; - - nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); - - this.uniforms[shaderStage].push(nodeUniform); - - nodeData.uniform = nodeUniform; - } - - return nodeUniform; - } - - getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - let nodeVar = nodeData.variable; - - if (nodeVar === undefined) { - const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); - - if (name === null) name = 'nodeVar' + vars.length; - - nodeVar = new NodeVar(name, type); - - vars.push(nodeVar); - - nodeData.variable = nodeVar; - } - - return nodeVar; - } - - getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { - const nodeData = this.getDataFromNode(node, 'any'); - - let nodeVarying = nodeData.varying; - - if (nodeVarying === undefined) { - const varyings = this.varyings; - const index = varyings.length; - - if (name === null) name = 'nodeVarying' + index; - - nodeVarying = new NodeVarying(name, type); - - varyings.push(nodeVarying); - - nodeData.varying = nodeVarying; - } - - return nodeVarying; - } - - getCodeFromNode(node, type, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node); - - let nodeCode = nodeData.code; - - if (nodeCode === undefined) { - const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); - const index = codes.length; - - nodeCode = new NodeCode('nodeCode' + index, type); - - codes.push(nodeCode); - - nodeData.code = nodeCode; - } - - return nodeCode; - } - - addLineFlowCode(code) { - if (code === '') return this; - - code = this.tab + code; - - if (!/;\s*$/.test(code)) { - code = code + ';\n'; - } - - this.flow.code += code; - - return this; - } - - addFlowCode(code) { - this.flow.code += code; - - return this; - } - - addFlowTab() { - this.tab += '\t'; - - return this; - } - - removeFlowTab() { - this.tab = this.tab.slice(0, -1); - - return this; - } - - getFlowData(node /*, shaderStage*/) { - return this.flowsData.get(node); - } - - flowNode(node) { - const output = node.getNodeType(this); - - const flowData = this.flowChildNode(node, output); - - this.flowsData.set(node, flowData); - - return flowData; - } - - buildFunctionNode(shaderNode) { - const fn = new FunctionNode(); - - const previous = this.currentFunctionNode; - - this.currentFunctionNode = fn; - - fn.code = this.buildFunctionCode(shaderNode); - - this.currentFunctionNode = previous; - - return fn; - } - - flowShaderNode(shaderNode) { - const layout = shaderNode.layout; - - let inputs; - - if (shaderNode.isArrayInput) { - inputs = []; - - for (const input of layout.inputs) { - inputs.push(new ParameterNode(input.type, input.name)); - } - } else { - inputs = {}; - - for (const input of layout.inputs) { - inputs[input.name] = new ParameterNode(input.type, input.name); - } - } - - // - - shaderNode.layout = null; - - const callNode = shaderNode.call(inputs); - const flowData = this.flowStagesNode(callNode, layout.type); - - shaderNode.layout = layout; - - return flowData; - } - - flowStagesNode(node, output = null) { - const previousFlow = this.flow; - const previousVars = this.vars; - const previousBuildStage = this.buildStage; - - const flow = { - code: '', - }; - - this.flow = flow; - this.vars = {}; - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - flow.result = node.build(this, output); - } - - flow.vars = this.getVars(this.shaderStage); - - this.flow = previousFlow; - this.vars = previousVars; - this.setBuildStage(previousBuildStage); - - return flow; - } - - getFunctionOperator() { - return null; - } - - flowChildNode(node, output = null) { - const previousFlow = this.flow; - - const flow = { - code: '', - }; - - this.flow = flow; - - flow.result = node.build(this, output); - - this.flow = previousFlow; - - return flow; - } - - flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { - const previousShaderStage = this.shaderStage; - - this.setShaderStage(shaderStage); - - const flowData = this.flowChildNode(node, output); - - if (propertyName !== null) { - flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; - } - - this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; - - this.setShaderStage(previousShaderStage); - - return flowData; - } - - getAttributesArray() { - return this.attributes.concat(this.bufferAttributes); - } - - getAttributes(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVaryings(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVar(type, name) { - return `${this.getType(type)} ${name}`; - } - - getVars(shaderStage) { - let snippet = ''; - - const vars = this.vars[shaderStage]; - - if (vars !== undefined) { - for (const variable of vars) { - snippet += `${this.getVar(variable.type, variable.name)}; `; - } - } - - return snippet; - } - - getUniforms(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getCodes(shaderStage) { - const codes = this.codes[shaderStage]; - - let code = ''; - - if (codes !== undefined) { - for (const nodeCode of codes) { - code += nodeCode.code + '\n'; - } - } - - return code; - } - - getHash() { - return this.vertexShader + this.fragmentShader + this.computeShader; - } - - setShaderStage(shaderStage) { - this.shaderStage = shaderStage; - } - - getShaderStage() { - return this.shaderStage; - } - - setBuildStage(buildStage) { - this.buildStage = buildStage; - } - - getBuildStage() { - return this.buildStage; - } - - buildCode() { - console.warn('Abstract function.'); - } - - build() { - const { object, material } = this; - - if (material !== null) { - NodeMaterial.fromMaterial(material).build(this); - } else { - this.addFlow('compute', object); - } - - // setup() -> stage 1: create possible new nodes and returns an output reference node - // analyze() -> stage 2: analyze nodes to possible optimization and validation - // generate() -> stage 3: generate shader - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - if (this.context.vertex && this.context.vertex.isNode) { - this.flowNodeFromShaderStage('vertex', this.context.vertex); - } - - for (const shaderStage of shaderStages) { - this.setShaderStage(shaderStage); - - const flowNodes = this.flowNodes[shaderStage]; - - for (const node of flowNodes) { - if (buildStage === 'generate') { - this.flowNode(node); - } else { - node.build(this); - } - } - } - } - - this.setBuildStage(null); - this.setShaderStage(null); - - // stage 4: build code for a specific output - - this.buildCode(); - this.buildUpdateNodes(); - - return this; - } - - getNodeUniform(uniformNode, type) { - if (type === 'float') return new FloatNodeUniform(uniformNode); - if (type === 'vec2') return new Vector2NodeUniform(uniformNode); - if (type === 'vec3') return new Vector3NodeUniform(uniformNode); - if (type === 'vec4') return new Vector4NodeUniform(uniformNode); - if (type === 'color') return new ColorNodeUniform(uniformNode); - if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); - if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); - - throw new Error(`Uniform "${type}" not declared.`); - } - - createNodeMaterial(type = 'NodeMaterial') { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return createNodeMaterialFromType(type); - } - - format(snippet, fromType, toType) { - fromType = this.getVectorType(fromType); - toType = this.getVectorType(toType); - - if (fromType === toType || toType === null || this.isReference(toType)) { - return snippet; - } - - const fromTypeLength = this.getTypeLength(fromType); - const toTypeLength = this.getTypeLength(toType); - - if (fromTypeLength > 4) { - // fromType is matrix-like - - // @TODO: ignore for now - - return snippet; - } - - if (toTypeLength > 4 || toTypeLength === 0) { - // toType is matrix-like or unknown - - // @TODO: ignore for now - - return snippet; - } - - if (fromTypeLength === toTypeLength) { - return `${this.getType(toType)}( ${snippet} )`; - } - - if (fromTypeLength > toTypeLength) { - return this.format( - `${snippet}.${'xyz'.slice(0, toTypeLength)}`, - this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), - toType, - ); - } - - if (toTypeLength === 4 && fromTypeLength > 1) { - // toType is vec4-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; - } - - if (fromTypeLength === 2) { - // fromType is vec2-like and toType is vec3-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; - } - - if (fromTypeLength === 1 && toTypeLength > 1 && fromType[0] !== toType[0]) { - // fromType is float-like - - // convert a number value to vector type, e.g: - // vec3( 1u ) -> vec3( float( 1u ) ) - - snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; - } - - return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like - } - - getSignature() { - return `// Three.js r${REVISION} - NodeMaterial System\n`; - } -} - -export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts deleted file mode 100644 index b8e8d37b6..000000000 --- a/examples-jsm/examples/nodes/core/NodeFrame.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { NodeUpdateType } from './constants.js'; - -class NodeFrame { - constructor() { - this.time = 0; - this.deltaTime = 0; - - this.frameId = 0; - this.renderId = 0; - - this.startTime = null; - - this.updateMap = new WeakMap(); - this.updateBeforeMap = new WeakMap(); - - this.renderer = null; - this.material = null; - this.camera = null; - this.object = null; - this.scene = null; - } - - _getMaps(referenceMap, nodeRef) { - let maps = referenceMap.get(nodeRef); - - if (maps === undefined) { - maps = { - renderMap: new WeakMap(), - frameMap: new WeakMap(), - }; - - referenceMap.set(nodeRef, maps); - } - - return maps; - } - - updateBeforeNode(node) { - const updateType = node.getUpdateBeforeType(); - const reference = node.updateReference(this); - - if (updateType === NodeUpdateType.FRAME) { - const { frameMap } = this._getMaps(this.updateBeforeMap, reference); - - if (frameMap.get(reference) !== this.frameId) { - if (node.updateBefore(this) !== false) { - frameMap.set(reference, this.frameId); - } - } - } else if (updateType === NodeUpdateType.RENDER) { - const { renderMap } = this._getMaps(this.updateBeforeMap, reference); - - if (renderMap.get(reference) !== this.renderId) { - if (node.updateBefore(this) !== false) { - renderMap.set(reference, this.renderId); - } - } - } else if (updateType === NodeUpdateType.OBJECT) { - node.updateBefore(this); - } - } - - updateNode(node) { - const updateType = node.getUpdateType(); - const reference = node.updateReference(this); - - if (updateType === NodeUpdateType.FRAME) { - const { frameMap } = this._getMaps(this.updateMap, reference); - - if (frameMap.get(reference) !== this.frameId) { - if (node.update(this) !== false) { - frameMap.set(reference, this.frameId); - } - } - } else if (updateType === NodeUpdateType.RENDER) { - const { renderMap } = this._getMaps(this.updateMap, reference); - - if (renderMap.get(reference) !== this.renderId) { - if (node.update(this) !== false) { - renderMap.set(reference, this.renderId); - } - } - } else if (updateType === NodeUpdateType.OBJECT) { - node.update(this); - } - } - - update() { - this.frameId++; - - if (this.lastTime === undefined) this.lastTime = performance.now(); - - this.deltaTime = (performance.now() - this.lastTime) / 1000; - - this.lastTime = performance.now(); - - this.time += this.deltaTime; - } -} - -export default NodeFrame; diff --git a/examples-jsm/examples/nodes/core/NodeParser.ts b/examples-jsm/examples/nodes/core/NodeParser.ts deleted file mode 100644 index 9849452f1..000000000 --- a/examples-jsm/examples/nodes/core/NodeParser.ts +++ /dev/null @@ -1,7 +0,0 @@ -class NodeParser { - parseFunction(/*source*/) { - console.warn('Abstract function.'); - } -} - -export default NodeParser; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts deleted file mode 100644 index 3b01a9a6d..000000000 --- a/examples-jsm/examples/nodes/core/constants.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const NodeShaderStage = { - VERTEX: 'vertex', - FRAGMENT: 'fragment', -}; - -export const NodeUpdateType = { - NONE: 'none', - FRAME: 'frame', - RENDER: 'render', - OBJECT: 'object', -}; - -export const NodeType = { - BOOLEAN: 'bool', - INTEGER: 'int', - FLOAT: 'float', - VECTOR2: 'vec2', - VECTOR3: 'vec3', - VECTOR4: 'vec4', - MATRIX2: 'mat2', - MATRIX3: 'mat3', - MATRIX4: 'mat4', -}; - -export const defaultShaderStages = ['fragment', 'vertex']; -export const defaultBuildStages = ['setup', 'analyze', 'generate']; -export const shaderStages = [...defaultShaderStages, 'compute']; -export const vectorComponents = ['x', 'y', 'z', 'w']; diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts deleted file mode 100644 index 85713cfe6..000000000 --- a/examples-jsm/examples/renderers/common/Renderer.ts +++ /dev/null @@ -1,1225 +0,0 @@ -import Animation from './Animation.js'; -import RenderObjects from './RenderObjects.js'; -import Attributes from './Attributes.js'; -import Geometries from './Geometries.js'; -import Info from './Info.js'; -import Pipelines from './Pipelines.js'; -import Bindings from './Bindings.js'; -import RenderLists from './RenderLists.js'; -import RenderContexts from './RenderContexts.js'; -import Textures from './Textures.js'; -import Background from './Background.js'; -import Nodes from './nodes/Nodes.js'; -import Color4 from './Color4.js'; -import ClippingContext from './ClippingContext.js'; -import { - Scene, - Frustum, - Matrix4, - Vector2, - Vector3, - Vector4, - DoubleSide, - BackSide, - FrontSide, - SRGBColorSpace, - NoColorSpace, - NoToneMapping, - LinearFilter, - LinearSRGBColorSpace, - RenderTarget, - HalfFloatType, - RGBAFormat, -} from 'three'; -import { NodeMaterial } from '../../nodes/Nodes.js'; -import QuadMesh from '../../objects/QuadMesh.js'; - -const _scene = new Scene(); -const _drawingBufferSize = new Vector2(); -const _screen = new Vector4(); -const _frustum = new Frustum(); -const _projScreenMatrix = new Matrix4(); -const _vector3 = new Vector3(); -const _quad = new QuadMesh(new NodeMaterial()); - -class Renderer { - constructor(backend, parameters = {}) { - this.isRenderer = true; - - // - - const { logarithmicDepthBuffer = false, alpha = true } = parameters; - - // public - - this.domElement = backend.getDomElement(); - - this.backend = backend; - - this.autoClear = true; - this.autoClearColor = true; - this.autoClearDepth = true; - this.autoClearStencil = true; - - this.alpha = alpha; - - this.logarithmicDepthBuffer = logarithmicDepthBuffer; - - this.outputColorSpace = SRGBColorSpace; - - this.toneMapping = NoToneMapping; - this.toneMappingExposure = 1.0; - - this.sortObjects = true; - - this.depth = true; - this.stencil = false; - - this.clippingPlanes = []; - - this.info = new Info(); - - // nodes - - this.toneMappingNode = null; - - // internals - - this._pixelRatio = 1; - this._width = this.domElement.width; - this._height = this.domElement.height; - - this._viewport = new Vector4(0, 0, this._width, this._height); - this._scissor = new Vector4(0, 0, this._width, this._height); - this._scissorTest = false; - - this._attributes = null; - this._geometries = null; - this._nodes = null; - this._animation = null; - this._bindings = null; - this._objects = null; - this._pipelines = null; - this._renderLists = null; - this._renderContexts = null; - this._textures = null; - this._background = null; - - this._currentRenderContext = null; - - this._opaqueSort = null; - this._transparentSort = null; - - this._frameBufferTarget = null; - - const alphaClear = this.alpha === true ? 0 : 1; - - this._clearColor = new Color4(0, 0, 0, alphaClear); - this._clearDepth = 1; - this._clearStencil = 0; - - this._renderTarget = null; - this._activeCubeFace = 0; - this._activeMipmapLevel = 0; - - this._renderObjectFunction = null; - this._currentRenderObjectFunction = null; - - this._handleObjectFunction = this._renderObjectDirect; - - this._initialized = false; - this._initPromise = null; - - this._compilationPromises = null; - - // backwards compatibility - - this.shadowMap = { - enabled: false, - type: null, - }; - - this.xr = { - enabled: false, - }; - } - - async init() { - if (this._initialized) { - throw new Error('Renderer: Backend has already been initialized.'); - } - - if (this._initPromise !== null) { - return this._initPromise; - } - - this._initPromise = new Promise(async (resolve, reject) => { - const backend = this.backend; - - try { - await backend.init(this); - } catch (error) { - reject(error); - return; - } - - this._nodes = new Nodes(this, backend); - this._animation = new Animation(this._nodes, this.info); - this._attributes = new Attributes(backend); - this._background = new Background(this, this._nodes); - this._geometries = new Geometries(this._attributes, this.info); - this._textures = new Textures(this, backend, this.info); - this._pipelines = new Pipelines(backend, this._nodes); - this._bindings = new Bindings( - backend, - this._nodes, - this._textures, - this._attributes, - this._pipelines, - this.info, - ); - this._objects = new RenderObjects( - this, - this._nodes, - this._geometries, - this._pipelines, - this._bindings, - this.info, - ); - this._renderLists = new RenderLists(); - this._renderContexts = new RenderContexts(); - - // - - this._initialized = true; - - resolve(); - }); - - return this._initPromise; - } - - get coordinateSystem() { - return this.backend.coordinateSystem; - } - - async compileAsync(scene, camera, targetScene = null) { - if (this._initialized === false) await this.init(); - - // preserve render tree - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - const previousRenderContext = this._currentRenderContext; - const previousRenderObjectFunction = this._currentRenderObjectFunction; - const previousCompilationPromises = this._compilationPromises; - - // - - const sceneRef = scene.isScene === true ? scene : _scene; - - if (targetScene === null) targetScene = scene; - - const renderTarget = this._renderTarget; - const renderContext = this._renderContexts.get(targetScene, camera, renderTarget); - const activeMipmapLevel = this._activeMipmapLevel; - - const compilationPromises = []; - - this._currentRenderContext = renderContext; - this._currentRenderObjectFunction = this.renderObject; - - this._handleObjectFunction = this._createObjectPipeline; - - this._compilationPromises = compilationPromises; - - nodeFrame.renderId++; - - // - - nodeFrame.update(); - - // - - renderContext.depth = this.depth; - renderContext.stencil = this.stencil; - - if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); - renderContext.clippingContext.updateGlobal(this, camera); - - // - - sceneRef.onBeforeRender(this, scene, camera, renderTarget); - - // - - const renderList = this._renderLists.get(scene, camera); - renderList.begin(); - - this._projectObject(scene, camera, 0, renderList); - - // include lights from target scene - if (targetScene !== scene) { - targetScene.traverseVisible(function (object) { - if (object.isLight && object.layers.test(camera.layers)) { - renderList.pushLight(object); - } - }); - } - - renderList.finish(); - - // - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); - - const renderTargetData = this._textures.get(renderTarget); - - renderContext.textures = renderTargetData.textures; - renderContext.depthTexture = renderTargetData.depthTexture; - } else { - renderContext.textures = null; - renderContext.depthTexture = null; - } - - // - - this._nodes.updateScene(sceneRef); - - // - - this._background.update(sceneRef, renderList, renderContext); - - // process render lists - - const opaqueObjects = renderList.opaque; - const transparentObjects = renderList.transparent; - const lightsNode = renderList.lightsNode; - - if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); - if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); - - // restore render tree - - nodeFrame.renderId = previousRenderId; - - this._currentRenderContext = previousRenderContext; - this._currentRenderObjectFunction = previousRenderObjectFunction; - this._compilationPromises = previousCompilationPromises; - - this._handleObjectFunction = this._renderObjectDirect; - - // wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete - - await Promise.all(compilationPromises); - } - - async renderAsync(scene, camera) { - if (this._initialized === false) await this.init(); - - const renderContext = this._renderScene(scene, camera); - - await this.backend.resolveTimestampAsync(renderContext, 'render'); - } - - render(scene, camera) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', - ); - - return this.renderAsync(scene, camera); - } - - this._renderScene(scene, camera); - } - - _getFrameBufferTarget() { - const { currentColorSpace } = this; - - const useToneMapping = - this._renderTarget === null && (this.toneMapping !== NoToneMapping || this.toneMappingNode !== null); - const useColorSpace = currentColorSpace !== LinearSRGBColorSpace && currentColorSpace !== NoColorSpace; - - if (useToneMapping === false && useColorSpace === false) return null; - - const { width, height } = this.getDrawingBufferSize(_drawingBufferSize); - const { depth, stencil } = this; - - let frameBufferTarget = this._frameBufferTarget; - - if (frameBufferTarget === null) { - frameBufferTarget = new RenderTarget(width, height, { - depthBuffer: depth, - stencilBuffer: stencil, - type: HalfFloatType, // FloatType - format: RGBAFormat, - colorSpace: LinearSRGBColorSpace, - generateMipmaps: false, - minFilter: LinearFilter, - magFilter: LinearFilter, - samples: this.backend.parameters.antialias ? 4 : 0, - }); - - frameBufferTarget.isPostProcessingRenderTarget = true; - - this._frameBufferTarget = frameBufferTarget; - } - - frameBufferTarget.depthBuffer = depth; - frameBufferTarget.stencilBuffer = stencil; - frameBufferTarget.setSize(width, height); - frameBufferTarget.viewport.copy(this._viewport); - frameBufferTarget.scissor.copy(this._scissor); - frameBufferTarget.viewport.multiplyScalar(this._pixelRatio); - frameBufferTarget.scissor.multiplyScalar(this._pixelRatio); - frameBufferTarget.scissorTest = this._scissorTest; - - return frameBufferTarget; - } - - _renderScene(scene, camera, useFrameBufferTarget = true) { - const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; - - // preserve render tree - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - const previousRenderContext = this._currentRenderContext; - const previousRenderObjectFunction = this._currentRenderObjectFunction; - - // - - const sceneRef = scene.isScene === true ? scene : _scene; - - const outputRenderTarget = this._renderTarget; - - const activeCubeFace = this._activeCubeFace; - const activeMipmapLevel = this._activeMipmapLevel; - - // - - let renderTarget; - - if (frameBufferTarget !== null) { - renderTarget = frameBufferTarget; - - this.setRenderTarget(renderTarget); - } else { - renderTarget = outputRenderTarget; - } - - // - - const renderContext = this._renderContexts.get(scene, camera, renderTarget); - - this._currentRenderContext = renderContext; - this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; - - // - - this.info.calls++; - this.info.render.calls++; - - nodeFrame.renderId = this.info.calls; - - // - - const coordinateSystem = this.coordinateSystem; - - if (camera.coordinateSystem !== coordinateSystem) { - camera.coordinateSystem = coordinateSystem; - - camera.updateProjectionMatrix(); - } - - // - - if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld(); - - if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld(); - - // - - let viewport = this._viewport; - let scissor = this._scissor; - let pixelRatio = this._pixelRatio; - - if (renderTarget !== null) { - viewport = renderTarget.viewport; - scissor = renderTarget.scissor; - pixelRatio = 1; - } - - this.getDrawingBufferSize(_drawingBufferSize); - - _screen.set(0, 0, _drawingBufferSize.width, _drawingBufferSize.height); - - const minDepth = viewport.minDepth === undefined ? 0 : viewport.minDepth; - const maxDepth = viewport.maxDepth === undefined ? 1 : viewport.maxDepth; - - renderContext.viewportValue.copy(viewport).multiplyScalar(pixelRatio).floor(); - renderContext.viewportValue.width >>= activeMipmapLevel; - renderContext.viewportValue.height >>= activeMipmapLevel; - renderContext.viewportValue.minDepth = minDepth; - renderContext.viewportValue.maxDepth = maxDepth; - renderContext.viewport = renderContext.viewportValue.equals(_screen) === false; - - renderContext.scissorValue.copy(scissor).multiplyScalar(pixelRatio).floor(); - renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals(_screen) === false; - renderContext.scissorValue.width >>= activeMipmapLevel; - renderContext.scissorValue.height >>= activeMipmapLevel; - - if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); - renderContext.clippingContext.updateGlobal(this, camera); - - // - - sceneRef.onBeforeRender(this, scene, camera, renderTarget); - - // - - _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - _frustum.setFromProjectionMatrix(_projScreenMatrix, coordinateSystem); - - const renderList = this._renderLists.get(scene, camera); - renderList.begin(); - - this._projectObject(scene, camera, 0, renderList); - - renderList.finish(); - - if (this.sortObjects === true) { - renderList.sort(this._opaqueSort, this._transparentSort); - } - - // - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); - - const renderTargetData = this._textures.get(renderTarget); - - renderContext.textures = renderTargetData.textures; - renderContext.depthTexture = renderTargetData.depthTexture; - renderContext.width = renderTargetData.width; - renderContext.height = renderTargetData.height; - renderContext.renderTarget = renderTarget; - renderContext.depth = renderTarget.depthBuffer; - renderContext.stencil = renderTarget.stencilBuffer; - } else { - renderContext.textures = null; - renderContext.depthTexture = null; - renderContext.width = this.domElement.width; - renderContext.height = this.domElement.height; - renderContext.depth = this.depth; - renderContext.stencil = this.stencil; - } - - renderContext.width >>= activeMipmapLevel; - renderContext.height >>= activeMipmapLevel; - renderContext.activeCubeFace = activeCubeFace; - renderContext.activeMipmapLevel = activeMipmapLevel; - renderContext.occlusionQueryCount = renderList.occlusionQueryCount; - - // - - this._nodes.updateScene(sceneRef); - - // - - this._background.update(sceneRef, renderList, renderContext); - - // - - this.backend.beginRender(renderContext); - - // process render lists - - const opaqueObjects = renderList.opaque; - const transparentObjects = renderList.transparent; - const lightsNode = renderList.lightsNode; - - if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); - if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); - - // finish render pass - - this.backend.finishRender(renderContext); - - // restore render tree - - nodeFrame.renderId = previousRenderId; - - this._currentRenderContext = previousRenderContext; - this._currentRenderObjectFunction = previousRenderObjectFunction; - - // - - if (frameBufferTarget !== null) { - this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); - - _quad.material.fragmentNode = this._nodes.getOutputNode(renderTarget.texture); - - this._renderScene(_quad, _quad.camera, false); - } - - // - - sceneRef.onAfterRender(this, scene, camera, renderTarget); - - // - - return renderContext; - } - - getMaxAnisotropy() { - return this.backend.getMaxAnisotropy(); - } - - getActiveCubeFace() { - return this._activeCubeFace; - } - - getActiveMipmapLevel() { - return this._activeMipmapLevel; - } - - async setAnimationLoop(callback) { - if (this._initialized === false) await this.init(); - - this._animation.setAnimationLoop(callback); - } - - getArrayBuffer(attribute) { - // @deprecated, r155 - - console.warn('THREE.Renderer: getArrayBuffer() is deprecated. Use getArrayBufferAsync() instead.'); - - return this.getArrayBufferAsync(attribute); - } - - async getArrayBufferAsync(attribute) { - return await this.backend.getArrayBufferAsync(attribute); - } - - getContext() { - return this.backend.getContext(); - } - - getPixelRatio() { - return this._pixelRatio; - } - - getDrawingBufferSize(target) { - return target.set(this._width * this._pixelRatio, this._height * this._pixelRatio).floor(); - } - - getSize(target) { - return target.set(this._width, this._height); - } - - setPixelRatio(value = 1) { - this._pixelRatio = value; - - this.setSize(this._width, this._height, false); - } - - setDrawingBufferSize(width, height, pixelRatio) { - this._width = width; - this._height = height; - - this._pixelRatio = pixelRatio; - - this.domElement.width = Math.floor(width * pixelRatio); - this.domElement.height = Math.floor(height * pixelRatio); - - this.setViewport(0, 0, width, height); - - if (this._initialized) this.backend.updateSize(); - } - - setSize(width, height, updateStyle = true) { - this._width = width; - this._height = height; - - this.domElement.width = Math.floor(width * this._pixelRatio); - this.domElement.height = Math.floor(height * this._pixelRatio); - - if (updateStyle === true) { - this.domElement.style.width = width + 'px'; - this.domElement.style.height = height + 'px'; - } - - this.setViewport(0, 0, width, height); - - if (this._initialized) this.backend.updateSize(); - } - - setOpaqueSort(method) { - this._opaqueSort = method; - } - - setTransparentSort(method) { - this._transparentSort = method; - } - - getScissor(target) { - const scissor = this._scissor; - - target.x = scissor.x; - target.y = scissor.y; - target.width = scissor.width; - target.height = scissor.height; - - return target; - } - - setScissor(x, y, width, height) { - const scissor = this._scissor; - - if (x.isVector4) { - scissor.copy(x); - } else { - scissor.set(x, y, width, height); - } - } - - getScissorTest() { - return this._scissorTest; - } - - setScissorTest(boolean) { - this._scissorTest = boolean; - - this.backend.setScissorTest(boolean); - } - - getViewport(target) { - return target.copy(this._viewport); - } - - setViewport(x, y, width, height, minDepth = 0, maxDepth = 1) { - const viewport = this._viewport; - - if (x.isVector4) { - viewport.copy(x); - } else { - viewport.set(x, y, width, height); - } - - viewport.minDepth = minDepth; - viewport.maxDepth = maxDepth; - } - - getClearColor(target) { - return target.copy(this._clearColor); - } - - setClearColor(color, alpha = 1) { - this._clearColor.set(color); - this._clearColor.a = alpha; - } - - getClearAlpha() { - return this._clearColor.a; - } - - setClearAlpha(alpha) { - this._clearColor.a = alpha; - } - - getClearDepth() { - return this._clearDepth; - } - - setClearDepth(depth) { - this._clearDepth = depth; - } - - getClearStencil() { - return this._clearStencil; - } - - setClearStencil(stencil) { - this._clearStencil = stencil; - } - - isOccluded(object) { - const renderContext = this._currentRenderContext; - - return renderContext && this.backend.isOccluded(renderContext, object); - } - - clear(color = true, depth = true, stencil = true) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead.', - ); - - return this.clearAsync(color, depth, stencil); - } - - const renderTarget = this._renderTarget || this._getFrameBufferTarget(); - - let renderTargetData = null; - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget); - - renderTargetData = this._textures.get(renderTarget); - } - - this.backend.clear(color, depth, stencil, renderTargetData); - } - - clearColor() { - return this.clear(true, false, false); - } - - clearDepth() { - return this.clear(false, true, false); - } - - clearStencil() { - return this.clear(false, false, true); - } - - async clearAsync(color = true, depth = true, stencil = true) { - if (this._initialized === false) await this.init(); - - this.clear(color, depth, stencil); - } - - clearColorAsync() { - return this.clearAsync(true, false, false); - } - - clearDepthAsync() { - return this.clearAsync(false, true, false); - } - - clearStencilAsync() { - return this.clearAsync(false, false, true); - } - - get currentColorSpace() { - const renderTarget = this._renderTarget; - - if (renderTarget !== null) { - const texture = renderTarget.texture; - - return (Array.isArray(texture) ? texture[0] : texture).colorSpace; - } - - return this.outputColorSpace; - } - - dispose() { - this.info.dispose(); - - this._animation.dispose(); - this._objects.dispose(); - this._pipelines.dispose(); - this._nodes.dispose(); - this._bindings.dispose(); - this._renderLists.dispose(); - this._renderContexts.dispose(); - this._textures.dispose(); - - this.setRenderTarget(null); - this.setAnimationLoop(null); - } - - setRenderTarget(renderTarget, activeCubeFace = 0, activeMipmapLevel = 0) { - this._renderTarget = renderTarget; - this._activeCubeFace = activeCubeFace; - this._activeMipmapLevel = activeMipmapLevel; - } - - getRenderTarget() { - return this._renderTarget; - } - - setRenderObjectFunction(renderObjectFunction) { - this._renderObjectFunction = renderObjectFunction; - } - - getRenderObjectFunction() { - return this._renderObjectFunction; - } - - async computeAsync(computeNodes) { - if (this._initialized === false) await this.init(); - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - - // - - this.info.calls++; - this.info.compute.calls++; - this.info.compute.computeCalls++; - - nodeFrame.renderId = this.info.calls; - - // - - const backend = this.backend; - const pipelines = this._pipelines; - const bindings = this._bindings; - const nodes = this._nodes; - const computeList = Array.isArray(computeNodes) ? computeNodes : [computeNodes]; - - if (computeList[0] === undefined || computeList[0].isComputeNode !== true) { - throw new Error('THREE.Renderer: .compute() expects a ComputeNode.'); - } - - backend.beginCompute(computeNodes); - - for (const computeNode of computeList) { - // onInit - - if (pipelines.has(computeNode) === false) { - const dispose = () => { - computeNode.removeEventListener('dispose', dispose); - - pipelines.delete(computeNode); - bindings.delete(computeNode); - nodes.delete(computeNode); - }; - - computeNode.addEventListener('dispose', dispose); - - // - - computeNode.onInit({ renderer: this }); - } - - nodes.updateForCompute(computeNode); - bindings.updateForCompute(computeNode); - - const computeBindings = bindings.getForCompute(computeNode); - const computePipeline = pipelines.getForCompute(computeNode, computeBindings); - - backend.compute(computeNodes, computeNode, computeBindings, computePipeline); - } - - backend.finishCompute(computeNodes); - - await this.backend.resolveTimestampAsync(computeNodes, 'compute'); - - // - - nodeFrame.renderId = previousRenderId; - } - - async hasFeatureAsync(name) { - if (this._initialized === false) await this.init(); - - return this.backend.hasFeature(name); - } - - hasFeature(name) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', - ); - - return false; - } - - return this.backend.hasFeature(name); - } - - copyFramebufferToTexture(framebufferTexture) { - const renderContext = this._currentRenderContext; - - this._textures.updateTexture(framebufferTexture); - - this.backend.copyFramebufferToTexture(framebufferTexture, renderContext); - } - - copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0) { - this._textures.updateTexture(srcTexture); - this._textures.updateTexture(dstTexture); - - this.backend.copyTextureToTexture(srcTexture, dstTexture, srcRegion, dstPosition, level); - } - - readRenderTargetPixelsAsync(renderTarget, x, y, width, height, index = 0) { - return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); - } - - _projectObject(object, camera, groupOrder, renderList) { - if (object.visible === false) return; - - const visible = object.layers.test(camera.layers); - - if (visible) { - if (object.isGroup) { - groupOrder = object.renderOrder; - } else if (object.isLOD) { - if (object.autoUpdate === true) object.update(camera); - } else if (object.isLight) { - renderList.pushLight(object); - } else if (object.isSprite) { - if (!object.frustumCulled || _frustum.intersectsSprite(object)) { - if (this.sortObjects === true) { - _vector3.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix); - } - - const geometry = object.geometry; - const material = object.material; - - if (material.visible) { - renderList.push(object, geometry, material, groupOrder, _vector3.z, null); - } - } - } else if (object.isLineLoop) { - console.error( - 'THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.', - ); - } else if (object.isMesh || object.isLine || object.isPoints) { - if (!object.frustumCulled || _frustum.intersectsObject(object)) { - const geometry = object.geometry; - const material = object.material; - - if (this.sortObjects === true) { - if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); - - _vector3 - .copy(geometry.boundingSphere.center) - .applyMatrix4(object.matrixWorld) - .applyMatrix4(_projScreenMatrix); - } - - if (Array.isArray(material)) { - const groups = geometry.groups; - - for (let i = 0, l = groups.length; i < l; i++) { - const group = groups[i]; - const groupMaterial = material[group.materialIndex]; - - if (groupMaterial && groupMaterial.visible) { - renderList.push(object, geometry, groupMaterial, groupOrder, _vector3.z, group); - } - } - } else if (material.visible) { - renderList.push(object, geometry, material, groupOrder, _vector3.z, null); - } - } - } - } - - const children = object.children; - - for (let i = 0, l = children.length; i < l; i++) { - this._projectObject(children[i], camera, groupOrder, renderList); - } - } - - _renderObjects(renderList, camera, scene, lightsNode) { - // process renderable objects - - for (let i = 0, il = renderList.length; i < il; i++) { - const renderItem = renderList[i]; - - // @TODO: Add support for multiple materials per object. This will require to extract - // the material from the renderItem object and pass it with its group data to renderObject(). - - const { object, geometry, material, group } = renderItem; - - if (camera.isArrayCamera) { - const cameras = camera.cameras; - - for (let j = 0, jl = cameras.length; j < jl; j++) { - const camera2 = cameras[j]; - - if (object.layers.test(camera2.layers)) { - const vp = camera2.viewport; - const minDepth = vp.minDepth === undefined ? 0 : vp.minDepth; - const maxDepth = vp.maxDepth === undefined ? 1 : vp.maxDepth; - - const viewportValue = this._currentRenderContext.viewportValue; - viewportValue.copy(vp).multiplyScalar(this._pixelRatio).floor(); - viewportValue.minDepth = minDepth; - viewportValue.maxDepth = maxDepth; - - this.backend.updateViewport(this._currentRenderContext); - - this._currentRenderObjectFunction( - object, - scene, - camera2, - geometry, - material, - group, - lightsNode, - ); - } - } - } else { - this._currentRenderObjectFunction(object, scene, camera, geometry, material, group, lightsNode); - } - } - } - - renderObject(object, scene, camera, geometry, material, group, lightsNode) { - let overridePositionNode; - let overrideFragmentNode; - let overrideDepthNode; - - // - - object.onBeforeRender(this, scene, camera, geometry, material, group); - - material.onBeforeRender(this, scene, camera, geometry, material, group); - - // - - if (scene.overrideMaterial !== null) { - const overrideMaterial = scene.overrideMaterial; - - if (material.positionNode && material.positionNode.isNode) { - overridePositionNode = overrideMaterial.positionNode; - overrideMaterial.positionNode = material.positionNode; - } - - if (overrideMaterial.isShadowNodeMaterial) { - overrideMaterial.side = material.shadowSide === null ? material.side : material.shadowSide; - - if (material.depthNode && material.depthNode.isNode) { - overrideDepthNode = overrideMaterial.depthNode; - overrideMaterial.depthNode = material.depthNode; - } - - if (material.shadowNode && material.shadowNode.isNode) { - overrideFragmentNode = overrideMaterial.fragmentNode; - overrideMaterial.fragmentNode = material.shadowNode; - } - - if (this.localClippingEnabled) { - if (material.clipShadows) { - if (overrideMaterial.clippingPlanes !== material.clippingPlanes) { - overrideMaterial.clippingPlanes = material.clippingPlanes; - overrideMaterial.needsUpdate = true; - } - - if (overrideMaterial.clipIntersection !== material.clipIntersection) { - overrideMaterial.clipIntersection = material.clipIntersection; - } - } else if (Array.isArray(overrideMaterial.clippingPlanes)) { - overrideMaterial.clippingPlanes = null; - overrideMaterial.needsUpdate = true; - } - } - } - - material = overrideMaterial; - } - - // - - if (material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false) { - material.side = BackSide; - this._handleObjectFunction(object, material, scene, camera, lightsNode, group, 'backSide'); // create backSide pass id - - material.side = FrontSide; - this._handleObjectFunction(object, material, scene, camera, lightsNode, group); // use default pass id - - material.side = DoubleSide; - } else { - this._handleObjectFunction(object, material, scene, camera, lightsNode, group); - } - - // - - if (overridePositionNode !== undefined) { - scene.overrideMaterial.positionNode = overridePositionNode; - } - - if (overrideDepthNode !== undefined) { - scene.overrideMaterial.depthNode = overrideDepthNode; - } - - if (overrideFragmentNode !== undefined) { - scene.overrideMaterial.fragmentNode = overrideFragmentNode; - } - - // - - object.onAfterRender(this, scene, camera, geometry, material, group); - } - - _renderObjectDirect(object, material, scene, camera, lightsNode, group, passId) { - const renderObject = this._objects.get( - object, - material, - scene, - camera, - lightsNode, - this._currentRenderContext, - passId, - ); - renderObject.drawRange = group || object.geometry.drawRange; - - // - - this._nodes.updateBefore(renderObject); - - // - - object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld); - object.normalMatrix.getNormalMatrix(object.modelViewMatrix); - - // - - this._nodes.updateForRender(renderObject); - this._geometries.updateForRender(renderObject); - this._bindings.updateForRender(renderObject); - this._pipelines.updateForRender(renderObject); - - // - - this.backend.draw(renderObject, this.info); - } - - _createObjectPipeline(object, material, scene, camera, lightsNode, passId) { - const renderObject = this._objects.get( - object, - material, - scene, - camera, - lightsNode, - this._currentRenderContext, - passId, - ); - - // - - this._nodes.updateBefore(renderObject); - - // - - this._nodes.updateForRender(renderObject); - this._geometries.updateForRender(renderObject); - this._bindings.updateForRender(renderObject); - - this._pipelines.getForRender(renderObject, this._compilationPromises); - } - - get compute() { - return this.computeAsync; - } - - get compile() { - return this.compileAsync; - } -} - -export default Renderer; From 8a5767ebc0573729aa51cb5036d514d6bafcf609 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 20:48:09 -0400 Subject: [PATCH 13/18] Add examples --- examples-jsm/examples/nodes/core/Node.ts | 411 ++++++ .../examples/nodes/core/NodeBuilder.ts | 1011 ++++++++++++++ examples-jsm/examples/nodes/core/NodeFrame.ts | 101 ++ .../examples/nodes/core/NodeParser.ts | 7 + examples-jsm/examples/nodes/core/constants.ts | 28 + examples-jsm/examples/nodes/fog/FogNode.ts | 38 + .../nodes/lighting/EnvironmentNode.ts | 118 ++ .../examples/nodes/lighting/LightsNode.ts | 157 +++ .../examples/renderers/common/Renderer.ts | 1225 +++++++++++++++++ examples-jsm/index.js | 3 + 10 files changed, 3099 insertions(+) create mode 100644 examples-jsm/examples/nodes/core/Node.ts create mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts create mode 100644 examples-jsm/examples/nodes/core/NodeFrame.ts create mode 100644 examples-jsm/examples/nodes/core/NodeParser.ts create mode 100644 examples-jsm/examples/nodes/core/constants.ts create mode 100644 examples-jsm/examples/nodes/fog/FogNode.ts create mode 100644 examples-jsm/examples/nodes/lighting/EnvironmentNode.ts create mode 100644 examples-jsm/examples/nodes/lighting/LightsNode.ts create mode 100644 examples-jsm/examples/renderers/common/Renderer.ts diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts new file mode 100644 index 000000000..438c44dd1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/Node.ts @@ -0,0 +1,411 @@ +import { EventDispatcher } from 'three'; +import { NodeUpdateType } from './constants.js'; +import { getNodeChildren, getCacheKey } from './NodeUtils.js'; +import { MathUtils } from 'three'; + +const NodeClasses = new Map(); + +let _nodeId = 0; + +class Node extends EventDispatcher { + constructor(nodeType = null) { + super(); + + this.nodeType = nodeType; + + this.updateType = NodeUpdateType.NONE; + this.updateBeforeType = NodeUpdateType.NONE; + + this.uuid = MathUtils.generateUUID(); + + this.version = 0; + + this._cacheKey = null; + this._cacheKeyVersion = 0; + + this.isNode = true; + + Object.defineProperty(this, 'id', { value: _nodeId++ }); + } + + set needsUpdate(value) { + if (value === true) { + this.version++; + } + } + + get type() { + return this.constructor.type; + } + + onUpdate(callback, updateType) { + this.updateType = updateType; + this.update = callback.bind(this.getSelf()); + + return this; + } + + onFrameUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.FRAME); + } + + onRenderUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.RENDER); + } + + onObjectUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.OBJECT); + } + + onReference(callback) { + this.updateReference = callback.bind(this.getSelf()); + + return this; + } + + getSelf() { + // Returns non-node object. + + return this.self || this; + } + + updateReference(/*state*/) { + return this; + } + + isGlobal(/*builder*/) { + return false; + } + + *getChildren() { + for (const { childNode } of getNodeChildren(this)) { + yield childNode; + } + } + + dispose() { + this.dispatchEvent({ type: 'dispose' }); + } + + traverse(callback) { + callback(this); + + for (const childNode of this.getChildren()) { + childNode.traverse(callback); + } + } + + getCacheKey(force = false) { + force = force || this.version !== this._cacheKeyVersion; + + if (force === true || this._cacheKey === null) { + this._cacheKey = getCacheKey(this, force); + this._cacheKeyVersion = this.version; + } + + return this._cacheKey; + } + + getHash(/*builder*/) { + return this.uuid; + } + + getUpdateType() { + return this.updateType; + } + + getUpdateBeforeType() { + return this.updateBeforeType; + } + + getElementType(builder) { + const type = this.getNodeType(builder); + const elementType = builder.getElementType(type); + + return elementType; + } + + getNodeType(builder) { + const nodeProperties = builder.getNodeProperties(this); + + if (nodeProperties.outputNode) { + return nodeProperties.outputNode.getNodeType(builder); + } + + return this.nodeType; + } + + getShared(builder) { + const hash = this.getHash(builder); + const nodeFromHash = builder.getNodeFromHash(hash); + + return nodeFromHash || this; + } + + setup(builder) { + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of this.getChildren()) { + nodeProperties['_node' + childNode.id] = childNode; + } + + // return a outputNode if exists + return null; + } + + construct(builder) { + // @deprecated, r157 + + console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); + + return this.setup(builder); + } + + increaseUsage(builder) { + const nodeData = builder.getDataFromNode(this); + nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; + + return nodeData.usageCount; + } + + analyze(builder) { + const usageCount = this.increaseUsage(builder); + + if (usageCount === 1) { + // node flow children + + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of Object.values(nodeProperties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } + + generate(builder, output) { + const { outputNode } = builder.getNodeProperties(this); + + if (outputNode && outputNode.isNode === true) { + return outputNode.build(builder, output); + } + } + + updateBefore(/*frame*/) { + console.warn('Abstract function.'); + } + + update(/*frame*/) { + console.warn('Abstract function.'); + } + + build(builder, output = null) { + const refNode = this.getShared(builder); + + if (this !== refNode) { + return refNode.build(builder, output); + } + + builder.addNode(this); + builder.addChain(this); + + /* Build stages expected results: + - "setup" -> Node + - "analyze" -> null + - "generate" -> String + */ + let result = null; + + const buildStage = builder.getBuildStage(); + + if (buildStage === 'setup') { + this.updateReference(builder); + + const properties = builder.getNodeProperties(this); + + if (properties.initialized !== true || builder.context.tempRead === false) { + const stackNodesBeforeSetup = builder.stack.nodes.length; + + properties.initialized = true; + properties.outputNode = this.setup(builder); + + if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { + properties.outputNode = builder.stack; + } + + for (const childNode of Object.values(properties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } else if (buildStage === 'analyze') { + this.analyze(builder); + } else if (buildStage === 'generate') { + const isGenerateOnce = this.generate.length === 1; + + if (isGenerateOnce) { + const type = this.getNodeType(builder); + const nodeData = builder.getDataFromNode(this); + + result = nodeData.snippet; + + if (result === undefined /*|| builder.context.tempRead === false*/) { + result = this.generate(builder) || ''; + + nodeData.snippet = result; + } + + result = builder.format(result, type, output); + } else { + result = this.generate(builder, output) || ''; + } + } + + builder.removeChain(this); + + return result; + } + + getSerializeChildren() { + return getNodeChildren(this); + } + + serialize(json) { + const nodeChildren = this.getSerializeChildren(); + + const inputNodes = {}; + + for (const { property, index, childNode } of nodeChildren) { + if (index !== undefined) { + if (inputNodes[property] === undefined) { + inputNodes[property] = Number.isInteger(index) ? [] : {}; + } + + inputNodes[property][index] = childNode.toJSON(json.meta).uuid; + } else { + inputNodes[property] = childNode.toJSON(json.meta).uuid; + } + } + + if (Object.keys(inputNodes).length > 0) { + json.inputNodes = inputNodes; + } + } + + deserialize(json) { + if (json.inputNodes !== undefined) { + const nodes = json.meta.nodes; + + for (const property in json.inputNodes) { + if (Array.isArray(json.inputNodes[property])) { + const inputArray = []; + + for (const uuid of json.inputNodes[property]) { + inputArray.push(nodes[uuid]); + } + + this[property] = inputArray; + } else if (typeof json.inputNodes[property] === 'object') { + const inputObject = {}; + + for (const subProperty in json.inputNodes[property]) { + const uuid = json.inputNodes[property][subProperty]; + + inputObject[subProperty] = nodes[uuid]; + } + + this[property] = inputObject; + } else { + const uuid = json.inputNodes[property]; + + this[property] = nodes[uuid]; + } + } + } + } + + toJSON(meta) { + const { uuid, type } = this; + const isRoot = meta === undefined || typeof meta === 'string'; + + if (isRoot) { + meta = { + textures: {}, + images: {}, + nodes: {}, + }; + } + + // serialize + + let data = meta.nodes[uuid]; + + if (data === undefined) { + data = { + uuid, + type, + meta, + metadata: { + version: 4.6, + type: 'Node', + generator: 'Node.toJSON', + }, + }; + + if (isRoot !== true) meta.nodes[data.uuid] = data; + + this.serialize(data); + + delete data.meta; + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache(cache) { + const values = []; + + for (const key in cache) { + const data = cache[key]; + delete data.metadata; + values.push(data); + } + + return values; + } + + if (isRoot) { + const textures = extractFromCache(meta.textures); + const images = extractFromCache(meta.images); + const nodes = extractFromCache(meta.nodes); + + if (textures.length > 0) data.textures = textures; + if (images.length > 0) data.images = images; + if (nodes.length > 0) data.nodes = nodes; + } + + return data; + } +} + +export default Node; + +export function addNodeClass(type, nodeClass) { + if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); + if (NodeClasses.has(type)) { + console.warn(`Redefinition of node class ${type}`); + return; + } + + NodeClasses.set(type, nodeClass); + nodeClass.type = type; +} + +export function createNodeFromType(type) { + const Class = NodeClasses.get(type); + + if (Class !== undefined) { + return new Class(); + } +} diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts new file mode 100644 index 000000000..ebdc13ff1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts @@ -0,0 +1,1011 @@ +import NodeUniform from './NodeUniform.js'; +import NodeAttribute from './NodeAttribute.js'; +import NodeVarying from './NodeVarying.js'; +import NodeVar from './NodeVar.js'; +import NodeCode from './NodeCode.js'; +import NodeKeywords from './NodeKeywords.js'; +import NodeCache from './NodeCache.js'; +import ParameterNode from './ParameterNode.js'; +import FunctionNode from '../code/FunctionNode.js'; +import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; +import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; + +import { + FloatNodeUniform, + Vector2NodeUniform, + Vector3NodeUniform, + Vector4NodeUniform, + ColorNodeUniform, + Matrix3NodeUniform, + Matrix4NodeUniform, +} from '../../renderers/common/nodes/NodeUniform.js'; + +import { + REVISION, + RenderTarget, + Color, + Vector2, + Vector3, + Vector4, + IntType, + UnsignedIntType, + Float16BufferAttribute, +} from 'three'; + +import { stack } from './StackNode.js'; +import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; + +import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; +import ChainMap from '../../renderers/common/ChainMap.js'; + +import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; + +const uniformsGroupCache = new ChainMap(); + +const typeFromLength = new Map([ + [2, 'vec2'], + [3, 'vec3'], + [4, 'vec4'], + [9, 'mat3'], + [16, 'mat4'], +]); + +const typeFromArray = new Map([ + [Int8Array, 'int'], + [Int16Array, 'int'], + [Int32Array, 'int'], + [Uint8Array, 'uint'], + [Uint16Array, 'uint'], + [Uint32Array, 'uint'], + [Float32Array, 'float'], +]); + +const toFloat = value => { + value = Number(value); + + return value + (value % 1 ? '' : '.0'); +}; + +class NodeBuilder { + constructor(object, renderer, parser, scene = null, material = null) { + this.object = object; + this.material = material || (object && object.material) || null; + this.geometry = (object && object.geometry) || null; + this.renderer = renderer; + this.parser = parser; + this.scene = scene; + + this.nodes = []; + this.updateNodes = []; + this.updateBeforeNodes = []; + this.hashNodes = {}; + + this.lightsNode = null; + this.environmentNode = null; + this.fogNode = null; + + this.clippingContext = null; + + this.vertexShader = null; + this.fragmentShader = null; + this.computeShader = null; + + this.flowNodes = { vertex: [], fragment: [], compute: [] }; + this.flowCode = { vertex: '', fragment: '', compute: [] }; + this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; + this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; + this.bindings = { vertex: [], fragment: [], compute: [] }; + this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; + this.bindingsArray = null; + this.attributes = []; + this.bufferAttributes = []; + this.varyings = []; + this.codes = {}; + this.vars = {}; + this.flow = { code: '' }; + this.chaining = []; + this.stack = stack(); + this.stacks = []; + this.tab = '\t'; + + this.currentFunctionNode = null; + + this.context = { + keywords: new NodeKeywords(), + material: this.material, + }; + + this.cache = new NodeCache(); + this.globalCache = this.cache; + + this.flowsData = new WeakMap(); + + this.shaderStage = null; + this.buildStage = null; + } + + createRenderTarget(width, height, options) { + return new RenderTarget(width, height, options); + } + + createCubeRenderTarget(size, options) { + return new CubeRenderTarget(size, options); + } + + createPMREMGenerator() { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return new PMREMGenerator(this.renderer); + } + + includes(node) { + return this.nodes.includes(node); + } + + _getSharedBindings(bindings) { + const shared = []; + + for (const binding of bindings) { + if (binding.shared === true) { + // nodes is the chainmap key + const nodes = binding.getNodes(); + + let sharedBinding = uniformsGroupCache.get(nodes); + + if (sharedBinding === undefined) { + uniformsGroupCache.set(nodes, binding); + + sharedBinding = binding; + } + + shared.push(sharedBinding); + } else { + shared.push(binding); + } + } + + return shared; + } + + getBindings() { + let bindingsArray = this.bindingsArray; + + if (bindingsArray === null) { + const bindings = this.bindings; + + this.bindingsArray = bindingsArray = this._getSharedBindings( + this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, + ); + } + + return bindingsArray; + } + + setHashNode(node, hash) { + this.hashNodes[hash] = node; + } + + addNode(node) { + if (this.nodes.includes(node) === false) { + this.nodes.push(node); + + this.setHashNode(node, node.getHash(this)); + } + } + + buildUpdateNodes() { + for (const node of this.nodes) { + const updateType = node.getUpdateType(); + const updateBeforeType = node.getUpdateBeforeType(); + + if (updateType !== NodeUpdateType.NONE) { + this.updateNodes.push(node.getSelf()); + } + + if (updateBeforeType !== NodeUpdateType.NONE) { + this.updateBeforeNodes.push(node); + } + } + } + + get currentNode() { + return this.chaining[this.chaining.length - 1]; + } + + addChain(node) { + /* + if ( this.chaining.indexOf( node ) !== - 1 ) { + + console.warn( 'Recursive node: ', node ); + + } + */ + + this.chaining.push(node); + } + + removeChain(node) { + const lastChain = this.chaining.pop(); + + if (lastChain !== node) { + throw new Error('NodeBuilder: Invalid node chaining!'); + } + } + + getMethod(method) { + return method; + } + + getNodeFromHash(hash) { + return this.hashNodes[hash]; + } + + addFlow(shaderStage, node) { + this.flowNodes[shaderStage].push(node); + + return node; + } + + setContext(context) { + this.context = context; + } + + getContext() { + return this.context; + } + + setCache(cache) { + this.cache = cache; + } + + getCache() { + return this.cache; + } + + isAvailable(/*name*/) { + return false; + } + + getVertexIndex() { + console.warn('Abstract function.'); + } + + getInstanceIndex() { + console.warn('Abstract function.'); + } + + getFrontFacing() { + console.warn('Abstract function.'); + } + + getFragCoord() { + console.warn('Abstract function.'); + } + + isFlipY() { + return false; + } + + generateTexture(/* texture, textureProperty, uvSnippet */) { + console.warn('Abstract function.'); + } + + generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { + console.warn('Abstract function.'); + } + + generateConst(type, value = null) { + if (value === null) { + if (type === 'float' || type === 'int' || type === 'uint') value = 0; + else if (type === 'bool') value = false; + else if (type === 'color') value = new Color(); + else if (type === 'vec2') value = new Vector2(); + else if (type === 'vec3') value = new Vector3(); + else if (type === 'vec4') value = new Vector4(); + } + + if (type === 'float') return toFloat(value); + if (type === 'int') return `${Math.round(value)}`; + if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; + if (type === 'bool') return value ? 'true' : 'false'; + if (type === 'color') + return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; + + const typeLength = this.getTypeLength(type); + + const componentType = this.getComponentType(type); + + const generateConst = value => this.generateConst(componentType, value); + + if (typeLength === 2) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; + } else if (typeLength === 3) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; + } else if (typeLength === 4) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; + } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { + return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; + } else if (typeLength > 4) { + return `${this.getType(type)}()`; + } + + throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); + } + + getType(type) { + if (type === 'color') return 'vec3'; + + return type; + } + + generateMethod(method) { + return method; + } + + hasGeometryAttribute(name) { + return this.geometry && this.geometry.getAttribute(name) !== undefined; + } + + getAttribute(name, type) { + const attributes = this.attributes; + + // find attribute + + for (const attribute of attributes) { + if (attribute.name === name) { + return attribute; + } + } + + // create a new if no exist + + const attribute = new NodeAttribute(name, type); + + attributes.push(attribute); + + return attribute; + } + + getPropertyName(node /*, shaderStage*/) { + return node.name; + } + + isVector(type) { + return /vec\d/.test(type); + } + + isMatrix(type) { + return /mat\d/.test(type); + } + + isReference(type) { + return ( + type === 'void' || + type === 'property' || + type === 'sampler' || + type === 'texture' || + type === 'cubeTexture' || + type === 'storageTexture' || + type === 'texture3D' + ); + } + + needsColorSpaceToLinear(/*texture*/) { + return false; + } + + getComponentTypeFromTexture(texture) { + const type = texture.type; + + if (texture.isDataTexture) { + if (type === IntType) return 'int'; + if (type === UnsignedIntType) return 'uint'; + } + + return 'float'; + } + + getElementType(type) { + if (type === 'mat2') return 'vec2'; + if (type === 'mat3') return 'vec3'; + if (type === 'mat4') return 'vec4'; + + return this.getComponentType(type); + } + + getComponentType(type) { + type = this.getVectorType(type); + + if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; + + const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); + + if (componentType === null) return null; + + if (componentType[1] === 'b') return 'bool'; + if (componentType[1] === 'i') return 'int'; + if (componentType[1] === 'u') return 'uint'; + + return 'float'; + } + + getVectorType(type) { + if (type === 'color') return 'vec3'; + if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') + return 'vec4'; + + return type; + } + + getTypeFromLength(length, componentType = 'float') { + if (length === 1) return componentType; + + const baseType = typeFromLength.get(length); + const prefix = componentType === 'float' ? '' : componentType[0]; + + return prefix + baseType; + } + + getTypeFromArray(array) { + return typeFromArray.get(array.constructor); + } + + getTypeFromAttribute(attribute) { + let dataAttribute = attribute; + + if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; + + const array = dataAttribute.array; + const itemSize = attribute.itemSize; + const normalized = attribute.normalized; + + let arrayType; + + if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { + arrayType = this.getTypeFromArray(array); + } + + return this.getTypeFromLength(itemSize, arrayType); + } + + getTypeLength(type) { + const vecType = this.getVectorType(type); + const vecNum = /vec([2-4])/.exec(vecType); + + if (vecNum !== null) return Number(vecNum[1]); + if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; + if (/mat2/.test(type) === true) return 4; + if (/mat3/.test(type) === true) return 9; + if (/mat4/.test(type) === true) return 16; + + return 0; + } + + getVectorFromMatrix(type) { + return type.replace('mat', 'vec'); + } + + changeComponentType(type, newComponentType) { + return this.getTypeFromLength(this.getTypeLength(type), newComponentType); + } + + getIntegerType(type) { + const componentType = this.getComponentType(type); + + if (componentType === 'int' || componentType === 'uint') return type; + + return this.changeComponentType(type, 'int'); + } + + addStack() { + this.stack = stack(this.stack); + + this.stacks.push(getCurrentStack() || this.stack); + setCurrentStack(this.stack); + + return this.stack; + } + + removeStack() { + const lastStack = this.stack; + this.stack = lastStack.parent; + + setCurrentStack(this.stacks.pop()); + + return lastStack; + } + + getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { + cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; + + let nodeData = cache.getNodeData(node); + + if (nodeData === undefined) { + nodeData = {}; + + cache.setNodeData(node, nodeData); + } + + if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; + + return nodeData[shaderStage]; + } + + getNodeProperties(node, shaderStage = 'any') { + const nodeData = this.getDataFromNode(node, shaderStage); + + return nodeData.properties || (nodeData.properties = { outputNode: null }); + } + + getBufferAttributeFromNode(node, type) { + const nodeData = this.getDataFromNode(node); + + let bufferAttribute = nodeData.bufferAttribute; + + if (bufferAttribute === undefined) { + const index = this.uniforms.index++; + + bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); + + this.bufferAttributes.push(bufferAttribute); + + nodeData.bufferAttribute = bufferAttribute; + } + + return bufferAttribute; + } + + getStructTypeFromNode(node, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + if (nodeData.structType === undefined) { + const index = this.structs.index++; + + node.name = `StructType${index}`; + this.structs[shaderStage].push(node); + + nodeData.structType = node; + } + + return node; + } + + getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { + const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); + + let nodeUniform = nodeData.uniform; + + if (nodeUniform === undefined) { + const index = this.uniforms.index++; + + nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); + + this.uniforms[shaderStage].push(nodeUniform); + + nodeData.uniform = nodeUniform; + } + + return nodeUniform; + } + + getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + let nodeVar = nodeData.variable; + + if (nodeVar === undefined) { + const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); + + if (name === null) name = 'nodeVar' + vars.length; + + nodeVar = new NodeVar(name, type); + + vars.push(nodeVar); + + nodeData.variable = nodeVar; + } + + return nodeVar; + } + + getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { + const nodeData = this.getDataFromNode(node, 'any'); + + let nodeVarying = nodeData.varying; + + if (nodeVarying === undefined) { + const varyings = this.varyings; + const index = varyings.length; + + if (name === null) name = 'nodeVarying' + index; + + nodeVarying = new NodeVarying(name, type); + + varyings.push(nodeVarying); + + nodeData.varying = nodeVarying; + } + + return nodeVarying; + } + + getCodeFromNode(node, type, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node); + + let nodeCode = nodeData.code; + + if (nodeCode === undefined) { + const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); + const index = codes.length; + + nodeCode = new NodeCode('nodeCode' + index, type); + + codes.push(nodeCode); + + nodeData.code = nodeCode; + } + + return nodeCode; + } + + addLineFlowCode(code) { + if (code === '') return this; + + code = this.tab + code; + + if (!/;\s*$/.test(code)) { + code = code + ';\n'; + } + + this.flow.code += code; + + return this; + } + + addFlowCode(code) { + this.flow.code += code; + + return this; + } + + addFlowTab() { + this.tab += '\t'; + + return this; + } + + removeFlowTab() { + this.tab = this.tab.slice(0, -1); + + return this; + } + + getFlowData(node /*, shaderStage*/) { + return this.flowsData.get(node); + } + + flowNode(node) { + const output = node.getNodeType(this); + + const flowData = this.flowChildNode(node, output); + + this.flowsData.set(node, flowData); + + return flowData; + } + + buildFunctionNode(shaderNode) { + const fn = new FunctionNode(); + + const previous = this.currentFunctionNode; + + this.currentFunctionNode = fn; + + fn.code = this.buildFunctionCode(shaderNode); + + this.currentFunctionNode = previous; + + return fn; + } + + flowShaderNode(shaderNode) { + const layout = shaderNode.layout; + + let inputs; + + if (shaderNode.isArrayInput) { + inputs = []; + + for (const input of layout.inputs) { + inputs.push(new ParameterNode(input.type, input.name)); + } + } else { + inputs = {}; + + for (const input of layout.inputs) { + inputs[input.name] = new ParameterNode(input.type, input.name); + } + } + + // + + shaderNode.layout = null; + + const callNode = shaderNode.call(inputs); + const flowData = this.flowStagesNode(callNode, layout.type); + + shaderNode.layout = layout; + + return flowData; + } + + flowStagesNode(node, output = null) { + const previousFlow = this.flow; + const previousVars = this.vars; + const previousBuildStage = this.buildStage; + + const flow = { + code: '', + }; + + this.flow = flow; + this.vars = {}; + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + flow.result = node.build(this, output); + } + + flow.vars = this.getVars(this.shaderStage); + + this.flow = previousFlow; + this.vars = previousVars; + this.setBuildStage(previousBuildStage); + + return flow; + } + + getFunctionOperator() { + return null; + } + + flowChildNode(node, output = null) { + const previousFlow = this.flow; + + const flow = { + code: '', + }; + + this.flow = flow; + + flow.result = node.build(this, output); + + this.flow = previousFlow; + + return flow; + } + + flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { + const previousShaderStage = this.shaderStage; + + this.setShaderStage(shaderStage); + + const flowData = this.flowChildNode(node, output); + + if (propertyName !== null) { + flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; + } + + this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; + + this.setShaderStage(previousShaderStage); + + return flowData; + } + + getAttributesArray() { + return this.attributes.concat(this.bufferAttributes); + } + + getAttributes(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVaryings(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVar(type, name) { + return `${this.getType(type)} ${name}`; + } + + getVars(shaderStage) { + let snippet = ''; + + const vars = this.vars[shaderStage]; + + if (vars !== undefined) { + for (const variable of vars) { + snippet += `${this.getVar(variable.type, variable.name)}; `; + } + } + + return snippet; + } + + getUniforms(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getCodes(shaderStage) { + const codes = this.codes[shaderStage]; + + let code = ''; + + if (codes !== undefined) { + for (const nodeCode of codes) { + code += nodeCode.code + '\n'; + } + } + + return code; + } + + getHash() { + return this.vertexShader + this.fragmentShader + this.computeShader; + } + + setShaderStage(shaderStage) { + this.shaderStage = shaderStage; + } + + getShaderStage() { + return this.shaderStage; + } + + setBuildStage(buildStage) { + this.buildStage = buildStage; + } + + getBuildStage() { + return this.buildStage; + } + + buildCode() { + console.warn('Abstract function.'); + } + + build() { + const { object, material } = this; + + if (material !== null) { + NodeMaterial.fromMaterial(material).build(this); + } else { + this.addFlow('compute', object); + } + + // setup() -> stage 1: create possible new nodes and returns an output reference node + // analyze() -> stage 2: analyze nodes to possible optimization and validation + // generate() -> stage 3: generate shader + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + if (this.context.vertex && this.context.vertex.isNode) { + this.flowNodeFromShaderStage('vertex', this.context.vertex); + } + + for (const shaderStage of shaderStages) { + this.setShaderStage(shaderStage); + + const flowNodes = this.flowNodes[shaderStage]; + + for (const node of flowNodes) { + if (buildStage === 'generate') { + this.flowNode(node); + } else { + node.build(this); + } + } + } + } + + this.setBuildStage(null); + this.setShaderStage(null); + + // stage 4: build code for a specific output + + this.buildCode(); + this.buildUpdateNodes(); + + return this; + } + + getNodeUniform(uniformNode, type) { + if (type === 'float') return new FloatNodeUniform(uniformNode); + if (type === 'vec2') return new Vector2NodeUniform(uniformNode); + if (type === 'vec3') return new Vector3NodeUniform(uniformNode); + if (type === 'vec4') return new Vector4NodeUniform(uniformNode); + if (type === 'color') return new ColorNodeUniform(uniformNode); + if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); + if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); + + throw new Error(`Uniform "${type}" not declared.`); + } + + createNodeMaterial(type = 'NodeMaterial') { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return createNodeMaterialFromType(type); + } + + format(snippet, fromType, toType) { + fromType = this.getVectorType(fromType); + toType = this.getVectorType(toType); + + if (fromType === toType || toType === null || this.isReference(toType)) { + return snippet; + } + + const fromTypeLength = this.getTypeLength(fromType); + const toTypeLength = this.getTypeLength(toType); + + if (fromTypeLength > 4) { + // fromType is matrix-like + + // @TODO: ignore for now + + return snippet; + } + + if (toTypeLength > 4 || toTypeLength === 0) { + // toType is matrix-like or unknown + + // @TODO: ignore for now + + return snippet; + } + + if (fromTypeLength === toTypeLength) { + return `${this.getType(toType)}( ${snippet} )`; + } + + if (fromTypeLength > toTypeLength) { + return this.format( + `${snippet}.${'xyz'.slice(0, toTypeLength)}`, + this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), + toType, + ); + } + + if (toTypeLength === 4 && fromTypeLength > 1) { + // toType is vec4-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; + } + + if (fromTypeLength === 2) { + // fromType is vec2-like and toType is vec3-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; + } + + if (fromTypeLength === 1 && toTypeLength > 1 && fromType[0] !== toType[0]) { + // fromType is float-like + + // convert a number value to vector type, e.g: + // vec3( 1u ) -> vec3( float( 1u ) ) + + snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; + } + + return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like + } + + getSignature() { + return `// Three.js r${REVISION} - NodeMaterial System\n`; + } +} + +export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts new file mode 100644 index 000000000..b8e8d37b6 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeFrame.ts @@ -0,0 +1,101 @@ +import { NodeUpdateType } from './constants.js'; + +class NodeFrame { + constructor() { + this.time = 0; + this.deltaTime = 0; + + this.frameId = 0; + this.renderId = 0; + + this.startTime = null; + + this.updateMap = new WeakMap(); + this.updateBeforeMap = new WeakMap(); + + this.renderer = null; + this.material = null; + this.camera = null; + this.object = null; + this.scene = null; + } + + _getMaps(referenceMap, nodeRef) { + let maps = referenceMap.get(nodeRef); + + if (maps === undefined) { + maps = { + renderMap: new WeakMap(), + frameMap: new WeakMap(), + }; + + referenceMap.set(nodeRef, maps); + } + + return maps; + } + + updateBeforeNode(node) { + const updateType = node.getUpdateBeforeType(); + const reference = node.updateReference(this); + + if (updateType === NodeUpdateType.FRAME) { + const { frameMap } = this._getMaps(this.updateBeforeMap, reference); + + if (frameMap.get(reference) !== this.frameId) { + if (node.updateBefore(this) !== false) { + frameMap.set(reference, this.frameId); + } + } + } else if (updateType === NodeUpdateType.RENDER) { + const { renderMap } = this._getMaps(this.updateBeforeMap, reference); + + if (renderMap.get(reference) !== this.renderId) { + if (node.updateBefore(this) !== false) { + renderMap.set(reference, this.renderId); + } + } + } else if (updateType === NodeUpdateType.OBJECT) { + node.updateBefore(this); + } + } + + updateNode(node) { + const updateType = node.getUpdateType(); + const reference = node.updateReference(this); + + if (updateType === NodeUpdateType.FRAME) { + const { frameMap } = this._getMaps(this.updateMap, reference); + + if (frameMap.get(reference) !== this.frameId) { + if (node.update(this) !== false) { + frameMap.set(reference, this.frameId); + } + } + } else if (updateType === NodeUpdateType.RENDER) { + const { renderMap } = this._getMaps(this.updateMap, reference); + + if (renderMap.get(reference) !== this.renderId) { + if (node.update(this) !== false) { + renderMap.set(reference, this.renderId); + } + } + } else if (updateType === NodeUpdateType.OBJECT) { + node.update(this); + } + } + + update() { + this.frameId++; + + if (this.lastTime === undefined) this.lastTime = performance.now(); + + this.deltaTime = (performance.now() - this.lastTime) / 1000; + + this.lastTime = performance.now(); + + this.time += this.deltaTime; + } +} + +export default NodeFrame; diff --git a/examples-jsm/examples/nodes/core/NodeParser.ts b/examples-jsm/examples/nodes/core/NodeParser.ts new file mode 100644 index 000000000..9849452f1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeParser.ts @@ -0,0 +1,7 @@ +class NodeParser { + parseFunction(/*source*/) { + console.warn('Abstract function.'); + } +} + +export default NodeParser; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts new file mode 100644 index 000000000..3b01a9a6d --- /dev/null +++ b/examples-jsm/examples/nodes/core/constants.ts @@ -0,0 +1,28 @@ +export const NodeShaderStage = { + VERTEX: 'vertex', + FRAGMENT: 'fragment', +}; + +export const NodeUpdateType = { + NONE: 'none', + FRAME: 'frame', + RENDER: 'render', + OBJECT: 'object', +}; + +export const NodeType = { + BOOLEAN: 'bool', + INTEGER: 'int', + FLOAT: 'float', + VECTOR2: 'vec2', + VECTOR3: 'vec3', + VECTOR4: 'vec4', + MATRIX2: 'mat2', + MATRIX3: 'mat3', + MATRIX4: 'mat4', +}; + +export const defaultShaderStages = ['fragment', 'vertex']; +export const defaultBuildStages = ['setup', 'analyze', 'generate']; +export const shaderStages = [...defaultShaderStages, 'compute']; +export const vectorComponents = ['x', 'y', 'z', 'w']; diff --git a/examples-jsm/examples/nodes/fog/FogNode.ts b/examples-jsm/examples/nodes/fog/FogNode.ts new file mode 100644 index 000000000..9417df5a5 --- /dev/null +++ b/examples-jsm/examples/nodes/fog/FogNode.ts @@ -0,0 +1,38 @@ +import Node, { addNodeClass } from '../core/Node.js'; +import { positionView } from '../accessors/PositionNode.js'; +import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; + +class FogNode extends Node { + constructor(colorNode, factorNode) { + super('float'); + + this.isFogNode = true; + + this.colorNode = colorNode; + this.factorNode = factorNode; + } + + getViewZNode(builder) { + let viewZ; + + const getViewZ = builder.context.getViewZ; + + if (getViewZ !== undefined) { + viewZ = getViewZ(this); + } + + return (viewZ || positionView.z).negate(); + } + + setup() { + return this.factorNode; + } +} + +export default FogNode; + +export const fog = nodeProxy(FogNode); + +addNodeElement('fog', fog); + +addNodeClass('FogNode', FogNode); diff --git a/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts b/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts new file mode 100644 index 000000000..56f8109c2 --- /dev/null +++ b/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts @@ -0,0 +1,118 @@ +import LightingNode from './LightingNode.js'; +import { cache } from '../core/CacheNode.js'; +import { context } from '../core/ContextNode.js'; +import { roughness, clearcoatRoughness } from '../core/PropertyNode.js'; +import { cameraViewMatrix } from '../accessors/CameraNode.js'; +import { + transformedClearcoatNormalView, + transformedNormalView, + transformedNormalWorld, +} from '../accessors/NormalNode.js'; +import { positionViewDirection } from '../accessors/PositionNode.js'; +import { addNodeClass } from '../core/Node.js'; +import { float } from '../shadernode/ShaderNode.js'; +import { reference } from '../accessors/ReferenceNode.js'; +import { transformedBentNormalView } from '../accessors/AccessorsUtils.js'; +import { pmremTexture } from '../pmrem/PMREMNode.js'; + +const envNodeCache = new WeakMap(); + +class EnvironmentNode extends LightingNode { + constructor(envNode = null) { + super(); + + this.envNode = envNode; + } + + setup(builder) { + let envNode = this.envNode; + + if (envNode.isTextureNode) { + let cacheEnvNode = envNodeCache.get(envNode.value); + + if (cacheEnvNode === undefined) { + cacheEnvNode = pmremTexture(envNode.value); + + envNodeCache.set(envNode.value, cacheEnvNode); + } + + envNode = cacheEnvNode; + } + + // + + const { material } = builder; + + const envMap = material.envMap; + const intensity = envMap + ? reference('envMapIntensity', 'float', builder.material) + : reference('environmentIntensity', 'float', builder.scene); // @TODO: Add materialEnvIntensity in MaterialNode + + const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0; + const radianceNormalView = useAnisotropy ? transformedBentNormalView : transformedNormalView; + + const radiance = context(envNode, createRadianceContext(roughness, radianceNormalView)).mul(intensity); + const irradiance = context(envNode, createIrradianceContext(transformedNormalWorld)) + .mul(Math.PI) + .mul(intensity); + + const isolateRadiance = cache(radiance); + + // + + builder.context.radiance.addAssign(isolateRadiance); + + builder.context.iblIrradiance.addAssign(irradiance); + + // + + const clearcoatRadiance = builder.context.lightingModel.clearcoatRadiance; + + if (clearcoatRadiance) { + const clearcoatRadianceContext = context( + envNode, + createRadianceContext(clearcoatRoughness, transformedClearcoatNormalView), + ).mul(intensity); + const isolateClearcoatRadiance = cache(clearcoatRadianceContext); + + clearcoatRadiance.addAssign(isolateClearcoatRadiance); + } + } +} + +const createRadianceContext = (roughnessNode, normalViewNode) => { + let reflectVec = null; + + return { + getUV: () => { + if (reflectVec === null) { + reflectVec = positionViewDirection.negate().reflect(normalViewNode); + + // Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane. + reflectVec = roughnessNode.mul(roughnessNode).mix(reflectVec, normalViewNode).normalize(); + + reflectVec = reflectVec.transformDirection(cameraViewMatrix); + } + + return reflectVec; + }, + getTextureLevel: () => { + return roughnessNode; + }, + }; +}; + +const createIrradianceContext = normalWorldNode => { + return { + getUV: () => { + return normalWorldNode; + }, + getTextureLevel: () => { + return float(1.0); + }, + }; +}; + +export default EnvironmentNode; + +addNodeClass('EnvironmentNode', EnvironmentNode); diff --git a/examples-jsm/examples/nodes/lighting/LightsNode.ts b/examples-jsm/examples/nodes/lighting/LightsNode.ts new file mode 100644 index 000000000..b3695ea8b --- /dev/null +++ b/examples-jsm/examples/nodes/lighting/LightsNode.ts @@ -0,0 +1,157 @@ +import Node from '../core/Node.js'; +import AnalyticLightNode from './AnalyticLightNode.js'; +import { nodeObject, nodeProxy, vec3 } from '../shadernode/ShaderNode.js'; + +const LightNodes = new WeakMap(); + +const sortLights = lights => { + return lights.sort((a, b) => a.id - b.id); +}; + +class LightsNode extends Node { + constructor(lightNodes = []) { + super('vec3'); + + this.totalDiffuseNode = vec3().temp('totalDiffuse'); + this.totalSpecularNode = vec3().temp('totalSpecular'); + + this.outgoingLightNode = vec3().temp('outgoingLight'); + + this.lightNodes = lightNodes; + + this._hash = null; + } + + get hasLight() { + return this.lightNodes.length > 0; + } + + getHash() { + if (this._hash === null) { + const hash = []; + + for (const lightNode of this.lightNodes) { + hash.push(lightNode.getHash()); + } + + this._hash = 'lights-' + hash.join(','); + } + + return this._hash; + } + + setup(builder) { + const context = builder.context; + const lightingModel = context.lightingModel; + + let outgoingLightNode = this.outgoingLightNode; + + if (lightingModel) { + const { lightNodes, totalDiffuseNode, totalSpecularNode } = this; + + context.outgoingLight = outgoingLightNode; + + const stack = builder.addStack(); + + // + + lightingModel.start(context, stack, builder); + + // lights + + for (const lightNode of lightNodes) { + lightNode.build(builder); + } + + // + + lightingModel.indirectDiffuse(context, stack, builder); + lightingModel.indirectSpecular(context, stack, builder); + lightingModel.ambientOcclusion(context, stack, builder); + + // + + const { backdrop, backdropAlpha } = context; + const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = context.reflectedLight; + + let totalDiffuse = directDiffuse.add(indirectDiffuse); + + if (backdrop !== null) { + if (backdropAlpha !== null) { + totalDiffuse = vec3(backdropAlpha.mix(totalDiffuse, backdrop)); + } else { + totalDiffuse = vec3(backdrop); + } + + context.material.transparent = true; + } + + totalDiffuseNode.assign(totalDiffuse); + totalSpecularNode.assign(directSpecular.add(indirectSpecular)); + + outgoingLightNode.assign(totalDiffuseNode.add(totalSpecularNode)); + + // + + lightingModel.finish(context, stack, builder); + + // + + outgoingLightNode = outgoingLightNode.bypass(builder.removeStack()); + } + + return outgoingLightNode; + } + + _getLightNodeById(id) { + for (const lightNode of this.lightNodes) { + if (lightNode.isAnalyticLightNode && lightNode.light.id === id) { + return lightNode; + } + } + + return null; + } + + fromLights(lights = []) { + const lightNodes = []; + + lights = sortLights(lights); + + for (const light of lights) { + let lightNode = this._getLightNodeById(light.id); + + if (lightNode === null) { + const lightClass = light.constructor; + const lightNodeClass = LightNodes.has(lightClass) ? LightNodes.get(lightClass) : AnalyticLightNode; + + lightNode = nodeObject(new lightNodeClass(light)); + } + + lightNodes.push(lightNode); + } + + this.lightNodes = lightNodes; + this._hash = null; + + return this; + } +} + +export default LightsNode; + +export const lights = lights => nodeObject(new LightsNode().fromLights(lights)); +export const lightsNode = nodeProxy(LightsNode); + +export function addLightNode(lightClass, lightNodeClass) { + if (LightNodes.has(lightClass)) { + console.warn(`Redefinition of light node ${lightNodeClass.type}`); + return; + } + + if (typeof lightClass !== 'function') throw new Error(`Light ${lightClass.name} is not a class`); + if (typeof lightNodeClass !== 'function' || !lightNodeClass.type) + throw new Error(`Light node ${lightNodeClass.type} is not a class`); + + LightNodes.set(lightClass, lightNodeClass); +} diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts new file mode 100644 index 000000000..85713cfe6 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Renderer.ts @@ -0,0 +1,1225 @@ +import Animation from './Animation.js'; +import RenderObjects from './RenderObjects.js'; +import Attributes from './Attributes.js'; +import Geometries from './Geometries.js'; +import Info from './Info.js'; +import Pipelines from './Pipelines.js'; +import Bindings from './Bindings.js'; +import RenderLists from './RenderLists.js'; +import RenderContexts from './RenderContexts.js'; +import Textures from './Textures.js'; +import Background from './Background.js'; +import Nodes from './nodes/Nodes.js'; +import Color4 from './Color4.js'; +import ClippingContext from './ClippingContext.js'; +import { + Scene, + Frustum, + Matrix4, + Vector2, + Vector3, + Vector4, + DoubleSide, + BackSide, + FrontSide, + SRGBColorSpace, + NoColorSpace, + NoToneMapping, + LinearFilter, + LinearSRGBColorSpace, + RenderTarget, + HalfFloatType, + RGBAFormat, +} from 'three'; +import { NodeMaterial } from '../../nodes/Nodes.js'; +import QuadMesh from '../../objects/QuadMesh.js'; + +const _scene = new Scene(); +const _drawingBufferSize = new Vector2(); +const _screen = new Vector4(); +const _frustum = new Frustum(); +const _projScreenMatrix = new Matrix4(); +const _vector3 = new Vector3(); +const _quad = new QuadMesh(new NodeMaterial()); + +class Renderer { + constructor(backend, parameters = {}) { + this.isRenderer = true; + + // + + const { logarithmicDepthBuffer = false, alpha = true } = parameters; + + // public + + this.domElement = backend.getDomElement(); + + this.backend = backend; + + this.autoClear = true; + this.autoClearColor = true; + this.autoClearDepth = true; + this.autoClearStencil = true; + + this.alpha = alpha; + + this.logarithmicDepthBuffer = logarithmicDepthBuffer; + + this.outputColorSpace = SRGBColorSpace; + + this.toneMapping = NoToneMapping; + this.toneMappingExposure = 1.0; + + this.sortObjects = true; + + this.depth = true; + this.stencil = false; + + this.clippingPlanes = []; + + this.info = new Info(); + + // nodes + + this.toneMappingNode = null; + + // internals + + this._pixelRatio = 1; + this._width = this.domElement.width; + this._height = this.domElement.height; + + this._viewport = new Vector4(0, 0, this._width, this._height); + this._scissor = new Vector4(0, 0, this._width, this._height); + this._scissorTest = false; + + this._attributes = null; + this._geometries = null; + this._nodes = null; + this._animation = null; + this._bindings = null; + this._objects = null; + this._pipelines = null; + this._renderLists = null; + this._renderContexts = null; + this._textures = null; + this._background = null; + + this._currentRenderContext = null; + + this._opaqueSort = null; + this._transparentSort = null; + + this._frameBufferTarget = null; + + const alphaClear = this.alpha === true ? 0 : 1; + + this._clearColor = new Color4(0, 0, 0, alphaClear); + this._clearDepth = 1; + this._clearStencil = 0; + + this._renderTarget = null; + this._activeCubeFace = 0; + this._activeMipmapLevel = 0; + + this._renderObjectFunction = null; + this._currentRenderObjectFunction = null; + + this._handleObjectFunction = this._renderObjectDirect; + + this._initialized = false; + this._initPromise = null; + + this._compilationPromises = null; + + // backwards compatibility + + this.shadowMap = { + enabled: false, + type: null, + }; + + this.xr = { + enabled: false, + }; + } + + async init() { + if (this._initialized) { + throw new Error('Renderer: Backend has already been initialized.'); + } + + if (this._initPromise !== null) { + return this._initPromise; + } + + this._initPromise = new Promise(async (resolve, reject) => { + const backend = this.backend; + + try { + await backend.init(this); + } catch (error) { + reject(error); + return; + } + + this._nodes = new Nodes(this, backend); + this._animation = new Animation(this._nodes, this.info); + this._attributes = new Attributes(backend); + this._background = new Background(this, this._nodes); + this._geometries = new Geometries(this._attributes, this.info); + this._textures = new Textures(this, backend, this.info); + this._pipelines = new Pipelines(backend, this._nodes); + this._bindings = new Bindings( + backend, + this._nodes, + this._textures, + this._attributes, + this._pipelines, + this.info, + ); + this._objects = new RenderObjects( + this, + this._nodes, + this._geometries, + this._pipelines, + this._bindings, + this.info, + ); + this._renderLists = new RenderLists(); + this._renderContexts = new RenderContexts(); + + // + + this._initialized = true; + + resolve(); + }); + + return this._initPromise; + } + + get coordinateSystem() { + return this.backend.coordinateSystem; + } + + async compileAsync(scene, camera, targetScene = null) { + if (this._initialized === false) await this.init(); + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + const previousCompilationPromises = this._compilationPromises; + + // + + const sceneRef = scene.isScene === true ? scene : _scene; + + if (targetScene === null) targetScene = scene; + + const renderTarget = this._renderTarget; + const renderContext = this._renderContexts.get(targetScene, camera, renderTarget); + const activeMipmapLevel = this._activeMipmapLevel; + + const compilationPromises = []; + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this.renderObject; + + this._handleObjectFunction = this._createObjectPipeline; + + this._compilationPromises = compilationPromises; + + nodeFrame.renderId++; + + // + + nodeFrame.update(); + + // + + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + + if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal(this, camera); + + // + + sceneRef.onBeforeRender(this, scene, camera, renderTarget); + + // + + const renderList = this._renderLists.get(scene, camera); + renderList.begin(); + + this._projectObject(scene, camera, 0, renderList); + + // include lights from target scene + if (targetScene !== scene) { + targetScene.traverseVisible(function (object) { + if (object.isLight && object.layers.test(camera.layers)) { + renderList.pushLight(object); + } + }); + } + + renderList.finish(); + + // + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); + + const renderTargetData = this._textures.get(renderTarget); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + } else { + renderContext.textures = null; + renderContext.depthTexture = null; + } + + // + + this._nodes.updateScene(sceneRef); + + // + + this._background.update(sceneRef, renderList, renderContext); + + // process render lists + + const opaqueObjects = renderList.opaque; + const transparentObjects = renderList.transparent; + const lightsNode = renderList.lightsNode; + + if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); + if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + this._compilationPromises = previousCompilationPromises; + + this._handleObjectFunction = this._renderObjectDirect; + + // wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete + + await Promise.all(compilationPromises); + } + + async renderAsync(scene, camera) { + if (this._initialized === false) await this.init(); + + const renderContext = this._renderScene(scene, camera); + + await this.backend.resolveTimestampAsync(renderContext, 'render'); + } + + render(scene, camera) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', + ); + + return this.renderAsync(scene, camera); + } + + this._renderScene(scene, camera); + } + + _getFrameBufferTarget() { + const { currentColorSpace } = this; + + const useToneMapping = + this._renderTarget === null && (this.toneMapping !== NoToneMapping || this.toneMappingNode !== null); + const useColorSpace = currentColorSpace !== LinearSRGBColorSpace && currentColorSpace !== NoColorSpace; + + if (useToneMapping === false && useColorSpace === false) return null; + + const { width, height } = this.getDrawingBufferSize(_drawingBufferSize); + const { depth, stencil } = this; + + let frameBufferTarget = this._frameBufferTarget; + + if (frameBufferTarget === null) { + frameBufferTarget = new RenderTarget(width, height, { + depthBuffer: depth, + stencilBuffer: stencil, + type: HalfFloatType, // FloatType + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + generateMipmaps: false, + minFilter: LinearFilter, + magFilter: LinearFilter, + samples: this.backend.parameters.antialias ? 4 : 0, + }); + + frameBufferTarget.isPostProcessingRenderTarget = true; + + this._frameBufferTarget = frameBufferTarget; + } + + frameBufferTarget.depthBuffer = depth; + frameBufferTarget.stencilBuffer = stencil; + frameBufferTarget.setSize(width, height); + frameBufferTarget.viewport.copy(this._viewport); + frameBufferTarget.scissor.copy(this._scissor); + frameBufferTarget.viewport.multiplyScalar(this._pixelRatio); + frameBufferTarget.scissor.multiplyScalar(this._pixelRatio); + frameBufferTarget.scissorTest = this._scissorTest; + + return frameBufferTarget; + } + + _renderScene(scene, camera, useFrameBufferTarget = true) { + const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + + // + + const sceneRef = scene.isScene === true ? scene : _scene; + + const outputRenderTarget = this._renderTarget; + + const activeCubeFace = this._activeCubeFace; + const activeMipmapLevel = this._activeMipmapLevel; + + // + + let renderTarget; + + if (frameBufferTarget !== null) { + renderTarget = frameBufferTarget; + + this.setRenderTarget(renderTarget); + } else { + renderTarget = outputRenderTarget; + } + + // + + const renderContext = this._renderContexts.get(scene, camera, renderTarget); + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; + + // + + this.info.calls++; + this.info.render.calls++; + + nodeFrame.renderId = this.info.calls; + + // + + const coordinateSystem = this.coordinateSystem; + + if (camera.coordinateSystem !== coordinateSystem) { + camera.coordinateSystem = coordinateSystem; + + camera.updateProjectionMatrix(); + } + + // + + if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld(); + + if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld(); + + // + + let viewport = this._viewport; + let scissor = this._scissor; + let pixelRatio = this._pixelRatio; + + if (renderTarget !== null) { + viewport = renderTarget.viewport; + scissor = renderTarget.scissor; + pixelRatio = 1; + } + + this.getDrawingBufferSize(_drawingBufferSize); + + _screen.set(0, 0, _drawingBufferSize.width, _drawingBufferSize.height); + + const minDepth = viewport.minDepth === undefined ? 0 : viewport.minDepth; + const maxDepth = viewport.maxDepth === undefined ? 1 : viewport.maxDepth; + + renderContext.viewportValue.copy(viewport).multiplyScalar(pixelRatio).floor(); + renderContext.viewportValue.width >>= activeMipmapLevel; + renderContext.viewportValue.height >>= activeMipmapLevel; + renderContext.viewportValue.minDepth = minDepth; + renderContext.viewportValue.maxDepth = maxDepth; + renderContext.viewport = renderContext.viewportValue.equals(_screen) === false; + + renderContext.scissorValue.copy(scissor).multiplyScalar(pixelRatio).floor(); + renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals(_screen) === false; + renderContext.scissorValue.width >>= activeMipmapLevel; + renderContext.scissorValue.height >>= activeMipmapLevel; + + if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal(this, camera); + + // + + sceneRef.onBeforeRender(this, scene, camera, renderTarget); + + // + + _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + _frustum.setFromProjectionMatrix(_projScreenMatrix, coordinateSystem); + + const renderList = this._renderLists.get(scene, camera); + renderList.begin(); + + this._projectObject(scene, camera, 0, renderList); + + renderList.finish(); + + if (this.sortObjects === true) { + renderList.sort(this._opaqueSort, this._transparentSort); + } + + // + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); + + const renderTargetData = this._textures.get(renderTarget); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + renderContext.width = renderTargetData.width; + renderContext.height = renderTargetData.height; + renderContext.renderTarget = renderTarget; + renderContext.depth = renderTarget.depthBuffer; + renderContext.stencil = renderTarget.stencilBuffer; + } else { + renderContext.textures = null; + renderContext.depthTexture = null; + renderContext.width = this.domElement.width; + renderContext.height = this.domElement.height; + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + } + + renderContext.width >>= activeMipmapLevel; + renderContext.height >>= activeMipmapLevel; + renderContext.activeCubeFace = activeCubeFace; + renderContext.activeMipmapLevel = activeMipmapLevel; + renderContext.occlusionQueryCount = renderList.occlusionQueryCount; + + // + + this._nodes.updateScene(sceneRef); + + // + + this._background.update(sceneRef, renderList, renderContext); + + // + + this.backend.beginRender(renderContext); + + // process render lists + + const opaqueObjects = renderList.opaque; + const transparentObjects = renderList.transparent; + const lightsNode = renderList.lightsNode; + + if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); + if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); + + // finish render pass + + this.backend.finishRender(renderContext); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + + // + + if (frameBufferTarget !== null) { + this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); + + _quad.material.fragmentNode = this._nodes.getOutputNode(renderTarget.texture); + + this._renderScene(_quad, _quad.camera, false); + } + + // + + sceneRef.onAfterRender(this, scene, camera, renderTarget); + + // + + return renderContext; + } + + getMaxAnisotropy() { + return this.backend.getMaxAnisotropy(); + } + + getActiveCubeFace() { + return this._activeCubeFace; + } + + getActiveMipmapLevel() { + return this._activeMipmapLevel; + } + + async setAnimationLoop(callback) { + if (this._initialized === false) await this.init(); + + this._animation.setAnimationLoop(callback); + } + + getArrayBuffer(attribute) { + // @deprecated, r155 + + console.warn('THREE.Renderer: getArrayBuffer() is deprecated. Use getArrayBufferAsync() instead.'); + + return this.getArrayBufferAsync(attribute); + } + + async getArrayBufferAsync(attribute) { + return await this.backend.getArrayBufferAsync(attribute); + } + + getContext() { + return this.backend.getContext(); + } + + getPixelRatio() { + return this._pixelRatio; + } + + getDrawingBufferSize(target) { + return target.set(this._width * this._pixelRatio, this._height * this._pixelRatio).floor(); + } + + getSize(target) { + return target.set(this._width, this._height); + } + + setPixelRatio(value = 1) { + this._pixelRatio = value; + + this.setSize(this._width, this._height, false); + } + + setDrawingBufferSize(width, height, pixelRatio) { + this._width = width; + this._height = height; + + this._pixelRatio = pixelRatio; + + this.domElement.width = Math.floor(width * pixelRatio); + this.domElement.height = Math.floor(height * pixelRatio); + + this.setViewport(0, 0, width, height); + + if (this._initialized) this.backend.updateSize(); + } + + setSize(width, height, updateStyle = true) { + this._width = width; + this._height = height; + + this.domElement.width = Math.floor(width * this._pixelRatio); + this.domElement.height = Math.floor(height * this._pixelRatio); + + if (updateStyle === true) { + this.domElement.style.width = width + 'px'; + this.domElement.style.height = height + 'px'; + } + + this.setViewport(0, 0, width, height); + + if (this._initialized) this.backend.updateSize(); + } + + setOpaqueSort(method) { + this._opaqueSort = method; + } + + setTransparentSort(method) { + this._transparentSort = method; + } + + getScissor(target) { + const scissor = this._scissor; + + target.x = scissor.x; + target.y = scissor.y; + target.width = scissor.width; + target.height = scissor.height; + + return target; + } + + setScissor(x, y, width, height) { + const scissor = this._scissor; + + if (x.isVector4) { + scissor.copy(x); + } else { + scissor.set(x, y, width, height); + } + } + + getScissorTest() { + return this._scissorTest; + } + + setScissorTest(boolean) { + this._scissorTest = boolean; + + this.backend.setScissorTest(boolean); + } + + getViewport(target) { + return target.copy(this._viewport); + } + + setViewport(x, y, width, height, minDepth = 0, maxDepth = 1) { + const viewport = this._viewport; + + if (x.isVector4) { + viewport.copy(x); + } else { + viewport.set(x, y, width, height); + } + + viewport.minDepth = minDepth; + viewport.maxDepth = maxDepth; + } + + getClearColor(target) { + return target.copy(this._clearColor); + } + + setClearColor(color, alpha = 1) { + this._clearColor.set(color); + this._clearColor.a = alpha; + } + + getClearAlpha() { + return this._clearColor.a; + } + + setClearAlpha(alpha) { + this._clearColor.a = alpha; + } + + getClearDepth() { + return this._clearDepth; + } + + setClearDepth(depth) { + this._clearDepth = depth; + } + + getClearStencil() { + return this._clearStencil; + } + + setClearStencil(stencil) { + this._clearStencil = stencil; + } + + isOccluded(object) { + const renderContext = this._currentRenderContext; + + return renderContext && this.backend.isOccluded(renderContext, object); + } + + clear(color = true, depth = true, stencil = true) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead.', + ); + + return this.clearAsync(color, depth, stencil); + } + + const renderTarget = this._renderTarget || this._getFrameBufferTarget(); + + let renderTargetData = null; + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget); + + renderTargetData = this._textures.get(renderTarget); + } + + this.backend.clear(color, depth, stencil, renderTargetData); + } + + clearColor() { + return this.clear(true, false, false); + } + + clearDepth() { + return this.clear(false, true, false); + } + + clearStencil() { + return this.clear(false, false, true); + } + + async clearAsync(color = true, depth = true, stencil = true) { + if (this._initialized === false) await this.init(); + + this.clear(color, depth, stencil); + } + + clearColorAsync() { + return this.clearAsync(true, false, false); + } + + clearDepthAsync() { + return this.clearAsync(false, true, false); + } + + clearStencilAsync() { + return this.clearAsync(false, false, true); + } + + get currentColorSpace() { + const renderTarget = this._renderTarget; + + if (renderTarget !== null) { + const texture = renderTarget.texture; + + return (Array.isArray(texture) ? texture[0] : texture).colorSpace; + } + + return this.outputColorSpace; + } + + dispose() { + this.info.dispose(); + + this._animation.dispose(); + this._objects.dispose(); + this._pipelines.dispose(); + this._nodes.dispose(); + this._bindings.dispose(); + this._renderLists.dispose(); + this._renderContexts.dispose(); + this._textures.dispose(); + + this.setRenderTarget(null); + this.setAnimationLoop(null); + } + + setRenderTarget(renderTarget, activeCubeFace = 0, activeMipmapLevel = 0) { + this._renderTarget = renderTarget; + this._activeCubeFace = activeCubeFace; + this._activeMipmapLevel = activeMipmapLevel; + } + + getRenderTarget() { + return this._renderTarget; + } + + setRenderObjectFunction(renderObjectFunction) { + this._renderObjectFunction = renderObjectFunction; + } + + getRenderObjectFunction() { + return this._renderObjectFunction; + } + + async computeAsync(computeNodes) { + if (this._initialized === false) await this.init(); + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + + // + + this.info.calls++; + this.info.compute.calls++; + this.info.compute.computeCalls++; + + nodeFrame.renderId = this.info.calls; + + // + + const backend = this.backend; + const pipelines = this._pipelines; + const bindings = this._bindings; + const nodes = this._nodes; + const computeList = Array.isArray(computeNodes) ? computeNodes : [computeNodes]; + + if (computeList[0] === undefined || computeList[0].isComputeNode !== true) { + throw new Error('THREE.Renderer: .compute() expects a ComputeNode.'); + } + + backend.beginCompute(computeNodes); + + for (const computeNode of computeList) { + // onInit + + if (pipelines.has(computeNode) === false) { + const dispose = () => { + computeNode.removeEventListener('dispose', dispose); + + pipelines.delete(computeNode); + bindings.delete(computeNode); + nodes.delete(computeNode); + }; + + computeNode.addEventListener('dispose', dispose); + + // + + computeNode.onInit({ renderer: this }); + } + + nodes.updateForCompute(computeNode); + bindings.updateForCompute(computeNode); + + const computeBindings = bindings.getForCompute(computeNode); + const computePipeline = pipelines.getForCompute(computeNode, computeBindings); + + backend.compute(computeNodes, computeNode, computeBindings, computePipeline); + } + + backend.finishCompute(computeNodes); + + await this.backend.resolveTimestampAsync(computeNodes, 'compute'); + + // + + nodeFrame.renderId = previousRenderId; + } + + async hasFeatureAsync(name) { + if (this._initialized === false) await this.init(); + + return this.backend.hasFeature(name); + } + + hasFeature(name) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', + ); + + return false; + } + + return this.backend.hasFeature(name); + } + + copyFramebufferToTexture(framebufferTexture) { + const renderContext = this._currentRenderContext; + + this._textures.updateTexture(framebufferTexture); + + this.backend.copyFramebufferToTexture(framebufferTexture, renderContext); + } + + copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0) { + this._textures.updateTexture(srcTexture); + this._textures.updateTexture(dstTexture); + + this.backend.copyTextureToTexture(srcTexture, dstTexture, srcRegion, dstPosition, level); + } + + readRenderTargetPixelsAsync(renderTarget, x, y, width, height, index = 0) { + return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); + } + + _projectObject(object, camera, groupOrder, renderList) { + if (object.visible === false) return; + + const visible = object.layers.test(camera.layers); + + if (visible) { + if (object.isGroup) { + groupOrder = object.renderOrder; + } else if (object.isLOD) { + if (object.autoUpdate === true) object.update(camera); + } else if (object.isLight) { + renderList.pushLight(object); + } else if (object.isSprite) { + if (!object.frustumCulled || _frustum.intersectsSprite(object)) { + if (this.sortObjects === true) { + _vector3.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix); + } + + const geometry = object.geometry; + const material = object.material; + + if (material.visible) { + renderList.push(object, geometry, material, groupOrder, _vector3.z, null); + } + } + } else if (object.isLineLoop) { + console.error( + 'THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.', + ); + } else if (object.isMesh || object.isLine || object.isPoints) { + if (!object.frustumCulled || _frustum.intersectsObject(object)) { + const geometry = object.geometry; + const material = object.material; + + if (this.sortObjects === true) { + if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); + + _vector3 + .copy(geometry.boundingSphere.center) + .applyMatrix4(object.matrixWorld) + .applyMatrix4(_projScreenMatrix); + } + + if (Array.isArray(material)) { + const groups = geometry.groups; + + for (let i = 0, l = groups.length; i < l; i++) { + const group = groups[i]; + const groupMaterial = material[group.materialIndex]; + + if (groupMaterial && groupMaterial.visible) { + renderList.push(object, geometry, groupMaterial, groupOrder, _vector3.z, group); + } + } + } else if (material.visible) { + renderList.push(object, geometry, material, groupOrder, _vector3.z, null); + } + } + } + } + + const children = object.children; + + for (let i = 0, l = children.length; i < l; i++) { + this._projectObject(children[i], camera, groupOrder, renderList); + } + } + + _renderObjects(renderList, camera, scene, lightsNode) { + // process renderable objects + + for (let i = 0, il = renderList.length; i < il; i++) { + const renderItem = renderList[i]; + + // @TODO: Add support for multiple materials per object. This will require to extract + // the material from the renderItem object and pass it with its group data to renderObject(). + + const { object, geometry, material, group } = renderItem; + + if (camera.isArrayCamera) { + const cameras = camera.cameras; + + for (let j = 0, jl = cameras.length; j < jl; j++) { + const camera2 = cameras[j]; + + if (object.layers.test(camera2.layers)) { + const vp = camera2.viewport; + const minDepth = vp.minDepth === undefined ? 0 : vp.minDepth; + const maxDepth = vp.maxDepth === undefined ? 1 : vp.maxDepth; + + const viewportValue = this._currentRenderContext.viewportValue; + viewportValue.copy(vp).multiplyScalar(this._pixelRatio).floor(); + viewportValue.minDepth = minDepth; + viewportValue.maxDepth = maxDepth; + + this.backend.updateViewport(this._currentRenderContext); + + this._currentRenderObjectFunction( + object, + scene, + camera2, + geometry, + material, + group, + lightsNode, + ); + } + } + } else { + this._currentRenderObjectFunction(object, scene, camera, geometry, material, group, lightsNode); + } + } + } + + renderObject(object, scene, camera, geometry, material, group, lightsNode) { + let overridePositionNode; + let overrideFragmentNode; + let overrideDepthNode; + + // + + object.onBeforeRender(this, scene, camera, geometry, material, group); + + material.onBeforeRender(this, scene, camera, geometry, material, group); + + // + + if (scene.overrideMaterial !== null) { + const overrideMaterial = scene.overrideMaterial; + + if (material.positionNode && material.positionNode.isNode) { + overridePositionNode = overrideMaterial.positionNode; + overrideMaterial.positionNode = material.positionNode; + } + + if (overrideMaterial.isShadowNodeMaterial) { + overrideMaterial.side = material.shadowSide === null ? material.side : material.shadowSide; + + if (material.depthNode && material.depthNode.isNode) { + overrideDepthNode = overrideMaterial.depthNode; + overrideMaterial.depthNode = material.depthNode; + } + + if (material.shadowNode && material.shadowNode.isNode) { + overrideFragmentNode = overrideMaterial.fragmentNode; + overrideMaterial.fragmentNode = material.shadowNode; + } + + if (this.localClippingEnabled) { + if (material.clipShadows) { + if (overrideMaterial.clippingPlanes !== material.clippingPlanes) { + overrideMaterial.clippingPlanes = material.clippingPlanes; + overrideMaterial.needsUpdate = true; + } + + if (overrideMaterial.clipIntersection !== material.clipIntersection) { + overrideMaterial.clipIntersection = material.clipIntersection; + } + } else if (Array.isArray(overrideMaterial.clippingPlanes)) { + overrideMaterial.clippingPlanes = null; + overrideMaterial.needsUpdate = true; + } + } + } + + material = overrideMaterial; + } + + // + + if (material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false) { + material.side = BackSide; + this._handleObjectFunction(object, material, scene, camera, lightsNode, group, 'backSide'); // create backSide pass id + + material.side = FrontSide; + this._handleObjectFunction(object, material, scene, camera, lightsNode, group); // use default pass id + + material.side = DoubleSide; + } else { + this._handleObjectFunction(object, material, scene, camera, lightsNode, group); + } + + // + + if (overridePositionNode !== undefined) { + scene.overrideMaterial.positionNode = overridePositionNode; + } + + if (overrideDepthNode !== undefined) { + scene.overrideMaterial.depthNode = overrideDepthNode; + } + + if (overrideFragmentNode !== undefined) { + scene.overrideMaterial.fragmentNode = overrideFragmentNode; + } + + // + + object.onAfterRender(this, scene, camera, geometry, material, group); + } + + _renderObjectDirect(object, material, scene, camera, lightsNode, group, passId) { + const renderObject = this._objects.get( + object, + material, + scene, + camera, + lightsNode, + this._currentRenderContext, + passId, + ); + renderObject.drawRange = group || object.geometry.drawRange; + + // + + this._nodes.updateBefore(renderObject); + + // + + object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld); + object.normalMatrix.getNormalMatrix(object.modelViewMatrix); + + // + + this._nodes.updateForRender(renderObject); + this._geometries.updateForRender(renderObject); + this._bindings.updateForRender(renderObject); + this._pipelines.updateForRender(renderObject); + + // + + this.backend.draw(renderObject, this.info); + } + + _createObjectPipeline(object, material, scene, camera, lightsNode, passId) { + const renderObject = this._objects.get( + object, + material, + scene, + camera, + lightsNode, + this._currentRenderContext, + passId, + ); + + // + + this._nodes.updateBefore(renderObject); + + // + + this._nodes.updateForRender(renderObject); + this._geometries.updateForRender(renderObject); + this._bindings.updateForRender(renderObject); + + this._pipelines.getForRender(renderObject, this._compilationPromises); + } + + get compute() { + return this.computeAsync; + } + + get compile() { + return this.compileAsync; + } +} + +export default Renderer; diff --git a/examples-jsm/index.js b/examples-jsm/index.js index ad6e990dc..72e1b224b 100644 --- a/examples-jsm/index.js +++ b/examples-jsm/index.js @@ -9,6 +9,9 @@ const files = [ 'nodes/core/NodeFrame', 'nodes/core/NodeParser', 'nodes/core/constants', + 'nodes/fog/FogNode', + 'nodes/lighting/EnvironmentNode', + 'nodes/lighting/LightsNode', 'renderers/common/Renderer', ]; From 31b7dc29dafe0174700dbb7b0d6051621a4fe259 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 21:00:02 -0400 Subject: [PATCH 14/18] Update patch and delete examples --- examples-jsm/changes.patch | 44 +- examples-jsm/examples/nodes/core/Node.ts | 411 ------ .../examples/nodes/core/NodeBuilder.ts | 1011 -------------- examples-jsm/examples/nodes/core/NodeFrame.ts | 101 -- .../examples/nodes/core/NodeParser.ts | 7 - examples-jsm/examples/nodes/core/constants.ts | 28 - examples-jsm/examples/nodes/fog/FogNode.ts | 38 - .../nodes/lighting/EnvironmentNode.ts | 118 -- .../examples/nodes/lighting/LightsNode.ts | 157 --- .../examples/renderers/common/Renderer.ts | 1225 ----------------- 10 files changed, 41 insertions(+), 3099 deletions(-) delete mode 100644 examples-jsm/examples/nodes/core/Node.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeFrame.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeParser.ts delete mode 100644 examples-jsm/examples/nodes/core/constants.ts delete mode 100644 examples-jsm/examples/nodes/fog/FogNode.ts delete mode 100644 examples-jsm/examples/nodes/lighting/EnvironmentNode.ts delete mode 100644 examples-jsm/examples/nodes/lighting/LightsNode.ts delete mode 100644 examples-jsm/examples/renderers/common/Renderer.ts diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index ae4371476..b25ac2d31 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -151,7 +151,7 @@ index 438c44dd..dd88fd34 100644 if (this !== refNode) { diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts -index ebdc13ff..179f68ec 100644 +index ebdc13ff..1304d0da 100644 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts @@ -30,6 +30,11 @@ import { @@ -166,17 +166,20 @@ index ebdc13ff..179f68ec 100644 } from 'three'; import { stack } from './StackNode.js'; -@@ -39,6 +44,9 @@ import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; +@@ -39,6 +44,12 @@ import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; import ChainMap from '../../renderers/common/ChainMap.js'; import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; +import Renderer from '../../renderers/common/Renderer.js'; +import NodeParser from './NodeParser.js'; +import Node from './Node.js'; ++import LightsNode from '../lighting/LightsNode.js'; ++import EnvironmentNode from '../lighting/EnvironmentNode.js'; ++import FogNode from '../fog/FogNode.js'; const uniformsGroupCache = new ChainMap(); -@@ -67,10 +75,28 @@ const toFloat = value => { +@@ -67,10 +78,63 @@ const toFloat = value => { }; class NodeBuilder { @@ -193,6 +196,41 @@ index ebdc13ff..179f68ec 100644 + updateBeforeNodes: Node[]; + hashNodes: { [hash: string]: Node }; + ++ lightsNode: LightsNode | null; ++ environmentNode: EnvironmentNode | null; ++ fogNode: FogNode | null; ++ ++ // TODO ++ // clippingContext ++ ++ vertexShader: string | null; ++ fragmentShader: string | null; ++ computeShader: string | null; ++ ++ // TODO ++ // flowNodes ++ // flowCode ++ // uniforms ++ // structs ++ // bindings ++ bindingsOffset: { vertex: number; fragment: number; compute: number }; ++ // TODO ++ // bindingsArray ++ // attributes ++ // bufferAttributes ++ // varyings ++ // codes ++ // vars ++ flow: { code: string }; ++ // TODO ++ // chaining ++ // stack ++ // stacks ++ tab: string; ++ ++ // TODO ++ // currentFunctionNode ++ + constructor( + object: Object3D, + renderer: Renderer, diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts deleted file mode 100644 index 438c44dd1..000000000 --- a/examples-jsm/examples/nodes/core/Node.ts +++ /dev/null @@ -1,411 +0,0 @@ -import { EventDispatcher } from 'three'; -import { NodeUpdateType } from './constants.js'; -import { getNodeChildren, getCacheKey } from './NodeUtils.js'; -import { MathUtils } from 'three'; - -const NodeClasses = new Map(); - -let _nodeId = 0; - -class Node extends EventDispatcher { - constructor(nodeType = null) { - super(); - - this.nodeType = nodeType; - - this.updateType = NodeUpdateType.NONE; - this.updateBeforeType = NodeUpdateType.NONE; - - this.uuid = MathUtils.generateUUID(); - - this.version = 0; - - this._cacheKey = null; - this._cacheKeyVersion = 0; - - this.isNode = true; - - Object.defineProperty(this, 'id', { value: _nodeId++ }); - } - - set needsUpdate(value) { - if (value === true) { - this.version++; - } - } - - get type() { - return this.constructor.type; - } - - onUpdate(callback, updateType) { - this.updateType = updateType; - this.update = callback.bind(this.getSelf()); - - return this; - } - - onFrameUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.FRAME); - } - - onRenderUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.RENDER); - } - - onObjectUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.OBJECT); - } - - onReference(callback) { - this.updateReference = callback.bind(this.getSelf()); - - return this; - } - - getSelf() { - // Returns non-node object. - - return this.self || this; - } - - updateReference(/*state*/) { - return this; - } - - isGlobal(/*builder*/) { - return false; - } - - *getChildren() { - for (const { childNode } of getNodeChildren(this)) { - yield childNode; - } - } - - dispose() { - this.dispatchEvent({ type: 'dispose' }); - } - - traverse(callback) { - callback(this); - - for (const childNode of this.getChildren()) { - childNode.traverse(callback); - } - } - - getCacheKey(force = false) { - force = force || this.version !== this._cacheKeyVersion; - - if (force === true || this._cacheKey === null) { - this._cacheKey = getCacheKey(this, force); - this._cacheKeyVersion = this.version; - } - - return this._cacheKey; - } - - getHash(/*builder*/) { - return this.uuid; - } - - getUpdateType() { - return this.updateType; - } - - getUpdateBeforeType() { - return this.updateBeforeType; - } - - getElementType(builder) { - const type = this.getNodeType(builder); - const elementType = builder.getElementType(type); - - return elementType; - } - - getNodeType(builder) { - const nodeProperties = builder.getNodeProperties(this); - - if (nodeProperties.outputNode) { - return nodeProperties.outputNode.getNodeType(builder); - } - - return this.nodeType; - } - - getShared(builder) { - const hash = this.getHash(builder); - const nodeFromHash = builder.getNodeFromHash(hash); - - return nodeFromHash || this; - } - - setup(builder) { - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of this.getChildren()) { - nodeProperties['_node' + childNode.id] = childNode; - } - - // return a outputNode if exists - return null; - } - - construct(builder) { - // @deprecated, r157 - - console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); - - return this.setup(builder); - } - - increaseUsage(builder) { - const nodeData = builder.getDataFromNode(this); - nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; - - return nodeData.usageCount; - } - - analyze(builder) { - const usageCount = this.increaseUsage(builder); - - if (usageCount === 1) { - // node flow children - - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of Object.values(nodeProperties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } - - generate(builder, output) { - const { outputNode } = builder.getNodeProperties(this); - - if (outputNode && outputNode.isNode === true) { - return outputNode.build(builder, output); - } - } - - updateBefore(/*frame*/) { - console.warn('Abstract function.'); - } - - update(/*frame*/) { - console.warn('Abstract function.'); - } - - build(builder, output = null) { - const refNode = this.getShared(builder); - - if (this !== refNode) { - return refNode.build(builder, output); - } - - builder.addNode(this); - builder.addChain(this); - - /* Build stages expected results: - - "setup" -> Node - - "analyze" -> null - - "generate" -> String - */ - let result = null; - - const buildStage = builder.getBuildStage(); - - if (buildStage === 'setup') { - this.updateReference(builder); - - const properties = builder.getNodeProperties(this); - - if (properties.initialized !== true || builder.context.tempRead === false) { - const stackNodesBeforeSetup = builder.stack.nodes.length; - - properties.initialized = true; - properties.outputNode = this.setup(builder); - - if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { - properties.outputNode = builder.stack; - } - - for (const childNode of Object.values(properties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } else if (buildStage === 'analyze') { - this.analyze(builder); - } else if (buildStage === 'generate') { - const isGenerateOnce = this.generate.length === 1; - - if (isGenerateOnce) { - const type = this.getNodeType(builder); - const nodeData = builder.getDataFromNode(this); - - result = nodeData.snippet; - - if (result === undefined /*|| builder.context.tempRead === false*/) { - result = this.generate(builder) || ''; - - nodeData.snippet = result; - } - - result = builder.format(result, type, output); - } else { - result = this.generate(builder, output) || ''; - } - } - - builder.removeChain(this); - - return result; - } - - getSerializeChildren() { - return getNodeChildren(this); - } - - serialize(json) { - const nodeChildren = this.getSerializeChildren(); - - const inputNodes = {}; - - for (const { property, index, childNode } of nodeChildren) { - if (index !== undefined) { - if (inputNodes[property] === undefined) { - inputNodes[property] = Number.isInteger(index) ? [] : {}; - } - - inputNodes[property][index] = childNode.toJSON(json.meta).uuid; - } else { - inputNodes[property] = childNode.toJSON(json.meta).uuid; - } - } - - if (Object.keys(inputNodes).length > 0) { - json.inputNodes = inputNodes; - } - } - - deserialize(json) { - if (json.inputNodes !== undefined) { - const nodes = json.meta.nodes; - - for (const property in json.inputNodes) { - if (Array.isArray(json.inputNodes[property])) { - const inputArray = []; - - for (const uuid of json.inputNodes[property]) { - inputArray.push(nodes[uuid]); - } - - this[property] = inputArray; - } else if (typeof json.inputNodes[property] === 'object') { - const inputObject = {}; - - for (const subProperty in json.inputNodes[property]) { - const uuid = json.inputNodes[property][subProperty]; - - inputObject[subProperty] = nodes[uuid]; - } - - this[property] = inputObject; - } else { - const uuid = json.inputNodes[property]; - - this[property] = nodes[uuid]; - } - } - } - } - - toJSON(meta) { - const { uuid, type } = this; - const isRoot = meta === undefined || typeof meta === 'string'; - - if (isRoot) { - meta = { - textures: {}, - images: {}, - nodes: {}, - }; - } - - // serialize - - let data = meta.nodes[uuid]; - - if (data === undefined) { - data = { - uuid, - type, - meta, - metadata: { - version: 4.6, - type: 'Node', - generator: 'Node.toJSON', - }, - }; - - if (isRoot !== true) meta.nodes[data.uuid] = data; - - this.serialize(data); - - delete data.meta; - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache(cache) { - const values = []; - - for (const key in cache) { - const data = cache[key]; - delete data.metadata; - values.push(data); - } - - return values; - } - - if (isRoot) { - const textures = extractFromCache(meta.textures); - const images = extractFromCache(meta.images); - const nodes = extractFromCache(meta.nodes); - - if (textures.length > 0) data.textures = textures; - if (images.length > 0) data.images = images; - if (nodes.length > 0) data.nodes = nodes; - } - - return data; - } -} - -export default Node; - -export function addNodeClass(type, nodeClass) { - if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); - if (NodeClasses.has(type)) { - console.warn(`Redefinition of node class ${type}`); - return; - } - - NodeClasses.set(type, nodeClass); - nodeClass.type = type; -} - -export function createNodeFromType(type) { - const Class = NodeClasses.get(type); - - if (Class !== undefined) { - return new Class(); - } -} diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts deleted file mode 100644 index ebdc13ff1..000000000 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ /dev/null @@ -1,1011 +0,0 @@ -import NodeUniform from './NodeUniform.js'; -import NodeAttribute from './NodeAttribute.js'; -import NodeVarying from './NodeVarying.js'; -import NodeVar from './NodeVar.js'; -import NodeCode from './NodeCode.js'; -import NodeKeywords from './NodeKeywords.js'; -import NodeCache from './NodeCache.js'; -import ParameterNode from './ParameterNode.js'; -import FunctionNode from '../code/FunctionNode.js'; -import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; -import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; - -import { - FloatNodeUniform, - Vector2NodeUniform, - Vector3NodeUniform, - Vector4NodeUniform, - ColorNodeUniform, - Matrix3NodeUniform, - Matrix4NodeUniform, -} from '../../renderers/common/nodes/NodeUniform.js'; - -import { - REVISION, - RenderTarget, - Color, - Vector2, - Vector3, - Vector4, - IntType, - UnsignedIntType, - Float16BufferAttribute, -} from 'three'; - -import { stack } from './StackNode.js'; -import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; - -import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; -import ChainMap from '../../renderers/common/ChainMap.js'; - -import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; - -const uniformsGroupCache = new ChainMap(); - -const typeFromLength = new Map([ - [2, 'vec2'], - [3, 'vec3'], - [4, 'vec4'], - [9, 'mat3'], - [16, 'mat4'], -]); - -const typeFromArray = new Map([ - [Int8Array, 'int'], - [Int16Array, 'int'], - [Int32Array, 'int'], - [Uint8Array, 'uint'], - [Uint16Array, 'uint'], - [Uint32Array, 'uint'], - [Float32Array, 'float'], -]); - -const toFloat = value => { - value = Number(value); - - return value + (value % 1 ? '' : '.0'); -}; - -class NodeBuilder { - constructor(object, renderer, parser, scene = null, material = null) { - this.object = object; - this.material = material || (object && object.material) || null; - this.geometry = (object && object.geometry) || null; - this.renderer = renderer; - this.parser = parser; - this.scene = scene; - - this.nodes = []; - this.updateNodes = []; - this.updateBeforeNodes = []; - this.hashNodes = {}; - - this.lightsNode = null; - this.environmentNode = null; - this.fogNode = null; - - this.clippingContext = null; - - this.vertexShader = null; - this.fragmentShader = null; - this.computeShader = null; - - this.flowNodes = { vertex: [], fragment: [], compute: [] }; - this.flowCode = { vertex: '', fragment: '', compute: [] }; - this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; - this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; - this.bindings = { vertex: [], fragment: [], compute: [] }; - this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; - this.bindingsArray = null; - this.attributes = []; - this.bufferAttributes = []; - this.varyings = []; - this.codes = {}; - this.vars = {}; - this.flow = { code: '' }; - this.chaining = []; - this.stack = stack(); - this.stacks = []; - this.tab = '\t'; - - this.currentFunctionNode = null; - - this.context = { - keywords: new NodeKeywords(), - material: this.material, - }; - - this.cache = new NodeCache(); - this.globalCache = this.cache; - - this.flowsData = new WeakMap(); - - this.shaderStage = null; - this.buildStage = null; - } - - createRenderTarget(width, height, options) { - return new RenderTarget(width, height, options); - } - - createCubeRenderTarget(size, options) { - return new CubeRenderTarget(size, options); - } - - createPMREMGenerator() { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return new PMREMGenerator(this.renderer); - } - - includes(node) { - return this.nodes.includes(node); - } - - _getSharedBindings(bindings) { - const shared = []; - - for (const binding of bindings) { - if (binding.shared === true) { - // nodes is the chainmap key - const nodes = binding.getNodes(); - - let sharedBinding = uniformsGroupCache.get(nodes); - - if (sharedBinding === undefined) { - uniformsGroupCache.set(nodes, binding); - - sharedBinding = binding; - } - - shared.push(sharedBinding); - } else { - shared.push(binding); - } - } - - return shared; - } - - getBindings() { - let bindingsArray = this.bindingsArray; - - if (bindingsArray === null) { - const bindings = this.bindings; - - this.bindingsArray = bindingsArray = this._getSharedBindings( - this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, - ); - } - - return bindingsArray; - } - - setHashNode(node, hash) { - this.hashNodes[hash] = node; - } - - addNode(node) { - if (this.nodes.includes(node) === false) { - this.nodes.push(node); - - this.setHashNode(node, node.getHash(this)); - } - } - - buildUpdateNodes() { - for (const node of this.nodes) { - const updateType = node.getUpdateType(); - const updateBeforeType = node.getUpdateBeforeType(); - - if (updateType !== NodeUpdateType.NONE) { - this.updateNodes.push(node.getSelf()); - } - - if (updateBeforeType !== NodeUpdateType.NONE) { - this.updateBeforeNodes.push(node); - } - } - } - - get currentNode() { - return this.chaining[this.chaining.length - 1]; - } - - addChain(node) { - /* - if ( this.chaining.indexOf( node ) !== - 1 ) { - - console.warn( 'Recursive node: ', node ); - - } - */ - - this.chaining.push(node); - } - - removeChain(node) { - const lastChain = this.chaining.pop(); - - if (lastChain !== node) { - throw new Error('NodeBuilder: Invalid node chaining!'); - } - } - - getMethod(method) { - return method; - } - - getNodeFromHash(hash) { - return this.hashNodes[hash]; - } - - addFlow(shaderStage, node) { - this.flowNodes[shaderStage].push(node); - - return node; - } - - setContext(context) { - this.context = context; - } - - getContext() { - return this.context; - } - - setCache(cache) { - this.cache = cache; - } - - getCache() { - return this.cache; - } - - isAvailable(/*name*/) { - return false; - } - - getVertexIndex() { - console.warn('Abstract function.'); - } - - getInstanceIndex() { - console.warn('Abstract function.'); - } - - getFrontFacing() { - console.warn('Abstract function.'); - } - - getFragCoord() { - console.warn('Abstract function.'); - } - - isFlipY() { - return false; - } - - generateTexture(/* texture, textureProperty, uvSnippet */) { - console.warn('Abstract function.'); - } - - generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { - console.warn('Abstract function.'); - } - - generateConst(type, value = null) { - if (value === null) { - if (type === 'float' || type === 'int' || type === 'uint') value = 0; - else if (type === 'bool') value = false; - else if (type === 'color') value = new Color(); - else if (type === 'vec2') value = new Vector2(); - else if (type === 'vec3') value = new Vector3(); - else if (type === 'vec4') value = new Vector4(); - } - - if (type === 'float') return toFloat(value); - if (type === 'int') return `${Math.round(value)}`; - if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; - if (type === 'bool') return value ? 'true' : 'false'; - if (type === 'color') - return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; - - const typeLength = this.getTypeLength(type); - - const componentType = this.getComponentType(type); - - const generateConst = value => this.generateConst(componentType, value); - - if (typeLength === 2) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; - } else if (typeLength === 3) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; - } else if (typeLength === 4) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; - } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { - return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; - } else if (typeLength > 4) { - return `${this.getType(type)}()`; - } - - throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); - } - - getType(type) { - if (type === 'color') return 'vec3'; - - return type; - } - - generateMethod(method) { - return method; - } - - hasGeometryAttribute(name) { - return this.geometry && this.geometry.getAttribute(name) !== undefined; - } - - getAttribute(name, type) { - const attributes = this.attributes; - - // find attribute - - for (const attribute of attributes) { - if (attribute.name === name) { - return attribute; - } - } - - // create a new if no exist - - const attribute = new NodeAttribute(name, type); - - attributes.push(attribute); - - return attribute; - } - - getPropertyName(node /*, shaderStage*/) { - return node.name; - } - - isVector(type) { - return /vec\d/.test(type); - } - - isMatrix(type) { - return /mat\d/.test(type); - } - - isReference(type) { - return ( - type === 'void' || - type === 'property' || - type === 'sampler' || - type === 'texture' || - type === 'cubeTexture' || - type === 'storageTexture' || - type === 'texture3D' - ); - } - - needsColorSpaceToLinear(/*texture*/) { - return false; - } - - getComponentTypeFromTexture(texture) { - const type = texture.type; - - if (texture.isDataTexture) { - if (type === IntType) return 'int'; - if (type === UnsignedIntType) return 'uint'; - } - - return 'float'; - } - - getElementType(type) { - if (type === 'mat2') return 'vec2'; - if (type === 'mat3') return 'vec3'; - if (type === 'mat4') return 'vec4'; - - return this.getComponentType(type); - } - - getComponentType(type) { - type = this.getVectorType(type); - - if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; - - const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); - - if (componentType === null) return null; - - if (componentType[1] === 'b') return 'bool'; - if (componentType[1] === 'i') return 'int'; - if (componentType[1] === 'u') return 'uint'; - - return 'float'; - } - - getVectorType(type) { - if (type === 'color') return 'vec3'; - if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') - return 'vec4'; - - return type; - } - - getTypeFromLength(length, componentType = 'float') { - if (length === 1) return componentType; - - const baseType = typeFromLength.get(length); - const prefix = componentType === 'float' ? '' : componentType[0]; - - return prefix + baseType; - } - - getTypeFromArray(array) { - return typeFromArray.get(array.constructor); - } - - getTypeFromAttribute(attribute) { - let dataAttribute = attribute; - - if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; - - const array = dataAttribute.array; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; - - let arrayType; - - if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { - arrayType = this.getTypeFromArray(array); - } - - return this.getTypeFromLength(itemSize, arrayType); - } - - getTypeLength(type) { - const vecType = this.getVectorType(type); - const vecNum = /vec([2-4])/.exec(vecType); - - if (vecNum !== null) return Number(vecNum[1]); - if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; - if (/mat2/.test(type) === true) return 4; - if (/mat3/.test(type) === true) return 9; - if (/mat4/.test(type) === true) return 16; - - return 0; - } - - getVectorFromMatrix(type) { - return type.replace('mat', 'vec'); - } - - changeComponentType(type, newComponentType) { - return this.getTypeFromLength(this.getTypeLength(type), newComponentType); - } - - getIntegerType(type) { - const componentType = this.getComponentType(type); - - if (componentType === 'int' || componentType === 'uint') return type; - - return this.changeComponentType(type, 'int'); - } - - addStack() { - this.stack = stack(this.stack); - - this.stacks.push(getCurrentStack() || this.stack); - setCurrentStack(this.stack); - - return this.stack; - } - - removeStack() { - const lastStack = this.stack; - this.stack = lastStack.parent; - - setCurrentStack(this.stacks.pop()); - - return lastStack; - } - - getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { - cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; - - let nodeData = cache.getNodeData(node); - - if (nodeData === undefined) { - nodeData = {}; - - cache.setNodeData(node, nodeData); - } - - if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; - - return nodeData[shaderStage]; - } - - getNodeProperties(node, shaderStage = 'any') { - const nodeData = this.getDataFromNode(node, shaderStage); - - return nodeData.properties || (nodeData.properties = { outputNode: null }); - } - - getBufferAttributeFromNode(node, type) { - const nodeData = this.getDataFromNode(node); - - let bufferAttribute = nodeData.bufferAttribute; - - if (bufferAttribute === undefined) { - const index = this.uniforms.index++; - - bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); - - this.bufferAttributes.push(bufferAttribute); - - nodeData.bufferAttribute = bufferAttribute; - } - - return bufferAttribute; - } - - getStructTypeFromNode(node, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - if (nodeData.structType === undefined) { - const index = this.structs.index++; - - node.name = `StructType${index}`; - this.structs[shaderStage].push(node); - - nodeData.structType = node; - } - - return node; - } - - getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { - const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); - - let nodeUniform = nodeData.uniform; - - if (nodeUniform === undefined) { - const index = this.uniforms.index++; - - nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); - - this.uniforms[shaderStage].push(nodeUniform); - - nodeData.uniform = nodeUniform; - } - - return nodeUniform; - } - - getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - let nodeVar = nodeData.variable; - - if (nodeVar === undefined) { - const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); - - if (name === null) name = 'nodeVar' + vars.length; - - nodeVar = new NodeVar(name, type); - - vars.push(nodeVar); - - nodeData.variable = nodeVar; - } - - return nodeVar; - } - - getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { - const nodeData = this.getDataFromNode(node, 'any'); - - let nodeVarying = nodeData.varying; - - if (nodeVarying === undefined) { - const varyings = this.varyings; - const index = varyings.length; - - if (name === null) name = 'nodeVarying' + index; - - nodeVarying = new NodeVarying(name, type); - - varyings.push(nodeVarying); - - nodeData.varying = nodeVarying; - } - - return nodeVarying; - } - - getCodeFromNode(node, type, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node); - - let nodeCode = nodeData.code; - - if (nodeCode === undefined) { - const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); - const index = codes.length; - - nodeCode = new NodeCode('nodeCode' + index, type); - - codes.push(nodeCode); - - nodeData.code = nodeCode; - } - - return nodeCode; - } - - addLineFlowCode(code) { - if (code === '') return this; - - code = this.tab + code; - - if (!/;\s*$/.test(code)) { - code = code + ';\n'; - } - - this.flow.code += code; - - return this; - } - - addFlowCode(code) { - this.flow.code += code; - - return this; - } - - addFlowTab() { - this.tab += '\t'; - - return this; - } - - removeFlowTab() { - this.tab = this.tab.slice(0, -1); - - return this; - } - - getFlowData(node /*, shaderStage*/) { - return this.flowsData.get(node); - } - - flowNode(node) { - const output = node.getNodeType(this); - - const flowData = this.flowChildNode(node, output); - - this.flowsData.set(node, flowData); - - return flowData; - } - - buildFunctionNode(shaderNode) { - const fn = new FunctionNode(); - - const previous = this.currentFunctionNode; - - this.currentFunctionNode = fn; - - fn.code = this.buildFunctionCode(shaderNode); - - this.currentFunctionNode = previous; - - return fn; - } - - flowShaderNode(shaderNode) { - const layout = shaderNode.layout; - - let inputs; - - if (shaderNode.isArrayInput) { - inputs = []; - - for (const input of layout.inputs) { - inputs.push(new ParameterNode(input.type, input.name)); - } - } else { - inputs = {}; - - for (const input of layout.inputs) { - inputs[input.name] = new ParameterNode(input.type, input.name); - } - } - - // - - shaderNode.layout = null; - - const callNode = shaderNode.call(inputs); - const flowData = this.flowStagesNode(callNode, layout.type); - - shaderNode.layout = layout; - - return flowData; - } - - flowStagesNode(node, output = null) { - const previousFlow = this.flow; - const previousVars = this.vars; - const previousBuildStage = this.buildStage; - - const flow = { - code: '', - }; - - this.flow = flow; - this.vars = {}; - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - flow.result = node.build(this, output); - } - - flow.vars = this.getVars(this.shaderStage); - - this.flow = previousFlow; - this.vars = previousVars; - this.setBuildStage(previousBuildStage); - - return flow; - } - - getFunctionOperator() { - return null; - } - - flowChildNode(node, output = null) { - const previousFlow = this.flow; - - const flow = { - code: '', - }; - - this.flow = flow; - - flow.result = node.build(this, output); - - this.flow = previousFlow; - - return flow; - } - - flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { - const previousShaderStage = this.shaderStage; - - this.setShaderStage(shaderStage); - - const flowData = this.flowChildNode(node, output); - - if (propertyName !== null) { - flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; - } - - this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; - - this.setShaderStage(previousShaderStage); - - return flowData; - } - - getAttributesArray() { - return this.attributes.concat(this.bufferAttributes); - } - - getAttributes(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVaryings(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVar(type, name) { - return `${this.getType(type)} ${name}`; - } - - getVars(shaderStage) { - let snippet = ''; - - const vars = this.vars[shaderStage]; - - if (vars !== undefined) { - for (const variable of vars) { - snippet += `${this.getVar(variable.type, variable.name)}; `; - } - } - - return snippet; - } - - getUniforms(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getCodes(shaderStage) { - const codes = this.codes[shaderStage]; - - let code = ''; - - if (codes !== undefined) { - for (const nodeCode of codes) { - code += nodeCode.code + '\n'; - } - } - - return code; - } - - getHash() { - return this.vertexShader + this.fragmentShader + this.computeShader; - } - - setShaderStage(shaderStage) { - this.shaderStage = shaderStage; - } - - getShaderStage() { - return this.shaderStage; - } - - setBuildStage(buildStage) { - this.buildStage = buildStage; - } - - getBuildStage() { - return this.buildStage; - } - - buildCode() { - console.warn('Abstract function.'); - } - - build() { - const { object, material } = this; - - if (material !== null) { - NodeMaterial.fromMaterial(material).build(this); - } else { - this.addFlow('compute', object); - } - - // setup() -> stage 1: create possible new nodes and returns an output reference node - // analyze() -> stage 2: analyze nodes to possible optimization and validation - // generate() -> stage 3: generate shader - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - if (this.context.vertex && this.context.vertex.isNode) { - this.flowNodeFromShaderStage('vertex', this.context.vertex); - } - - for (const shaderStage of shaderStages) { - this.setShaderStage(shaderStage); - - const flowNodes = this.flowNodes[shaderStage]; - - for (const node of flowNodes) { - if (buildStage === 'generate') { - this.flowNode(node); - } else { - node.build(this); - } - } - } - } - - this.setBuildStage(null); - this.setShaderStage(null); - - // stage 4: build code for a specific output - - this.buildCode(); - this.buildUpdateNodes(); - - return this; - } - - getNodeUniform(uniformNode, type) { - if (type === 'float') return new FloatNodeUniform(uniformNode); - if (type === 'vec2') return new Vector2NodeUniform(uniformNode); - if (type === 'vec3') return new Vector3NodeUniform(uniformNode); - if (type === 'vec4') return new Vector4NodeUniform(uniformNode); - if (type === 'color') return new ColorNodeUniform(uniformNode); - if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); - if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); - - throw new Error(`Uniform "${type}" not declared.`); - } - - createNodeMaterial(type = 'NodeMaterial') { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return createNodeMaterialFromType(type); - } - - format(snippet, fromType, toType) { - fromType = this.getVectorType(fromType); - toType = this.getVectorType(toType); - - if (fromType === toType || toType === null || this.isReference(toType)) { - return snippet; - } - - const fromTypeLength = this.getTypeLength(fromType); - const toTypeLength = this.getTypeLength(toType); - - if (fromTypeLength > 4) { - // fromType is matrix-like - - // @TODO: ignore for now - - return snippet; - } - - if (toTypeLength > 4 || toTypeLength === 0) { - // toType is matrix-like or unknown - - // @TODO: ignore for now - - return snippet; - } - - if (fromTypeLength === toTypeLength) { - return `${this.getType(toType)}( ${snippet} )`; - } - - if (fromTypeLength > toTypeLength) { - return this.format( - `${snippet}.${'xyz'.slice(0, toTypeLength)}`, - this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), - toType, - ); - } - - if (toTypeLength === 4 && fromTypeLength > 1) { - // toType is vec4-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; - } - - if (fromTypeLength === 2) { - // fromType is vec2-like and toType is vec3-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; - } - - if (fromTypeLength === 1 && toTypeLength > 1 && fromType[0] !== toType[0]) { - // fromType is float-like - - // convert a number value to vector type, e.g: - // vec3( 1u ) -> vec3( float( 1u ) ) - - snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; - } - - return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like - } - - getSignature() { - return `// Three.js r${REVISION} - NodeMaterial System\n`; - } -} - -export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts deleted file mode 100644 index b8e8d37b6..000000000 --- a/examples-jsm/examples/nodes/core/NodeFrame.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { NodeUpdateType } from './constants.js'; - -class NodeFrame { - constructor() { - this.time = 0; - this.deltaTime = 0; - - this.frameId = 0; - this.renderId = 0; - - this.startTime = null; - - this.updateMap = new WeakMap(); - this.updateBeforeMap = new WeakMap(); - - this.renderer = null; - this.material = null; - this.camera = null; - this.object = null; - this.scene = null; - } - - _getMaps(referenceMap, nodeRef) { - let maps = referenceMap.get(nodeRef); - - if (maps === undefined) { - maps = { - renderMap: new WeakMap(), - frameMap: new WeakMap(), - }; - - referenceMap.set(nodeRef, maps); - } - - return maps; - } - - updateBeforeNode(node) { - const updateType = node.getUpdateBeforeType(); - const reference = node.updateReference(this); - - if (updateType === NodeUpdateType.FRAME) { - const { frameMap } = this._getMaps(this.updateBeforeMap, reference); - - if (frameMap.get(reference) !== this.frameId) { - if (node.updateBefore(this) !== false) { - frameMap.set(reference, this.frameId); - } - } - } else if (updateType === NodeUpdateType.RENDER) { - const { renderMap } = this._getMaps(this.updateBeforeMap, reference); - - if (renderMap.get(reference) !== this.renderId) { - if (node.updateBefore(this) !== false) { - renderMap.set(reference, this.renderId); - } - } - } else if (updateType === NodeUpdateType.OBJECT) { - node.updateBefore(this); - } - } - - updateNode(node) { - const updateType = node.getUpdateType(); - const reference = node.updateReference(this); - - if (updateType === NodeUpdateType.FRAME) { - const { frameMap } = this._getMaps(this.updateMap, reference); - - if (frameMap.get(reference) !== this.frameId) { - if (node.update(this) !== false) { - frameMap.set(reference, this.frameId); - } - } - } else if (updateType === NodeUpdateType.RENDER) { - const { renderMap } = this._getMaps(this.updateMap, reference); - - if (renderMap.get(reference) !== this.renderId) { - if (node.update(this) !== false) { - renderMap.set(reference, this.renderId); - } - } - } else if (updateType === NodeUpdateType.OBJECT) { - node.update(this); - } - } - - update() { - this.frameId++; - - if (this.lastTime === undefined) this.lastTime = performance.now(); - - this.deltaTime = (performance.now() - this.lastTime) / 1000; - - this.lastTime = performance.now(); - - this.time += this.deltaTime; - } -} - -export default NodeFrame; diff --git a/examples-jsm/examples/nodes/core/NodeParser.ts b/examples-jsm/examples/nodes/core/NodeParser.ts deleted file mode 100644 index 9849452f1..000000000 --- a/examples-jsm/examples/nodes/core/NodeParser.ts +++ /dev/null @@ -1,7 +0,0 @@ -class NodeParser { - parseFunction(/*source*/) { - console.warn('Abstract function.'); - } -} - -export default NodeParser; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts deleted file mode 100644 index 3b01a9a6d..000000000 --- a/examples-jsm/examples/nodes/core/constants.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const NodeShaderStage = { - VERTEX: 'vertex', - FRAGMENT: 'fragment', -}; - -export const NodeUpdateType = { - NONE: 'none', - FRAME: 'frame', - RENDER: 'render', - OBJECT: 'object', -}; - -export const NodeType = { - BOOLEAN: 'bool', - INTEGER: 'int', - FLOAT: 'float', - VECTOR2: 'vec2', - VECTOR3: 'vec3', - VECTOR4: 'vec4', - MATRIX2: 'mat2', - MATRIX3: 'mat3', - MATRIX4: 'mat4', -}; - -export const defaultShaderStages = ['fragment', 'vertex']; -export const defaultBuildStages = ['setup', 'analyze', 'generate']; -export const shaderStages = [...defaultShaderStages, 'compute']; -export const vectorComponents = ['x', 'y', 'z', 'w']; diff --git a/examples-jsm/examples/nodes/fog/FogNode.ts b/examples-jsm/examples/nodes/fog/FogNode.ts deleted file mode 100644 index 9417df5a5..000000000 --- a/examples-jsm/examples/nodes/fog/FogNode.ts +++ /dev/null @@ -1,38 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { positionView } from '../accessors/PositionNode.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class FogNode extends Node { - constructor(colorNode, factorNode) { - super('float'); - - this.isFogNode = true; - - this.colorNode = colorNode; - this.factorNode = factorNode; - } - - getViewZNode(builder) { - let viewZ; - - const getViewZ = builder.context.getViewZ; - - if (getViewZ !== undefined) { - viewZ = getViewZ(this); - } - - return (viewZ || positionView.z).negate(); - } - - setup() { - return this.factorNode; - } -} - -export default FogNode; - -export const fog = nodeProxy(FogNode); - -addNodeElement('fog', fog); - -addNodeClass('FogNode', FogNode); diff --git a/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts b/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts deleted file mode 100644 index 56f8109c2..000000000 --- a/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts +++ /dev/null @@ -1,118 +0,0 @@ -import LightingNode from './LightingNode.js'; -import { cache } from '../core/CacheNode.js'; -import { context } from '../core/ContextNode.js'; -import { roughness, clearcoatRoughness } from '../core/PropertyNode.js'; -import { cameraViewMatrix } from '../accessors/CameraNode.js'; -import { - transformedClearcoatNormalView, - transformedNormalView, - transformedNormalWorld, -} from '../accessors/NormalNode.js'; -import { positionViewDirection } from '../accessors/PositionNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { float } from '../shadernode/ShaderNode.js'; -import { reference } from '../accessors/ReferenceNode.js'; -import { transformedBentNormalView } from '../accessors/AccessorsUtils.js'; -import { pmremTexture } from '../pmrem/PMREMNode.js'; - -const envNodeCache = new WeakMap(); - -class EnvironmentNode extends LightingNode { - constructor(envNode = null) { - super(); - - this.envNode = envNode; - } - - setup(builder) { - let envNode = this.envNode; - - if (envNode.isTextureNode) { - let cacheEnvNode = envNodeCache.get(envNode.value); - - if (cacheEnvNode === undefined) { - cacheEnvNode = pmremTexture(envNode.value); - - envNodeCache.set(envNode.value, cacheEnvNode); - } - - envNode = cacheEnvNode; - } - - // - - const { material } = builder; - - const envMap = material.envMap; - const intensity = envMap - ? reference('envMapIntensity', 'float', builder.material) - : reference('environmentIntensity', 'float', builder.scene); // @TODO: Add materialEnvIntensity in MaterialNode - - const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0; - const radianceNormalView = useAnisotropy ? transformedBentNormalView : transformedNormalView; - - const radiance = context(envNode, createRadianceContext(roughness, radianceNormalView)).mul(intensity); - const irradiance = context(envNode, createIrradianceContext(transformedNormalWorld)) - .mul(Math.PI) - .mul(intensity); - - const isolateRadiance = cache(radiance); - - // - - builder.context.radiance.addAssign(isolateRadiance); - - builder.context.iblIrradiance.addAssign(irradiance); - - // - - const clearcoatRadiance = builder.context.lightingModel.clearcoatRadiance; - - if (clearcoatRadiance) { - const clearcoatRadianceContext = context( - envNode, - createRadianceContext(clearcoatRoughness, transformedClearcoatNormalView), - ).mul(intensity); - const isolateClearcoatRadiance = cache(clearcoatRadianceContext); - - clearcoatRadiance.addAssign(isolateClearcoatRadiance); - } - } -} - -const createRadianceContext = (roughnessNode, normalViewNode) => { - let reflectVec = null; - - return { - getUV: () => { - if (reflectVec === null) { - reflectVec = positionViewDirection.negate().reflect(normalViewNode); - - // Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane. - reflectVec = roughnessNode.mul(roughnessNode).mix(reflectVec, normalViewNode).normalize(); - - reflectVec = reflectVec.transformDirection(cameraViewMatrix); - } - - return reflectVec; - }, - getTextureLevel: () => { - return roughnessNode; - }, - }; -}; - -const createIrradianceContext = normalWorldNode => { - return { - getUV: () => { - return normalWorldNode; - }, - getTextureLevel: () => { - return float(1.0); - }, - }; -}; - -export default EnvironmentNode; - -addNodeClass('EnvironmentNode', EnvironmentNode); diff --git a/examples-jsm/examples/nodes/lighting/LightsNode.ts b/examples-jsm/examples/nodes/lighting/LightsNode.ts deleted file mode 100644 index b3695ea8b..000000000 --- a/examples-jsm/examples/nodes/lighting/LightsNode.ts +++ /dev/null @@ -1,157 +0,0 @@ -import Node from '../core/Node.js'; -import AnalyticLightNode from './AnalyticLightNode.js'; -import { nodeObject, nodeProxy, vec3 } from '../shadernode/ShaderNode.js'; - -const LightNodes = new WeakMap(); - -const sortLights = lights => { - return lights.sort((a, b) => a.id - b.id); -}; - -class LightsNode extends Node { - constructor(lightNodes = []) { - super('vec3'); - - this.totalDiffuseNode = vec3().temp('totalDiffuse'); - this.totalSpecularNode = vec3().temp('totalSpecular'); - - this.outgoingLightNode = vec3().temp('outgoingLight'); - - this.lightNodes = lightNodes; - - this._hash = null; - } - - get hasLight() { - return this.lightNodes.length > 0; - } - - getHash() { - if (this._hash === null) { - const hash = []; - - for (const lightNode of this.lightNodes) { - hash.push(lightNode.getHash()); - } - - this._hash = 'lights-' + hash.join(','); - } - - return this._hash; - } - - setup(builder) { - const context = builder.context; - const lightingModel = context.lightingModel; - - let outgoingLightNode = this.outgoingLightNode; - - if (lightingModel) { - const { lightNodes, totalDiffuseNode, totalSpecularNode } = this; - - context.outgoingLight = outgoingLightNode; - - const stack = builder.addStack(); - - // - - lightingModel.start(context, stack, builder); - - // lights - - for (const lightNode of lightNodes) { - lightNode.build(builder); - } - - // - - lightingModel.indirectDiffuse(context, stack, builder); - lightingModel.indirectSpecular(context, stack, builder); - lightingModel.ambientOcclusion(context, stack, builder); - - // - - const { backdrop, backdropAlpha } = context; - const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = context.reflectedLight; - - let totalDiffuse = directDiffuse.add(indirectDiffuse); - - if (backdrop !== null) { - if (backdropAlpha !== null) { - totalDiffuse = vec3(backdropAlpha.mix(totalDiffuse, backdrop)); - } else { - totalDiffuse = vec3(backdrop); - } - - context.material.transparent = true; - } - - totalDiffuseNode.assign(totalDiffuse); - totalSpecularNode.assign(directSpecular.add(indirectSpecular)); - - outgoingLightNode.assign(totalDiffuseNode.add(totalSpecularNode)); - - // - - lightingModel.finish(context, stack, builder); - - // - - outgoingLightNode = outgoingLightNode.bypass(builder.removeStack()); - } - - return outgoingLightNode; - } - - _getLightNodeById(id) { - for (const lightNode of this.lightNodes) { - if (lightNode.isAnalyticLightNode && lightNode.light.id === id) { - return lightNode; - } - } - - return null; - } - - fromLights(lights = []) { - const lightNodes = []; - - lights = sortLights(lights); - - for (const light of lights) { - let lightNode = this._getLightNodeById(light.id); - - if (lightNode === null) { - const lightClass = light.constructor; - const lightNodeClass = LightNodes.has(lightClass) ? LightNodes.get(lightClass) : AnalyticLightNode; - - lightNode = nodeObject(new lightNodeClass(light)); - } - - lightNodes.push(lightNode); - } - - this.lightNodes = lightNodes; - this._hash = null; - - return this; - } -} - -export default LightsNode; - -export const lights = lights => nodeObject(new LightsNode().fromLights(lights)); -export const lightsNode = nodeProxy(LightsNode); - -export function addLightNode(lightClass, lightNodeClass) { - if (LightNodes.has(lightClass)) { - console.warn(`Redefinition of light node ${lightNodeClass.type}`); - return; - } - - if (typeof lightClass !== 'function') throw new Error(`Light ${lightClass.name} is not a class`); - if (typeof lightNodeClass !== 'function' || !lightNodeClass.type) - throw new Error(`Light node ${lightNodeClass.type} is not a class`); - - LightNodes.set(lightClass, lightNodeClass); -} diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts deleted file mode 100644 index 85713cfe6..000000000 --- a/examples-jsm/examples/renderers/common/Renderer.ts +++ /dev/null @@ -1,1225 +0,0 @@ -import Animation from './Animation.js'; -import RenderObjects from './RenderObjects.js'; -import Attributes from './Attributes.js'; -import Geometries from './Geometries.js'; -import Info from './Info.js'; -import Pipelines from './Pipelines.js'; -import Bindings from './Bindings.js'; -import RenderLists from './RenderLists.js'; -import RenderContexts from './RenderContexts.js'; -import Textures from './Textures.js'; -import Background from './Background.js'; -import Nodes from './nodes/Nodes.js'; -import Color4 from './Color4.js'; -import ClippingContext from './ClippingContext.js'; -import { - Scene, - Frustum, - Matrix4, - Vector2, - Vector3, - Vector4, - DoubleSide, - BackSide, - FrontSide, - SRGBColorSpace, - NoColorSpace, - NoToneMapping, - LinearFilter, - LinearSRGBColorSpace, - RenderTarget, - HalfFloatType, - RGBAFormat, -} from 'three'; -import { NodeMaterial } from '../../nodes/Nodes.js'; -import QuadMesh from '../../objects/QuadMesh.js'; - -const _scene = new Scene(); -const _drawingBufferSize = new Vector2(); -const _screen = new Vector4(); -const _frustum = new Frustum(); -const _projScreenMatrix = new Matrix4(); -const _vector3 = new Vector3(); -const _quad = new QuadMesh(new NodeMaterial()); - -class Renderer { - constructor(backend, parameters = {}) { - this.isRenderer = true; - - // - - const { logarithmicDepthBuffer = false, alpha = true } = parameters; - - // public - - this.domElement = backend.getDomElement(); - - this.backend = backend; - - this.autoClear = true; - this.autoClearColor = true; - this.autoClearDepth = true; - this.autoClearStencil = true; - - this.alpha = alpha; - - this.logarithmicDepthBuffer = logarithmicDepthBuffer; - - this.outputColorSpace = SRGBColorSpace; - - this.toneMapping = NoToneMapping; - this.toneMappingExposure = 1.0; - - this.sortObjects = true; - - this.depth = true; - this.stencil = false; - - this.clippingPlanes = []; - - this.info = new Info(); - - // nodes - - this.toneMappingNode = null; - - // internals - - this._pixelRatio = 1; - this._width = this.domElement.width; - this._height = this.domElement.height; - - this._viewport = new Vector4(0, 0, this._width, this._height); - this._scissor = new Vector4(0, 0, this._width, this._height); - this._scissorTest = false; - - this._attributes = null; - this._geometries = null; - this._nodes = null; - this._animation = null; - this._bindings = null; - this._objects = null; - this._pipelines = null; - this._renderLists = null; - this._renderContexts = null; - this._textures = null; - this._background = null; - - this._currentRenderContext = null; - - this._opaqueSort = null; - this._transparentSort = null; - - this._frameBufferTarget = null; - - const alphaClear = this.alpha === true ? 0 : 1; - - this._clearColor = new Color4(0, 0, 0, alphaClear); - this._clearDepth = 1; - this._clearStencil = 0; - - this._renderTarget = null; - this._activeCubeFace = 0; - this._activeMipmapLevel = 0; - - this._renderObjectFunction = null; - this._currentRenderObjectFunction = null; - - this._handleObjectFunction = this._renderObjectDirect; - - this._initialized = false; - this._initPromise = null; - - this._compilationPromises = null; - - // backwards compatibility - - this.shadowMap = { - enabled: false, - type: null, - }; - - this.xr = { - enabled: false, - }; - } - - async init() { - if (this._initialized) { - throw new Error('Renderer: Backend has already been initialized.'); - } - - if (this._initPromise !== null) { - return this._initPromise; - } - - this._initPromise = new Promise(async (resolve, reject) => { - const backend = this.backend; - - try { - await backend.init(this); - } catch (error) { - reject(error); - return; - } - - this._nodes = new Nodes(this, backend); - this._animation = new Animation(this._nodes, this.info); - this._attributes = new Attributes(backend); - this._background = new Background(this, this._nodes); - this._geometries = new Geometries(this._attributes, this.info); - this._textures = new Textures(this, backend, this.info); - this._pipelines = new Pipelines(backend, this._nodes); - this._bindings = new Bindings( - backend, - this._nodes, - this._textures, - this._attributes, - this._pipelines, - this.info, - ); - this._objects = new RenderObjects( - this, - this._nodes, - this._geometries, - this._pipelines, - this._bindings, - this.info, - ); - this._renderLists = new RenderLists(); - this._renderContexts = new RenderContexts(); - - // - - this._initialized = true; - - resolve(); - }); - - return this._initPromise; - } - - get coordinateSystem() { - return this.backend.coordinateSystem; - } - - async compileAsync(scene, camera, targetScene = null) { - if (this._initialized === false) await this.init(); - - // preserve render tree - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - const previousRenderContext = this._currentRenderContext; - const previousRenderObjectFunction = this._currentRenderObjectFunction; - const previousCompilationPromises = this._compilationPromises; - - // - - const sceneRef = scene.isScene === true ? scene : _scene; - - if (targetScene === null) targetScene = scene; - - const renderTarget = this._renderTarget; - const renderContext = this._renderContexts.get(targetScene, camera, renderTarget); - const activeMipmapLevel = this._activeMipmapLevel; - - const compilationPromises = []; - - this._currentRenderContext = renderContext; - this._currentRenderObjectFunction = this.renderObject; - - this._handleObjectFunction = this._createObjectPipeline; - - this._compilationPromises = compilationPromises; - - nodeFrame.renderId++; - - // - - nodeFrame.update(); - - // - - renderContext.depth = this.depth; - renderContext.stencil = this.stencil; - - if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); - renderContext.clippingContext.updateGlobal(this, camera); - - // - - sceneRef.onBeforeRender(this, scene, camera, renderTarget); - - // - - const renderList = this._renderLists.get(scene, camera); - renderList.begin(); - - this._projectObject(scene, camera, 0, renderList); - - // include lights from target scene - if (targetScene !== scene) { - targetScene.traverseVisible(function (object) { - if (object.isLight && object.layers.test(camera.layers)) { - renderList.pushLight(object); - } - }); - } - - renderList.finish(); - - // - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); - - const renderTargetData = this._textures.get(renderTarget); - - renderContext.textures = renderTargetData.textures; - renderContext.depthTexture = renderTargetData.depthTexture; - } else { - renderContext.textures = null; - renderContext.depthTexture = null; - } - - // - - this._nodes.updateScene(sceneRef); - - // - - this._background.update(sceneRef, renderList, renderContext); - - // process render lists - - const opaqueObjects = renderList.opaque; - const transparentObjects = renderList.transparent; - const lightsNode = renderList.lightsNode; - - if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); - if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); - - // restore render tree - - nodeFrame.renderId = previousRenderId; - - this._currentRenderContext = previousRenderContext; - this._currentRenderObjectFunction = previousRenderObjectFunction; - this._compilationPromises = previousCompilationPromises; - - this._handleObjectFunction = this._renderObjectDirect; - - // wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete - - await Promise.all(compilationPromises); - } - - async renderAsync(scene, camera) { - if (this._initialized === false) await this.init(); - - const renderContext = this._renderScene(scene, camera); - - await this.backend.resolveTimestampAsync(renderContext, 'render'); - } - - render(scene, camera) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', - ); - - return this.renderAsync(scene, camera); - } - - this._renderScene(scene, camera); - } - - _getFrameBufferTarget() { - const { currentColorSpace } = this; - - const useToneMapping = - this._renderTarget === null && (this.toneMapping !== NoToneMapping || this.toneMappingNode !== null); - const useColorSpace = currentColorSpace !== LinearSRGBColorSpace && currentColorSpace !== NoColorSpace; - - if (useToneMapping === false && useColorSpace === false) return null; - - const { width, height } = this.getDrawingBufferSize(_drawingBufferSize); - const { depth, stencil } = this; - - let frameBufferTarget = this._frameBufferTarget; - - if (frameBufferTarget === null) { - frameBufferTarget = new RenderTarget(width, height, { - depthBuffer: depth, - stencilBuffer: stencil, - type: HalfFloatType, // FloatType - format: RGBAFormat, - colorSpace: LinearSRGBColorSpace, - generateMipmaps: false, - minFilter: LinearFilter, - magFilter: LinearFilter, - samples: this.backend.parameters.antialias ? 4 : 0, - }); - - frameBufferTarget.isPostProcessingRenderTarget = true; - - this._frameBufferTarget = frameBufferTarget; - } - - frameBufferTarget.depthBuffer = depth; - frameBufferTarget.stencilBuffer = stencil; - frameBufferTarget.setSize(width, height); - frameBufferTarget.viewport.copy(this._viewport); - frameBufferTarget.scissor.copy(this._scissor); - frameBufferTarget.viewport.multiplyScalar(this._pixelRatio); - frameBufferTarget.scissor.multiplyScalar(this._pixelRatio); - frameBufferTarget.scissorTest = this._scissorTest; - - return frameBufferTarget; - } - - _renderScene(scene, camera, useFrameBufferTarget = true) { - const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; - - // preserve render tree - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - const previousRenderContext = this._currentRenderContext; - const previousRenderObjectFunction = this._currentRenderObjectFunction; - - // - - const sceneRef = scene.isScene === true ? scene : _scene; - - const outputRenderTarget = this._renderTarget; - - const activeCubeFace = this._activeCubeFace; - const activeMipmapLevel = this._activeMipmapLevel; - - // - - let renderTarget; - - if (frameBufferTarget !== null) { - renderTarget = frameBufferTarget; - - this.setRenderTarget(renderTarget); - } else { - renderTarget = outputRenderTarget; - } - - // - - const renderContext = this._renderContexts.get(scene, camera, renderTarget); - - this._currentRenderContext = renderContext; - this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; - - // - - this.info.calls++; - this.info.render.calls++; - - nodeFrame.renderId = this.info.calls; - - // - - const coordinateSystem = this.coordinateSystem; - - if (camera.coordinateSystem !== coordinateSystem) { - camera.coordinateSystem = coordinateSystem; - - camera.updateProjectionMatrix(); - } - - // - - if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld(); - - if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld(); - - // - - let viewport = this._viewport; - let scissor = this._scissor; - let pixelRatio = this._pixelRatio; - - if (renderTarget !== null) { - viewport = renderTarget.viewport; - scissor = renderTarget.scissor; - pixelRatio = 1; - } - - this.getDrawingBufferSize(_drawingBufferSize); - - _screen.set(0, 0, _drawingBufferSize.width, _drawingBufferSize.height); - - const minDepth = viewport.minDepth === undefined ? 0 : viewport.minDepth; - const maxDepth = viewport.maxDepth === undefined ? 1 : viewport.maxDepth; - - renderContext.viewportValue.copy(viewport).multiplyScalar(pixelRatio).floor(); - renderContext.viewportValue.width >>= activeMipmapLevel; - renderContext.viewportValue.height >>= activeMipmapLevel; - renderContext.viewportValue.minDepth = minDepth; - renderContext.viewportValue.maxDepth = maxDepth; - renderContext.viewport = renderContext.viewportValue.equals(_screen) === false; - - renderContext.scissorValue.copy(scissor).multiplyScalar(pixelRatio).floor(); - renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals(_screen) === false; - renderContext.scissorValue.width >>= activeMipmapLevel; - renderContext.scissorValue.height >>= activeMipmapLevel; - - if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); - renderContext.clippingContext.updateGlobal(this, camera); - - // - - sceneRef.onBeforeRender(this, scene, camera, renderTarget); - - // - - _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - _frustum.setFromProjectionMatrix(_projScreenMatrix, coordinateSystem); - - const renderList = this._renderLists.get(scene, camera); - renderList.begin(); - - this._projectObject(scene, camera, 0, renderList); - - renderList.finish(); - - if (this.sortObjects === true) { - renderList.sort(this._opaqueSort, this._transparentSort); - } - - // - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); - - const renderTargetData = this._textures.get(renderTarget); - - renderContext.textures = renderTargetData.textures; - renderContext.depthTexture = renderTargetData.depthTexture; - renderContext.width = renderTargetData.width; - renderContext.height = renderTargetData.height; - renderContext.renderTarget = renderTarget; - renderContext.depth = renderTarget.depthBuffer; - renderContext.stencil = renderTarget.stencilBuffer; - } else { - renderContext.textures = null; - renderContext.depthTexture = null; - renderContext.width = this.domElement.width; - renderContext.height = this.domElement.height; - renderContext.depth = this.depth; - renderContext.stencil = this.stencil; - } - - renderContext.width >>= activeMipmapLevel; - renderContext.height >>= activeMipmapLevel; - renderContext.activeCubeFace = activeCubeFace; - renderContext.activeMipmapLevel = activeMipmapLevel; - renderContext.occlusionQueryCount = renderList.occlusionQueryCount; - - // - - this._nodes.updateScene(sceneRef); - - // - - this._background.update(sceneRef, renderList, renderContext); - - // - - this.backend.beginRender(renderContext); - - // process render lists - - const opaqueObjects = renderList.opaque; - const transparentObjects = renderList.transparent; - const lightsNode = renderList.lightsNode; - - if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); - if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); - - // finish render pass - - this.backend.finishRender(renderContext); - - // restore render tree - - nodeFrame.renderId = previousRenderId; - - this._currentRenderContext = previousRenderContext; - this._currentRenderObjectFunction = previousRenderObjectFunction; - - // - - if (frameBufferTarget !== null) { - this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); - - _quad.material.fragmentNode = this._nodes.getOutputNode(renderTarget.texture); - - this._renderScene(_quad, _quad.camera, false); - } - - // - - sceneRef.onAfterRender(this, scene, camera, renderTarget); - - // - - return renderContext; - } - - getMaxAnisotropy() { - return this.backend.getMaxAnisotropy(); - } - - getActiveCubeFace() { - return this._activeCubeFace; - } - - getActiveMipmapLevel() { - return this._activeMipmapLevel; - } - - async setAnimationLoop(callback) { - if (this._initialized === false) await this.init(); - - this._animation.setAnimationLoop(callback); - } - - getArrayBuffer(attribute) { - // @deprecated, r155 - - console.warn('THREE.Renderer: getArrayBuffer() is deprecated. Use getArrayBufferAsync() instead.'); - - return this.getArrayBufferAsync(attribute); - } - - async getArrayBufferAsync(attribute) { - return await this.backend.getArrayBufferAsync(attribute); - } - - getContext() { - return this.backend.getContext(); - } - - getPixelRatio() { - return this._pixelRatio; - } - - getDrawingBufferSize(target) { - return target.set(this._width * this._pixelRatio, this._height * this._pixelRatio).floor(); - } - - getSize(target) { - return target.set(this._width, this._height); - } - - setPixelRatio(value = 1) { - this._pixelRatio = value; - - this.setSize(this._width, this._height, false); - } - - setDrawingBufferSize(width, height, pixelRatio) { - this._width = width; - this._height = height; - - this._pixelRatio = pixelRatio; - - this.domElement.width = Math.floor(width * pixelRatio); - this.domElement.height = Math.floor(height * pixelRatio); - - this.setViewport(0, 0, width, height); - - if (this._initialized) this.backend.updateSize(); - } - - setSize(width, height, updateStyle = true) { - this._width = width; - this._height = height; - - this.domElement.width = Math.floor(width * this._pixelRatio); - this.domElement.height = Math.floor(height * this._pixelRatio); - - if (updateStyle === true) { - this.domElement.style.width = width + 'px'; - this.domElement.style.height = height + 'px'; - } - - this.setViewport(0, 0, width, height); - - if (this._initialized) this.backend.updateSize(); - } - - setOpaqueSort(method) { - this._opaqueSort = method; - } - - setTransparentSort(method) { - this._transparentSort = method; - } - - getScissor(target) { - const scissor = this._scissor; - - target.x = scissor.x; - target.y = scissor.y; - target.width = scissor.width; - target.height = scissor.height; - - return target; - } - - setScissor(x, y, width, height) { - const scissor = this._scissor; - - if (x.isVector4) { - scissor.copy(x); - } else { - scissor.set(x, y, width, height); - } - } - - getScissorTest() { - return this._scissorTest; - } - - setScissorTest(boolean) { - this._scissorTest = boolean; - - this.backend.setScissorTest(boolean); - } - - getViewport(target) { - return target.copy(this._viewport); - } - - setViewport(x, y, width, height, minDepth = 0, maxDepth = 1) { - const viewport = this._viewport; - - if (x.isVector4) { - viewport.copy(x); - } else { - viewport.set(x, y, width, height); - } - - viewport.minDepth = minDepth; - viewport.maxDepth = maxDepth; - } - - getClearColor(target) { - return target.copy(this._clearColor); - } - - setClearColor(color, alpha = 1) { - this._clearColor.set(color); - this._clearColor.a = alpha; - } - - getClearAlpha() { - return this._clearColor.a; - } - - setClearAlpha(alpha) { - this._clearColor.a = alpha; - } - - getClearDepth() { - return this._clearDepth; - } - - setClearDepth(depth) { - this._clearDepth = depth; - } - - getClearStencil() { - return this._clearStencil; - } - - setClearStencil(stencil) { - this._clearStencil = stencil; - } - - isOccluded(object) { - const renderContext = this._currentRenderContext; - - return renderContext && this.backend.isOccluded(renderContext, object); - } - - clear(color = true, depth = true, stencil = true) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead.', - ); - - return this.clearAsync(color, depth, stencil); - } - - const renderTarget = this._renderTarget || this._getFrameBufferTarget(); - - let renderTargetData = null; - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget); - - renderTargetData = this._textures.get(renderTarget); - } - - this.backend.clear(color, depth, stencil, renderTargetData); - } - - clearColor() { - return this.clear(true, false, false); - } - - clearDepth() { - return this.clear(false, true, false); - } - - clearStencil() { - return this.clear(false, false, true); - } - - async clearAsync(color = true, depth = true, stencil = true) { - if (this._initialized === false) await this.init(); - - this.clear(color, depth, stencil); - } - - clearColorAsync() { - return this.clearAsync(true, false, false); - } - - clearDepthAsync() { - return this.clearAsync(false, true, false); - } - - clearStencilAsync() { - return this.clearAsync(false, false, true); - } - - get currentColorSpace() { - const renderTarget = this._renderTarget; - - if (renderTarget !== null) { - const texture = renderTarget.texture; - - return (Array.isArray(texture) ? texture[0] : texture).colorSpace; - } - - return this.outputColorSpace; - } - - dispose() { - this.info.dispose(); - - this._animation.dispose(); - this._objects.dispose(); - this._pipelines.dispose(); - this._nodes.dispose(); - this._bindings.dispose(); - this._renderLists.dispose(); - this._renderContexts.dispose(); - this._textures.dispose(); - - this.setRenderTarget(null); - this.setAnimationLoop(null); - } - - setRenderTarget(renderTarget, activeCubeFace = 0, activeMipmapLevel = 0) { - this._renderTarget = renderTarget; - this._activeCubeFace = activeCubeFace; - this._activeMipmapLevel = activeMipmapLevel; - } - - getRenderTarget() { - return this._renderTarget; - } - - setRenderObjectFunction(renderObjectFunction) { - this._renderObjectFunction = renderObjectFunction; - } - - getRenderObjectFunction() { - return this._renderObjectFunction; - } - - async computeAsync(computeNodes) { - if (this._initialized === false) await this.init(); - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - - // - - this.info.calls++; - this.info.compute.calls++; - this.info.compute.computeCalls++; - - nodeFrame.renderId = this.info.calls; - - // - - const backend = this.backend; - const pipelines = this._pipelines; - const bindings = this._bindings; - const nodes = this._nodes; - const computeList = Array.isArray(computeNodes) ? computeNodes : [computeNodes]; - - if (computeList[0] === undefined || computeList[0].isComputeNode !== true) { - throw new Error('THREE.Renderer: .compute() expects a ComputeNode.'); - } - - backend.beginCompute(computeNodes); - - for (const computeNode of computeList) { - // onInit - - if (pipelines.has(computeNode) === false) { - const dispose = () => { - computeNode.removeEventListener('dispose', dispose); - - pipelines.delete(computeNode); - bindings.delete(computeNode); - nodes.delete(computeNode); - }; - - computeNode.addEventListener('dispose', dispose); - - // - - computeNode.onInit({ renderer: this }); - } - - nodes.updateForCompute(computeNode); - bindings.updateForCompute(computeNode); - - const computeBindings = bindings.getForCompute(computeNode); - const computePipeline = pipelines.getForCompute(computeNode, computeBindings); - - backend.compute(computeNodes, computeNode, computeBindings, computePipeline); - } - - backend.finishCompute(computeNodes); - - await this.backend.resolveTimestampAsync(computeNodes, 'compute'); - - // - - nodeFrame.renderId = previousRenderId; - } - - async hasFeatureAsync(name) { - if (this._initialized === false) await this.init(); - - return this.backend.hasFeature(name); - } - - hasFeature(name) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', - ); - - return false; - } - - return this.backend.hasFeature(name); - } - - copyFramebufferToTexture(framebufferTexture) { - const renderContext = this._currentRenderContext; - - this._textures.updateTexture(framebufferTexture); - - this.backend.copyFramebufferToTexture(framebufferTexture, renderContext); - } - - copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0) { - this._textures.updateTexture(srcTexture); - this._textures.updateTexture(dstTexture); - - this.backend.copyTextureToTexture(srcTexture, dstTexture, srcRegion, dstPosition, level); - } - - readRenderTargetPixelsAsync(renderTarget, x, y, width, height, index = 0) { - return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); - } - - _projectObject(object, camera, groupOrder, renderList) { - if (object.visible === false) return; - - const visible = object.layers.test(camera.layers); - - if (visible) { - if (object.isGroup) { - groupOrder = object.renderOrder; - } else if (object.isLOD) { - if (object.autoUpdate === true) object.update(camera); - } else if (object.isLight) { - renderList.pushLight(object); - } else if (object.isSprite) { - if (!object.frustumCulled || _frustum.intersectsSprite(object)) { - if (this.sortObjects === true) { - _vector3.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix); - } - - const geometry = object.geometry; - const material = object.material; - - if (material.visible) { - renderList.push(object, geometry, material, groupOrder, _vector3.z, null); - } - } - } else if (object.isLineLoop) { - console.error( - 'THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.', - ); - } else if (object.isMesh || object.isLine || object.isPoints) { - if (!object.frustumCulled || _frustum.intersectsObject(object)) { - const geometry = object.geometry; - const material = object.material; - - if (this.sortObjects === true) { - if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); - - _vector3 - .copy(geometry.boundingSphere.center) - .applyMatrix4(object.matrixWorld) - .applyMatrix4(_projScreenMatrix); - } - - if (Array.isArray(material)) { - const groups = geometry.groups; - - for (let i = 0, l = groups.length; i < l; i++) { - const group = groups[i]; - const groupMaterial = material[group.materialIndex]; - - if (groupMaterial && groupMaterial.visible) { - renderList.push(object, geometry, groupMaterial, groupOrder, _vector3.z, group); - } - } - } else if (material.visible) { - renderList.push(object, geometry, material, groupOrder, _vector3.z, null); - } - } - } - } - - const children = object.children; - - for (let i = 0, l = children.length; i < l; i++) { - this._projectObject(children[i], camera, groupOrder, renderList); - } - } - - _renderObjects(renderList, camera, scene, lightsNode) { - // process renderable objects - - for (let i = 0, il = renderList.length; i < il; i++) { - const renderItem = renderList[i]; - - // @TODO: Add support for multiple materials per object. This will require to extract - // the material from the renderItem object and pass it with its group data to renderObject(). - - const { object, geometry, material, group } = renderItem; - - if (camera.isArrayCamera) { - const cameras = camera.cameras; - - for (let j = 0, jl = cameras.length; j < jl; j++) { - const camera2 = cameras[j]; - - if (object.layers.test(camera2.layers)) { - const vp = camera2.viewport; - const minDepth = vp.minDepth === undefined ? 0 : vp.minDepth; - const maxDepth = vp.maxDepth === undefined ? 1 : vp.maxDepth; - - const viewportValue = this._currentRenderContext.viewportValue; - viewportValue.copy(vp).multiplyScalar(this._pixelRatio).floor(); - viewportValue.minDepth = minDepth; - viewportValue.maxDepth = maxDepth; - - this.backend.updateViewport(this._currentRenderContext); - - this._currentRenderObjectFunction( - object, - scene, - camera2, - geometry, - material, - group, - lightsNode, - ); - } - } - } else { - this._currentRenderObjectFunction(object, scene, camera, geometry, material, group, lightsNode); - } - } - } - - renderObject(object, scene, camera, geometry, material, group, lightsNode) { - let overridePositionNode; - let overrideFragmentNode; - let overrideDepthNode; - - // - - object.onBeforeRender(this, scene, camera, geometry, material, group); - - material.onBeforeRender(this, scene, camera, geometry, material, group); - - // - - if (scene.overrideMaterial !== null) { - const overrideMaterial = scene.overrideMaterial; - - if (material.positionNode && material.positionNode.isNode) { - overridePositionNode = overrideMaterial.positionNode; - overrideMaterial.positionNode = material.positionNode; - } - - if (overrideMaterial.isShadowNodeMaterial) { - overrideMaterial.side = material.shadowSide === null ? material.side : material.shadowSide; - - if (material.depthNode && material.depthNode.isNode) { - overrideDepthNode = overrideMaterial.depthNode; - overrideMaterial.depthNode = material.depthNode; - } - - if (material.shadowNode && material.shadowNode.isNode) { - overrideFragmentNode = overrideMaterial.fragmentNode; - overrideMaterial.fragmentNode = material.shadowNode; - } - - if (this.localClippingEnabled) { - if (material.clipShadows) { - if (overrideMaterial.clippingPlanes !== material.clippingPlanes) { - overrideMaterial.clippingPlanes = material.clippingPlanes; - overrideMaterial.needsUpdate = true; - } - - if (overrideMaterial.clipIntersection !== material.clipIntersection) { - overrideMaterial.clipIntersection = material.clipIntersection; - } - } else if (Array.isArray(overrideMaterial.clippingPlanes)) { - overrideMaterial.clippingPlanes = null; - overrideMaterial.needsUpdate = true; - } - } - } - - material = overrideMaterial; - } - - // - - if (material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false) { - material.side = BackSide; - this._handleObjectFunction(object, material, scene, camera, lightsNode, group, 'backSide'); // create backSide pass id - - material.side = FrontSide; - this._handleObjectFunction(object, material, scene, camera, lightsNode, group); // use default pass id - - material.side = DoubleSide; - } else { - this._handleObjectFunction(object, material, scene, camera, lightsNode, group); - } - - // - - if (overridePositionNode !== undefined) { - scene.overrideMaterial.positionNode = overridePositionNode; - } - - if (overrideDepthNode !== undefined) { - scene.overrideMaterial.depthNode = overrideDepthNode; - } - - if (overrideFragmentNode !== undefined) { - scene.overrideMaterial.fragmentNode = overrideFragmentNode; - } - - // - - object.onAfterRender(this, scene, camera, geometry, material, group); - } - - _renderObjectDirect(object, material, scene, camera, lightsNode, group, passId) { - const renderObject = this._objects.get( - object, - material, - scene, - camera, - lightsNode, - this._currentRenderContext, - passId, - ); - renderObject.drawRange = group || object.geometry.drawRange; - - // - - this._nodes.updateBefore(renderObject); - - // - - object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld); - object.normalMatrix.getNormalMatrix(object.modelViewMatrix); - - // - - this._nodes.updateForRender(renderObject); - this._geometries.updateForRender(renderObject); - this._bindings.updateForRender(renderObject); - this._pipelines.updateForRender(renderObject); - - // - - this.backend.draw(renderObject, this.info); - } - - _createObjectPipeline(object, material, scene, camera, lightsNode, passId) { - const renderObject = this._objects.get( - object, - material, - scene, - camera, - lightsNode, - this._currentRenderContext, - passId, - ); - - // - - this._nodes.updateBefore(renderObject); - - // - - this._nodes.updateForRender(renderObject); - this._geometries.updateForRender(renderObject); - this._bindings.updateForRender(renderObject); - - this._pipelines.getForRender(renderObject, this._compilationPromises); - } - - get compute() { - return this.computeAsync; - } - - get compile() { - return this.compileAsync; - } -} - -export default Renderer; From dc05bf8187bdb088f43157f143e20e3d0ce6681a Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 21:00:49 -0400 Subject: [PATCH 15/18] Update --- examples-jsm/examples/nodes/core/Node.ts | 411 ++++++ .../examples/nodes/core/NodeBuilder.ts | 1011 ++++++++++++++ examples-jsm/examples/nodes/core/NodeCache.ts | 18 + examples-jsm/examples/nodes/core/NodeFrame.ts | 101 ++ .../examples/nodes/core/NodeKeywords.ts | 58 + .../examples/nodes/core/NodeParser.ts | 7 + examples-jsm/examples/nodes/core/constants.ts | 28 + examples-jsm/examples/nodes/fog/FogNode.ts | 38 + .../nodes/lighting/EnvironmentNode.ts | 118 ++ .../examples/nodes/lighting/LightsNode.ts | 157 +++ .../examples/renderers/common/Renderer.ts | 1225 +++++++++++++++++ examples-jsm/index.js | 2 + 12 files changed, 3174 insertions(+) create mode 100644 examples-jsm/examples/nodes/core/Node.ts create mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts create mode 100644 examples-jsm/examples/nodes/core/NodeCache.ts create mode 100644 examples-jsm/examples/nodes/core/NodeFrame.ts create mode 100644 examples-jsm/examples/nodes/core/NodeKeywords.ts create mode 100644 examples-jsm/examples/nodes/core/NodeParser.ts create mode 100644 examples-jsm/examples/nodes/core/constants.ts create mode 100644 examples-jsm/examples/nodes/fog/FogNode.ts create mode 100644 examples-jsm/examples/nodes/lighting/EnvironmentNode.ts create mode 100644 examples-jsm/examples/nodes/lighting/LightsNode.ts create mode 100644 examples-jsm/examples/renderers/common/Renderer.ts diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts new file mode 100644 index 000000000..438c44dd1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/Node.ts @@ -0,0 +1,411 @@ +import { EventDispatcher } from 'three'; +import { NodeUpdateType } from './constants.js'; +import { getNodeChildren, getCacheKey } from './NodeUtils.js'; +import { MathUtils } from 'three'; + +const NodeClasses = new Map(); + +let _nodeId = 0; + +class Node extends EventDispatcher { + constructor(nodeType = null) { + super(); + + this.nodeType = nodeType; + + this.updateType = NodeUpdateType.NONE; + this.updateBeforeType = NodeUpdateType.NONE; + + this.uuid = MathUtils.generateUUID(); + + this.version = 0; + + this._cacheKey = null; + this._cacheKeyVersion = 0; + + this.isNode = true; + + Object.defineProperty(this, 'id', { value: _nodeId++ }); + } + + set needsUpdate(value) { + if (value === true) { + this.version++; + } + } + + get type() { + return this.constructor.type; + } + + onUpdate(callback, updateType) { + this.updateType = updateType; + this.update = callback.bind(this.getSelf()); + + return this; + } + + onFrameUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.FRAME); + } + + onRenderUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.RENDER); + } + + onObjectUpdate(callback) { + return this.onUpdate(callback, NodeUpdateType.OBJECT); + } + + onReference(callback) { + this.updateReference = callback.bind(this.getSelf()); + + return this; + } + + getSelf() { + // Returns non-node object. + + return this.self || this; + } + + updateReference(/*state*/) { + return this; + } + + isGlobal(/*builder*/) { + return false; + } + + *getChildren() { + for (const { childNode } of getNodeChildren(this)) { + yield childNode; + } + } + + dispose() { + this.dispatchEvent({ type: 'dispose' }); + } + + traverse(callback) { + callback(this); + + for (const childNode of this.getChildren()) { + childNode.traverse(callback); + } + } + + getCacheKey(force = false) { + force = force || this.version !== this._cacheKeyVersion; + + if (force === true || this._cacheKey === null) { + this._cacheKey = getCacheKey(this, force); + this._cacheKeyVersion = this.version; + } + + return this._cacheKey; + } + + getHash(/*builder*/) { + return this.uuid; + } + + getUpdateType() { + return this.updateType; + } + + getUpdateBeforeType() { + return this.updateBeforeType; + } + + getElementType(builder) { + const type = this.getNodeType(builder); + const elementType = builder.getElementType(type); + + return elementType; + } + + getNodeType(builder) { + const nodeProperties = builder.getNodeProperties(this); + + if (nodeProperties.outputNode) { + return nodeProperties.outputNode.getNodeType(builder); + } + + return this.nodeType; + } + + getShared(builder) { + const hash = this.getHash(builder); + const nodeFromHash = builder.getNodeFromHash(hash); + + return nodeFromHash || this; + } + + setup(builder) { + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of this.getChildren()) { + nodeProperties['_node' + childNode.id] = childNode; + } + + // return a outputNode if exists + return null; + } + + construct(builder) { + // @deprecated, r157 + + console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); + + return this.setup(builder); + } + + increaseUsage(builder) { + const nodeData = builder.getDataFromNode(this); + nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; + + return nodeData.usageCount; + } + + analyze(builder) { + const usageCount = this.increaseUsage(builder); + + if (usageCount === 1) { + // node flow children + + const nodeProperties = builder.getNodeProperties(this); + + for (const childNode of Object.values(nodeProperties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } + + generate(builder, output) { + const { outputNode } = builder.getNodeProperties(this); + + if (outputNode && outputNode.isNode === true) { + return outputNode.build(builder, output); + } + } + + updateBefore(/*frame*/) { + console.warn('Abstract function.'); + } + + update(/*frame*/) { + console.warn('Abstract function.'); + } + + build(builder, output = null) { + const refNode = this.getShared(builder); + + if (this !== refNode) { + return refNode.build(builder, output); + } + + builder.addNode(this); + builder.addChain(this); + + /* Build stages expected results: + - "setup" -> Node + - "analyze" -> null + - "generate" -> String + */ + let result = null; + + const buildStage = builder.getBuildStage(); + + if (buildStage === 'setup') { + this.updateReference(builder); + + const properties = builder.getNodeProperties(this); + + if (properties.initialized !== true || builder.context.tempRead === false) { + const stackNodesBeforeSetup = builder.stack.nodes.length; + + properties.initialized = true; + properties.outputNode = this.setup(builder); + + if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { + properties.outputNode = builder.stack; + } + + for (const childNode of Object.values(properties)) { + if (childNode && childNode.isNode === true) { + childNode.build(builder); + } + } + } + } else if (buildStage === 'analyze') { + this.analyze(builder); + } else if (buildStage === 'generate') { + const isGenerateOnce = this.generate.length === 1; + + if (isGenerateOnce) { + const type = this.getNodeType(builder); + const nodeData = builder.getDataFromNode(this); + + result = nodeData.snippet; + + if (result === undefined /*|| builder.context.tempRead === false*/) { + result = this.generate(builder) || ''; + + nodeData.snippet = result; + } + + result = builder.format(result, type, output); + } else { + result = this.generate(builder, output) || ''; + } + } + + builder.removeChain(this); + + return result; + } + + getSerializeChildren() { + return getNodeChildren(this); + } + + serialize(json) { + const nodeChildren = this.getSerializeChildren(); + + const inputNodes = {}; + + for (const { property, index, childNode } of nodeChildren) { + if (index !== undefined) { + if (inputNodes[property] === undefined) { + inputNodes[property] = Number.isInteger(index) ? [] : {}; + } + + inputNodes[property][index] = childNode.toJSON(json.meta).uuid; + } else { + inputNodes[property] = childNode.toJSON(json.meta).uuid; + } + } + + if (Object.keys(inputNodes).length > 0) { + json.inputNodes = inputNodes; + } + } + + deserialize(json) { + if (json.inputNodes !== undefined) { + const nodes = json.meta.nodes; + + for (const property in json.inputNodes) { + if (Array.isArray(json.inputNodes[property])) { + const inputArray = []; + + for (const uuid of json.inputNodes[property]) { + inputArray.push(nodes[uuid]); + } + + this[property] = inputArray; + } else if (typeof json.inputNodes[property] === 'object') { + const inputObject = {}; + + for (const subProperty in json.inputNodes[property]) { + const uuid = json.inputNodes[property][subProperty]; + + inputObject[subProperty] = nodes[uuid]; + } + + this[property] = inputObject; + } else { + const uuid = json.inputNodes[property]; + + this[property] = nodes[uuid]; + } + } + } + } + + toJSON(meta) { + const { uuid, type } = this; + const isRoot = meta === undefined || typeof meta === 'string'; + + if (isRoot) { + meta = { + textures: {}, + images: {}, + nodes: {}, + }; + } + + // serialize + + let data = meta.nodes[uuid]; + + if (data === undefined) { + data = { + uuid, + type, + meta, + metadata: { + version: 4.6, + type: 'Node', + generator: 'Node.toJSON', + }, + }; + + if (isRoot !== true) meta.nodes[data.uuid] = data; + + this.serialize(data); + + delete data.meta; + } + + // TODO: Copied from Object3D.toJSON + + function extractFromCache(cache) { + const values = []; + + for (const key in cache) { + const data = cache[key]; + delete data.metadata; + values.push(data); + } + + return values; + } + + if (isRoot) { + const textures = extractFromCache(meta.textures); + const images = extractFromCache(meta.images); + const nodes = extractFromCache(meta.nodes); + + if (textures.length > 0) data.textures = textures; + if (images.length > 0) data.images = images; + if (nodes.length > 0) data.nodes = nodes; + } + + return data; + } +} + +export default Node; + +export function addNodeClass(type, nodeClass) { + if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); + if (NodeClasses.has(type)) { + console.warn(`Redefinition of node class ${type}`); + return; + } + + NodeClasses.set(type, nodeClass); + nodeClass.type = type; +} + +export function createNodeFromType(type) { + const Class = NodeClasses.get(type); + + if (Class !== undefined) { + return new Class(); + } +} diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts new file mode 100644 index 000000000..ebdc13ff1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts @@ -0,0 +1,1011 @@ +import NodeUniform from './NodeUniform.js'; +import NodeAttribute from './NodeAttribute.js'; +import NodeVarying from './NodeVarying.js'; +import NodeVar from './NodeVar.js'; +import NodeCode from './NodeCode.js'; +import NodeKeywords from './NodeKeywords.js'; +import NodeCache from './NodeCache.js'; +import ParameterNode from './ParameterNode.js'; +import FunctionNode from '../code/FunctionNode.js'; +import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; +import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; + +import { + FloatNodeUniform, + Vector2NodeUniform, + Vector3NodeUniform, + Vector4NodeUniform, + ColorNodeUniform, + Matrix3NodeUniform, + Matrix4NodeUniform, +} from '../../renderers/common/nodes/NodeUniform.js'; + +import { + REVISION, + RenderTarget, + Color, + Vector2, + Vector3, + Vector4, + IntType, + UnsignedIntType, + Float16BufferAttribute, +} from 'three'; + +import { stack } from './StackNode.js'; +import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; + +import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; +import ChainMap from '../../renderers/common/ChainMap.js'; + +import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; + +const uniformsGroupCache = new ChainMap(); + +const typeFromLength = new Map([ + [2, 'vec2'], + [3, 'vec3'], + [4, 'vec4'], + [9, 'mat3'], + [16, 'mat4'], +]); + +const typeFromArray = new Map([ + [Int8Array, 'int'], + [Int16Array, 'int'], + [Int32Array, 'int'], + [Uint8Array, 'uint'], + [Uint16Array, 'uint'], + [Uint32Array, 'uint'], + [Float32Array, 'float'], +]); + +const toFloat = value => { + value = Number(value); + + return value + (value % 1 ? '' : '.0'); +}; + +class NodeBuilder { + constructor(object, renderer, parser, scene = null, material = null) { + this.object = object; + this.material = material || (object && object.material) || null; + this.geometry = (object && object.geometry) || null; + this.renderer = renderer; + this.parser = parser; + this.scene = scene; + + this.nodes = []; + this.updateNodes = []; + this.updateBeforeNodes = []; + this.hashNodes = {}; + + this.lightsNode = null; + this.environmentNode = null; + this.fogNode = null; + + this.clippingContext = null; + + this.vertexShader = null; + this.fragmentShader = null; + this.computeShader = null; + + this.flowNodes = { vertex: [], fragment: [], compute: [] }; + this.flowCode = { vertex: '', fragment: '', compute: [] }; + this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; + this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; + this.bindings = { vertex: [], fragment: [], compute: [] }; + this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; + this.bindingsArray = null; + this.attributes = []; + this.bufferAttributes = []; + this.varyings = []; + this.codes = {}; + this.vars = {}; + this.flow = { code: '' }; + this.chaining = []; + this.stack = stack(); + this.stacks = []; + this.tab = '\t'; + + this.currentFunctionNode = null; + + this.context = { + keywords: new NodeKeywords(), + material: this.material, + }; + + this.cache = new NodeCache(); + this.globalCache = this.cache; + + this.flowsData = new WeakMap(); + + this.shaderStage = null; + this.buildStage = null; + } + + createRenderTarget(width, height, options) { + return new RenderTarget(width, height, options); + } + + createCubeRenderTarget(size, options) { + return new CubeRenderTarget(size, options); + } + + createPMREMGenerator() { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return new PMREMGenerator(this.renderer); + } + + includes(node) { + return this.nodes.includes(node); + } + + _getSharedBindings(bindings) { + const shared = []; + + for (const binding of bindings) { + if (binding.shared === true) { + // nodes is the chainmap key + const nodes = binding.getNodes(); + + let sharedBinding = uniformsGroupCache.get(nodes); + + if (sharedBinding === undefined) { + uniformsGroupCache.set(nodes, binding); + + sharedBinding = binding; + } + + shared.push(sharedBinding); + } else { + shared.push(binding); + } + } + + return shared; + } + + getBindings() { + let bindingsArray = this.bindingsArray; + + if (bindingsArray === null) { + const bindings = this.bindings; + + this.bindingsArray = bindingsArray = this._getSharedBindings( + this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, + ); + } + + return bindingsArray; + } + + setHashNode(node, hash) { + this.hashNodes[hash] = node; + } + + addNode(node) { + if (this.nodes.includes(node) === false) { + this.nodes.push(node); + + this.setHashNode(node, node.getHash(this)); + } + } + + buildUpdateNodes() { + for (const node of this.nodes) { + const updateType = node.getUpdateType(); + const updateBeforeType = node.getUpdateBeforeType(); + + if (updateType !== NodeUpdateType.NONE) { + this.updateNodes.push(node.getSelf()); + } + + if (updateBeforeType !== NodeUpdateType.NONE) { + this.updateBeforeNodes.push(node); + } + } + } + + get currentNode() { + return this.chaining[this.chaining.length - 1]; + } + + addChain(node) { + /* + if ( this.chaining.indexOf( node ) !== - 1 ) { + + console.warn( 'Recursive node: ', node ); + + } + */ + + this.chaining.push(node); + } + + removeChain(node) { + const lastChain = this.chaining.pop(); + + if (lastChain !== node) { + throw new Error('NodeBuilder: Invalid node chaining!'); + } + } + + getMethod(method) { + return method; + } + + getNodeFromHash(hash) { + return this.hashNodes[hash]; + } + + addFlow(shaderStage, node) { + this.flowNodes[shaderStage].push(node); + + return node; + } + + setContext(context) { + this.context = context; + } + + getContext() { + return this.context; + } + + setCache(cache) { + this.cache = cache; + } + + getCache() { + return this.cache; + } + + isAvailable(/*name*/) { + return false; + } + + getVertexIndex() { + console.warn('Abstract function.'); + } + + getInstanceIndex() { + console.warn('Abstract function.'); + } + + getFrontFacing() { + console.warn('Abstract function.'); + } + + getFragCoord() { + console.warn('Abstract function.'); + } + + isFlipY() { + return false; + } + + generateTexture(/* texture, textureProperty, uvSnippet */) { + console.warn('Abstract function.'); + } + + generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { + console.warn('Abstract function.'); + } + + generateConst(type, value = null) { + if (value === null) { + if (type === 'float' || type === 'int' || type === 'uint') value = 0; + else if (type === 'bool') value = false; + else if (type === 'color') value = new Color(); + else if (type === 'vec2') value = new Vector2(); + else if (type === 'vec3') value = new Vector3(); + else if (type === 'vec4') value = new Vector4(); + } + + if (type === 'float') return toFloat(value); + if (type === 'int') return `${Math.round(value)}`; + if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; + if (type === 'bool') return value ? 'true' : 'false'; + if (type === 'color') + return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; + + const typeLength = this.getTypeLength(type); + + const componentType = this.getComponentType(type); + + const generateConst = value => this.generateConst(componentType, value); + + if (typeLength === 2) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; + } else if (typeLength === 3) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; + } else if (typeLength === 4) { + return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; + } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { + return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; + } else if (typeLength > 4) { + return `${this.getType(type)}()`; + } + + throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); + } + + getType(type) { + if (type === 'color') return 'vec3'; + + return type; + } + + generateMethod(method) { + return method; + } + + hasGeometryAttribute(name) { + return this.geometry && this.geometry.getAttribute(name) !== undefined; + } + + getAttribute(name, type) { + const attributes = this.attributes; + + // find attribute + + for (const attribute of attributes) { + if (attribute.name === name) { + return attribute; + } + } + + // create a new if no exist + + const attribute = new NodeAttribute(name, type); + + attributes.push(attribute); + + return attribute; + } + + getPropertyName(node /*, shaderStage*/) { + return node.name; + } + + isVector(type) { + return /vec\d/.test(type); + } + + isMatrix(type) { + return /mat\d/.test(type); + } + + isReference(type) { + return ( + type === 'void' || + type === 'property' || + type === 'sampler' || + type === 'texture' || + type === 'cubeTexture' || + type === 'storageTexture' || + type === 'texture3D' + ); + } + + needsColorSpaceToLinear(/*texture*/) { + return false; + } + + getComponentTypeFromTexture(texture) { + const type = texture.type; + + if (texture.isDataTexture) { + if (type === IntType) return 'int'; + if (type === UnsignedIntType) return 'uint'; + } + + return 'float'; + } + + getElementType(type) { + if (type === 'mat2') return 'vec2'; + if (type === 'mat3') return 'vec3'; + if (type === 'mat4') return 'vec4'; + + return this.getComponentType(type); + } + + getComponentType(type) { + type = this.getVectorType(type); + + if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; + + const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); + + if (componentType === null) return null; + + if (componentType[1] === 'b') return 'bool'; + if (componentType[1] === 'i') return 'int'; + if (componentType[1] === 'u') return 'uint'; + + return 'float'; + } + + getVectorType(type) { + if (type === 'color') return 'vec3'; + if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') + return 'vec4'; + + return type; + } + + getTypeFromLength(length, componentType = 'float') { + if (length === 1) return componentType; + + const baseType = typeFromLength.get(length); + const prefix = componentType === 'float' ? '' : componentType[0]; + + return prefix + baseType; + } + + getTypeFromArray(array) { + return typeFromArray.get(array.constructor); + } + + getTypeFromAttribute(attribute) { + let dataAttribute = attribute; + + if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; + + const array = dataAttribute.array; + const itemSize = attribute.itemSize; + const normalized = attribute.normalized; + + let arrayType; + + if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { + arrayType = this.getTypeFromArray(array); + } + + return this.getTypeFromLength(itemSize, arrayType); + } + + getTypeLength(type) { + const vecType = this.getVectorType(type); + const vecNum = /vec([2-4])/.exec(vecType); + + if (vecNum !== null) return Number(vecNum[1]); + if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; + if (/mat2/.test(type) === true) return 4; + if (/mat3/.test(type) === true) return 9; + if (/mat4/.test(type) === true) return 16; + + return 0; + } + + getVectorFromMatrix(type) { + return type.replace('mat', 'vec'); + } + + changeComponentType(type, newComponentType) { + return this.getTypeFromLength(this.getTypeLength(type), newComponentType); + } + + getIntegerType(type) { + const componentType = this.getComponentType(type); + + if (componentType === 'int' || componentType === 'uint') return type; + + return this.changeComponentType(type, 'int'); + } + + addStack() { + this.stack = stack(this.stack); + + this.stacks.push(getCurrentStack() || this.stack); + setCurrentStack(this.stack); + + return this.stack; + } + + removeStack() { + const lastStack = this.stack; + this.stack = lastStack.parent; + + setCurrentStack(this.stacks.pop()); + + return lastStack; + } + + getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { + cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; + + let nodeData = cache.getNodeData(node); + + if (nodeData === undefined) { + nodeData = {}; + + cache.setNodeData(node, nodeData); + } + + if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; + + return nodeData[shaderStage]; + } + + getNodeProperties(node, shaderStage = 'any') { + const nodeData = this.getDataFromNode(node, shaderStage); + + return nodeData.properties || (nodeData.properties = { outputNode: null }); + } + + getBufferAttributeFromNode(node, type) { + const nodeData = this.getDataFromNode(node); + + let bufferAttribute = nodeData.bufferAttribute; + + if (bufferAttribute === undefined) { + const index = this.uniforms.index++; + + bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); + + this.bufferAttributes.push(bufferAttribute); + + nodeData.bufferAttribute = bufferAttribute; + } + + return bufferAttribute; + } + + getStructTypeFromNode(node, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + if (nodeData.structType === undefined) { + const index = this.structs.index++; + + node.name = `StructType${index}`; + this.structs[shaderStage].push(node); + + nodeData.structType = node; + } + + return node; + } + + getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { + const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); + + let nodeUniform = nodeData.uniform; + + if (nodeUniform === undefined) { + const index = this.uniforms.index++; + + nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); + + this.uniforms[shaderStage].push(nodeUniform); + + nodeData.uniform = nodeUniform; + } + + return nodeUniform; + } + + getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node, shaderStage); + + let nodeVar = nodeData.variable; + + if (nodeVar === undefined) { + const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); + + if (name === null) name = 'nodeVar' + vars.length; + + nodeVar = new NodeVar(name, type); + + vars.push(nodeVar); + + nodeData.variable = nodeVar; + } + + return nodeVar; + } + + getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { + const nodeData = this.getDataFromNode(node, 'any'); + + let nodeVarying = nodeData.varying; + + if (nodeVarying === undefined) { + const varyings = this.varyings; + const index = varyings.length; + + if (name === null) name = 'nodeVarying' + index; + + nodeVarying = new NodeVarying(name, type); + + varyings.push(nodeVarying); + + nodeData.varying = nodeVarying; + } + + return nodeVarying; + } + + getCodeFromNode(node, type, shaderStage = this.shaderStage) { + const nodeData = this.getDataFromNode(node); + + let nodeCode = nodeData.code; + + if (nodeCode === undefined) { + const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); + const index = codes.length; + + nodeCode = new NodeCode('nodeCode' + index, type); + + codes.push(nodeCode); + + nodeData.code = nodeCode; + } + + return nodeCode; + } + + addLineFlowCode(code) { + if (code === '') return this; + + code = this.tab + code; + + if (!/;\s*$/.test(code)) { + code = code + ';\n'; + } + + this.flow.code += code; + + return this; + } + + addFlowCode(code) { + this.flow.code += code; + + return this; + } + + addFlowTab() { + this.tab += '\t'; + + return this; + } + + removeFlowTab() { + this.tab = this.tab.slice(0, -1); + + return this; + } + + getFlowData(node /*, shaderStage*/) { + return this.flowsData.get(node); + } + + flowNode(node) { + const output = node.getNodeType(this); + + const flowData = this.flowChildNode(node, output); + + this.flowsData.set(node, flowData); + + return flowData; + } + + buildFunctionNode(shaderNode) { + const fn = new FunctionNode(); + + const previous = this.currentFunctionNode; + + this.currentFunctionNode = fn; + + fn.code = this.buildFunctionCode(shaderNode); + + this.currentFunctionNode = previous; + + return fn; + } + + flowShaderNode(shaderNode) { + const layout = shaderNode.layout; + + let inputs; + + if (shaderNode.isArrayInput) { + inputs = []; + + for (const input of layout.inputs) { + inputs.push(new ParameterNode(input.type, input.name)); + } + } else { + inputs = {}; + + for (const input of layout.inputs) { + inputs[input.name] = new ParameterNode(input.type, input.name); + } + } + + // + + shaderNode.layout = null; + + const callNode = shaderNode.call(inputs); + const flowData = this.flowStagesNode(callNode, layout.type); + + shaderNode.layout = layout; + + return flowData; + } + + flowStagesNode(node, output = null) { + const previousFlow = this.flow; + const previousVars = this.vars; + const previousBuildStage = this.buildStage; + + const flow = { + code: '', + }; + + this.flow = flow; + this.vars = {}; + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + flow.result = node.build(this, output); + } + + flow.vars = this.getVars(this.shaderStage); + + this.flow = previousFlow; + this.vars = previousVars; + this.setBuildStage(previousBuildStage); + + return flow; + } + + getFunctionOperator() { + return null; + } + + flowChildNode(node, output = null) { + const previousFlow = this.flow; + + const flow = { + code: '', + }; + + this.flow = flow; + + flow.result = node.build(this, output); + + this.flow = previousFlow; + + return flow; + } + + flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { + const previousShaderStage = this.shaderStage; + + this.setShaderStage(shaderStage); + + const flowData = this.flowChildNode(node, output); + + if (propertyName !== null) { + flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; + } + + this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; + + this.setShaderStage(previousShaderStage); + + return flowData; + } + + getAttributesArray() { + return this.attributes.concat(this.bufferAttributes); + } + + getAttributes(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVaryings(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getVar(type, name) { + return `${this.getType(type)} ${name}`; + } + + getVars(shaderStage) { + let snippet = ''; + + const vars = this.vars[shaderStage]; + + if (vars !== undefined) { + for (const variable of vars) { + snippet += `${this.getVar(variable.type, variable.name)}; `; + } + } + + return snippet; + } + + getUniforms(/*shaderStage*/) { + console.warn('Abstract function.'); + } + + getCodes(shaderStage) { + const codes = this.codes[shaderStage]; + + let code = ''; + + if (codes !== undefined) { + for (const nodeCode of codes) { + code += nodeCode.code + '\n'; + } + } + + return code; + } + + getHash() { + return this.vertexShader + this.fragmentShader + this.computeShader; + } + + setShaderStage(shaderStage) { + this.shaderStage = shaderStage; + } + + getShaderStage() { + return this.shaderStage; + } + + setBuildStage(buildStage) { + this.buildStage = buildStage; + } + + getBuildStage() { + return this.buildStage; + } + + buildCode() { + console.warn('Abstract function.'); + } + + build() { + const { object, material } = this; + + if (material !== null) { + NodeMaterial.fromMaterial(material).build(this); + } else { + this.addFlow('compute', object); + } + + // setup() -> stage 1: create possible new nodes and returns an output reference node + // analyze() -> stage 2: analyze nodes to possible optimization and validation + // generate() -> stage 3: generate shader + + for (const buildStage of defaultBuildStages) { + this.setBuildStage(buildStage); + + if (this.context.vertex && this.context.vertex.isNode) { + this.flowNodeFromShaderStage('vertex', this.context.vertex); + } + + for (const shaderStage of shaderStages) { + this.setShaderStage(shaderStage); + + const flowNodes = this.flowNodes[shaderStage]; + + for (const node of flowNodes) { + if (buildStage === 'generate') { + this.flowNode(node); + } else { + node.build(this); + } + } + } + } + + this.setBuildStage(null); + this.setShaderStage(null); + + // stage 4: build code for a specific output + + this.buildCode(); + this.buildUpdateNodes(); + + return this; + } + + getNodeUniform(uniformNode, type) { + if (type === 'float') return new FloatNodeUniform(uniformNode); + if (type === 'vec2') return new Vector2NodeUniform(uniformNode); + if (type === 'vec3') return new Vector3NodeUniform(uniformNode); + if (type === 'vec4') return new Vector4NodeUniform(uniformNode); + if (type === 'color') return new ColorNodeUniform(uniformNode); + if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); + if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); + + throw new Error(`Uniform "${type}" not declared.`); + } + + createNodeMaterial(type = 'NodeMaterial') { + // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support + + return createNodeMaterialFromType(type); + } + + format(snippet, fromType, toType) { + fromType = this.getVectorType(fromType); + toType = this.getVectorType(toType); + + if (fromType === toType || toType === null || this.isReference(toType)) { + return snippet; + } + + const fromTypeLength = this.getTypeLength(fromType); + const toTypeLength = this.getTypeLength(toType); + + if (fromTypeLength > 4) { + // fromType is matrix-like + + // @TODO: ignore for now + + return snippet; + } + + if (toTypeLength > 4 || toTypeLength === 0) { + // toType is matrix-like or unknown + + // @TODO: ignore for now + + return snippet; + } + + if (fromTypeLength === toTypeLength) { + return `${this.getType(toType)}( ${snippet} )`; + } + + if (fromTypeLength > toTypeLength) { + return this.format( + `${snippet}.${'xyz'.slice(0, toTypeLength)}`, + this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), + toType, + ); + } + + if (toTypeLength === 4 && fromTypeLength > 1) { + // toType is vec4-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; + } + + if (fromTypeLength === 2) { + // fromType is vec2-like and toType is vec3-like + + return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; + } + + if (fromTypeLength === 1 && toTypeLength > 1 && fromType[0] !== toType[0]) { + // fromType is float-like + + // convert a number value to vector type, e.g: + // vec3( 1u ) -> vec3( float( 1u ) ) + + snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; + } + + return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like + } + + getSignature() { + return `// Three.js r${REVISION} - NodeMaterial System\n`; + } +} + +export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/NodeCache.ts b/examples-jsm/examples/nodes/core/NodeCache.ts new file mode 100644 index 000000000..96a7e0c76 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeCache.ts @@ -0,0 +1,18 @@ +let id = 0; + +class NodeCache { + constructor() { + this.id = id++; + this.nodesData = new WeakMap(); + } + + getNodeData(node) { + return this.nodesData.get(node); + } + + setNodeData(node, data) { + this.nodesData.set(node, data); + } +} + +export default NodeCache; diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts new file mode 100644 index 000000000..b8e8d37b6 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeFrame.ts @@ -0,0 +1,101 @@ +import { NodeUpdateType } from './constants.js'; + +class NodeFrame { + constructor() { + this.time = 0; + this.deltaTime = 0; + + this.frameId = 0; + this.renderId = 0; + + this.startTime = null; + + this.updateMap = new WeakMap(); + this.updateBeforeMap = new WeakMap(); + + this.renderer = null; + this.material = null; + this.camera = null; + this.object = null; + this.scene = null; + } + + _getMaps(referenceMap, nodeRef) { + let maps = referenceMap.get(nodeRef); + + if (maps === undefined) { + maps = { + renderMap: new WeakMap(), + frameMap: new WeakMap(), + }; + + referenceMap.set(nodeRef, maps); + } + + return maps; + } + + updateBeforeNode(node) { + const updateType = node.getUpdateBeforeType(); + const reference = node.updateReference(this); + + if (updateType === NodeUpdateType.FRAME) { + const { frameMap } = this._getMaps(this.updateBeforeMap, reference); + + if (frameMap.get(reference) !== this.frameId) { + if (node.updateBefore(this) !== false) { + frameMap.set(reference, this.frameId); + } + } + } else if (updateType === NodeUpdateType.RENDER) { + const { renderMap } = this._getMaps(this.updateBeforeMap, reference); + + if (renderMap.get(reference) !== this.renderId) { + if (node.updateBefore(this) !== false) { + renderMap.set(reference, this.renderId); + } + } + } else if (updateType === NodeUpdateType.OBJECT) { + node.updateBefore(this); + } + } + + updateNode(node) { + const updateType = node.getUpdateType(); + const reference = node.updateReference(this); + + if (updateType === NodeUpdateType.FRAME) { + const { frameMap } = this._getMaps(this.updateMap, reference); + + if (frameMap.get(reference) !== this.frameId) { + if (node.update(this) !== false) { + frameMap.set(reference, this.frameId); + } + } + } else if (updateType === NodeUpdateType.RENDER) { + const { renderMap } = this._getMaps(this.updateMap, reference); + + if (renderMap.get(reference) !== this.renderId) { + if (node.update(this) !== false) { + renderMap.set(reference, this.renderId); + } + } + } else if (updateType === NodeUpdateType.OBJECT) { + node.update(this); + } + } + + update() { + this.frameId++; + + if (this.lastTime === undefined) this.lastTime = performance.now(); + + this.deltaTime = (performance.now() - this.lastTime) / 1000; + + this.lastTime = performance.now(); + + this.time += this.deltaTime; + } +} + +export default NodeFrame; diff --git a/examples-jsm/examples/nodes/core/NodeKeywords.ts b/examples-jsm/examples/nodes/core/NodeKeywords.ts new file mode 100644 index 000000000..53da9bf50 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeKeywords.ts @@ -0,0 +1,58 @@ +class NodeKeywords { + constructor() { + this.keywords = []; + this.nodes = []; + this.keywordsCallback = {}; + } + + getNode(name) { + let node = this.nodes[name]; + + if (node === undefined && this.keywordsCallback[name] !== undefined) { + node = this.keywordsCallback[name](name); + + this.nodes[name] = node; + } + + return node; + } + + addKeyword(name, callback) { + this.keywords.push(name); + this.keywordsCallback[name] = callback; + + return this; + } + + parse(code) { + const keywordNames = this.keywords; + + const regExp = new RegExp(`\\b${keywordNames.join('\\b|\\b')}\\b`, 'g'); + + const codeKeywords = code.match(regExp); + + const keywordNodes = []; + + if (codeKeywords !== null) { + for (const keyword of codeKeywords) { + const node = this.getNode(keyword); + + if (node !== undefined && keywordNodes.indexOf(node) === -1) { + keywordNodes.push(node); + } + } + } + + return keywordNodes; + } + + include(builder, code) { + const keywordNodes = this.parse(code); + + for (const keywordNode of keywordNodes) { + keywordNode.build(builder); + } + } +} + +export default NodeKeywords; diff --git a/examples-jsm/examples/nodes/core/NodeParser.ts b/examples-jsm/examples/nodes/core/NodeParser.ts new file mode 100644 index 000000000..9849452f1 --- /dev/null +++ b/examples-jsm/examples/nodes/core/NodeParser.ts @@ -0,0 +1,7 @@ +class NodeParser { + parseFunction(/*source*/) { + console.warn('Abstract function.'); + } +} + +export default NodeParser; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts new file mode 100644 index 000000000..3b01a9a6d --- /dev/null +++ b/examples-jsm/examples/nodes/core/constants.ts @@ -0,0 +1,28 @@ +export const NodeShaderStage = { + VERTEX: 'vertex', + FRAGMENT: 'fragment', +}; + +export const NodeUpdateType = { + NONE: 'none', + FRAME: 'frame', + RENDER: 'render', + OBJECT: 'object', +}; + +export const NodeType = { + BOOLEAN: 'bool', + INTEGER: 'int', + FLOAT: 'float', + VECTOR2: 'vec2', + VECTOR3: 'vec3', + VECTOR4: 'vec4', + MATRIX2: 'mat2', + MATRIX3: 'mat3', + MATRIX4: 'mat4', +}; + +export const defaultShaderStages = ['fragment', 'vertex']; +export const defaultBuildStages = ['setup', 'analyze', 'generate']; +export const shaderStages = [...defaultShaderStages, 'compute']; +export const vectorComponents = ['x', 'y', 'z', 'w']; diff --git a/examples-jsm/examples/nodes/fog/FogNode.ts b/examples-jsm/examples/nodes/fog/FogNode.ts new file mode 100644 index 000000000..9417df5a5 --- /dev/null +++ b/examples-jsm/examples/nodes/fog/FogNode.ts @@ -0,0 +1,38 @@ +import Node, { addNodeClass } from '../core/Node.js'; +import { positionView } from '../accessors/PositionNode.js'; +import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; + +class FogNode extends Node { + constructor(colorNode, factorNode) { + super('float'); + + this.isFogNode = true; + + this.colorNode = colorNode; + this.factorNode = factorNode; + } + + getViewZNode(builder) { + let viewZ; + + const getViewZ = builder.context.getViewZ; + + if (getViewZ !== undefined) { + viewZ = getViewZ(this); + } + + return (viewZ || positionView.z).negate(); + } + + setup() { + return this.factorNode; + } +} + +export default FogNode; + +export const fog = nodeProxy(FogNode); + +addNodeElement('fog', fog); + +addNodeClass('FogNode', FogNode); diff --git a/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts b/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts new file mode 100644 index 000000000..56f8109c2 --- /dev/null +++ b/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts @@ -0,0 +1,118 @@ +import LightingNode from './LightingNode.js'; +import { cache } from '../core/CacheNode.js'; +import { context } from '../core/ContextNode.js'; +import { roughness, clearcoatRoughness } from '../core/PropertyNode.js'; +import { cameraViewMatrix } from '../accessors/CameraNode.js'; +import { + transformedClearcoatNormalView, + transformedNormalView, + transformedNormalWorld, +} from '../accessors/NormalNode.js'; +import { positionViewDirection } from '../accessors/PositionNode.js'; +import { addNodeClass } from '../core/Node.js'; +import { float } from '../shadernode/ShaderNode.js'; +import { reference } from '../accessors/ReferenceNode.js'; +import { transformedBentNormalView } from '../accessors/AccessorsUtils.js'; +import { pmremTexture } from '../pmrem/PMREMNode.js'; + +const envNodeCache = new WeakMap(); + +class EnvironmentNode extends LightingNode { + constructor(envNode = null) { + super(); + + this.envNode = envNode; + } + + setup(builder) { + let envNode = this.envNode; + + if (envNode.isTextureNode) { + let cacheEnvNode = envNodeCache.get(envNode.value); + + if (cacheEnvNode === undefined) { + cacheEnvNode = pmremTexture(envNode.value); + + envNodeCache.set(envNode.value, cacheEnvNode); + } + + envNode = cacheEnvNode; + } + + // + + const { material } = builder; + + const envMap = material.envMap; + const intensity = envMap + ? reference('envMapIntensity', 'float', builder.material) + : reference('environmentIntensity', 'float', builder.scene); // @TODO: Add materialEnvIntensity in MaterialNode + + const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0; + const radianceNormalView = useAnisotropy ? transformedBentNormalView : transformedNormalView; + + const radiance = context(envNode, createRadianceContext(roughness, radianceNormalView)).mul(intensity); + const irradiance = context(envNode, createIrradianceContext(transformedNormalWorld)) + .mul(Math.PI) + .mul(intensity); + + const isolateRadiance = cache(radiance); + + // + + builder.context.radiance.addAssign(isolateRadiance); + + builder.context.iblIrradiance.addAssign(irradiance); + + // + + const clearcoatRadiance = builder.context.lightingModel.clearcoatRadiance; + + if (clearcoatRadiance) { + const clearcoatRadianceContext = context( + envNode, + createRadianceContext(clearcoatRoughness, transformedClearcoatNormalView), + ).mul(intensity); + const isolateClearcoatRadiance = cache(clearcoatRadianceContext); + + clearcoatRadiance.addAssign(isolateClearcoatRadiance); + } + } +} + +const createRadianceContext = (roughnessNode, normalViewNode) => { + let reflectVec = null; + + return { + getUV: () => { + if (reflectVec === null) { + reflectVec = positionViewDirection.negate().reflect(normalViewNode); + + // Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane. + reflectVec = roughnessNode.mul(roughnessNode).mix(reflectVec, normalViewNode).normalize(); + + reflectVec = reflectVec.transformDirection(cameraViewMatrix); + } + + return reflectVec; + }, + getTextureLevel: () => { + return roughnessNode; + }, + }; +}; + +const createIrradianceContext = normalWorldNode => { + return { + getUV: () => { + return normalWorldNode; + }, + getTextureLevel: () => { + return float(1.0); + }, + }; +}; + +export default EnvironmentNode; + +addNodeClass('EnvironmentNode', EnvironmentNode); diff --git a/examples-jsm/examples/nodes/lighting/LightsNode.ts b/examples-jsm/examples/nodes/lighting/LightsNode.ts new file mode 100644 index 000000000..b3695ea8b --- /dev/null +++ b/examples-jsm/examples/nodes/lighting/LightsNode.ts @@ -0,0 +1,157 @@ +import Node from '../core/Node.js'; +import AnalyticLightNode from './AnalyticLightNode.js'; +import { nodeObject, nodeProxy, vec3 } from '../shadernode/ShaderNode.js'; + +const LightNodes = new WeakMap(); + +const sortLights = lights => { + return lights.sort((a, b) => a.id - b.id); +}; + +class LightsNode extends Node { + constructor(lightNodes = []) { + super('vec3'); + + this.totalDiffuseNode = vec3().temp('totalDiffuse'); + this.totalSpecularNode = vec3().temp('totalSpecular'); + + this.outgoingLightNode = vec3().temp('outgoingLight'); + + this.lightNodes = lightNodes; + + this._hash = null; + } + + get hasLight() { + return this.lightNodes.length > 0; + } + + getHash() { + if (this._hash === null) { + const hash = []; + + for (const lightNode of this.lightNodes) { + hash.push(lightNode.getHash()); + } + + this._hash = 'lights-' + hash.join(','); + } + + return this._hash; + } + + setup(builder) { + const context = builder.context; + const lightingModel = context.lightingModel; + + let outgoingLightNode = this.outgoingLightNode; + + if (lightingModel) { + const { lightNodes, totalDiffuseNode, totalSpecularNode } = this; + + context.outgoingLight = outgoingLightNode; + + const stack = builder.addStack(); + + // + + lightingModel.start(context, stack, builder); + + // lights + + for (const lightNode of lightNodes) { + lightNode.build(builder); + } + + // + + lightingModel.indirectDiffuse(context, stack, builder); + lightingModel.indirectSpecular(context, stack, builder); + lightingModel.ambientOcclusion(context, stack, builder); + + // + + const { backdrop, backdropAlpha } = context; + const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = context.reflectedLight; + + let totalDiffuse = directDiffuse.add(indirectDiffuse); + + if (backdrop !== null) { + if (backdropAlpha !== null) { + totalDiffuse = vec3(backdropAlpha.mix(totalDiffuse, backdrop)); + } else { + totalDiffuse = vec3(backdrop); + } + + context.material.transparent = true; + } + + totalDiffuseNode.assign(totalDiffuse); + totalSpecularNode.assign(directSpecular.add(indirectSpecular)); + + outgoingLightNode.assign(totalDiffuseNode.add(totalSpecularNode)); + + // + + lightingModel.finish(context, stack, builder); + + // + + outgoingLightNode = outgoingLightNode.bypass(builder.removeStack()); + } + + return outgoingLightNode; + } + + _getLightNodeById(id) { + for (const lightNode of this.lightNodes) { + if (lightNode.isAnalyticLightNode && lightNode.light.id === id) { + return lightNode; + } + } + + return null; + } + + fromLights(lights = []) { + const lightNodes = []; + + lights = sortLights(lights); + + for (const light of lights) { + let lightNode = this._getLightNodeById(light.id); + + if (lightNode === null) { + const lightClass = light.constructor; + const lightNodeClass = LightNodes.has(lightClass) ? LightNodes.get(lightClass) : AnalyticLightNode; + + lightNode = nodeObject(new lightNodeClass(light)); + } + + lightNodes.push(lightNode); + } + + this.lightNodes = lightNodes; + this._hash = null; + + return this; + } +} + +export default LightsNode; + +export const lights = lights => nodeObject(new LightsNode().fromLights(lights)); +export const lightsNode = nodeProxy(LightsNode); + +export function addLightNode(lightClass, lightNodeClass) { + if (LightNodes.has(lightClass)) { + console.warn(`Redefinition of light node ${lightNodeClass.type}`); + return; + } + + if (typeof lightClass !== 'function') throw new Error(`Light ${lightClass.name} is not a class`); + if (typeof lightNodeClass !== 'function' || !lightNodeClass.type) + throw new Error(`Light node ${lightNodeClass.type} is not a class`); + + LightNodes.set(lightClass, lightNodeClass); +} diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts new file mode 100644 index 000000000..85713cfe6 --- /dev/null +++ b/examples-jsm/examples/renderers/common/Renderer.ts @@ -0,0 +1,1225 @@ +import Animation from './Animation.js'; +import RenderObjects from './RenderObjects.js'; +import Attributes from './Attributes.js'; +import Geometries from './Geometries.js'; +import Info from './Info.js'; +import Pipelines from './Pipelines.js'; +import Bindings from './Bindings.js'; +import RenderLists from './RenderLists.js'; +import RenderContexts from './RenderContexts.js'; +import Textures from './Textures.js'; +import Background from './Background.js'; +import Nodes from './nodes/Nodes.js'; +import Color4 from './Color4.js'; +import ClippingContext from './ClippingContext.js'; +import { + Scene, + Frustum, + Matrix4, + Vector2, + Vector3, + Vector4, + DoubleSide, + BackSide, + FrontSide, + SRGBColorSpace, + NoColorSpace, + NoToneMapping, + LinearFilter, + LinearSRGBColorSpace, + RenderTarget, + HalfFloatType, + RGBAFormat, +} from 'three'; +import { NodeMaterial } from '../../nodes/Nodes.js'; +import QuadMesh from '../../objects/QuadMesh.js'; + +const _scene = new Scene(); +const _drawingBufferSize = new Vector2(); +const _screen = new Vector4(); +const _frustum = new Frustum(); +const _projScreenMatrix = new Matrix4(); +const _vector3 = new Vector3(); +const _quad = new QuadMesh(new NodeMaterial()); + +class Renderer { + constructor(backend, parameters = {}) { + this.isRenderer = true; + + // + + const { logarithmicDepthBuffer = false, alpha = true } = parameters; + + // public + + this.domElement = backend.getDomElement(); + + this.backend = backend; + + this.autoClear = true; + this.autoClearColor = true; + this.autoClearDepth = true; + this.autoClearStencil = true; + + this.alpha = alpha; + + this.logarithmicDepthBuffer = logarithmicDepthBuffer; + + this.outputColorSpace = SRGBColorSpace; + + this.toneMapping = NoToneMapping; + this.toneMappingExposure = 1.0; + + this.sortObjects = true; + + this.depth = true; + this.stencil = false; + + this.clippingPlanes = []; + + this.info = new Info(); + + // nodes + + this.toneMappingNode = null; + + // internals + + this._pixelRatio = 1; + this._width = this.domElement.width; + this._height = this.domElement.height; + + this._viewport = new Vector4(0, 0, this._width, this._height); + this._scissor = new Vector4(0, 0, this._width, this._height); + this._scissorTest = false; + + this._attributes = null; + this._geometries = null; + this._nodes = null; + this._animation = null; + this._bindings = null; + this._objects = null; + this._pipelines = null; + this._renderLists = null; + this._renderContexts = null; + this._textures = null; + this._background = null; + + this._currentRenderContext = null; + + this._opaqueSort = null; + this._transparentSort = null; + + this._frameBufferTarget = null; + + const alphaClear = this.alpha === true ? 0 : 1; + + this._clearColor = new Color4(0, 0, 0, alphaClear); + this._clearDepth = 1; + this._clearStencil = 0; + + this._renderTarget = null; + this._activeCubeFace = 0; + this._activeMipmapLevel = 0; + + this._renderObjectFunction = null; + this._currentRenderObjectFunction = null; + + this._handleObjectFunction = this._renderObjectDirect; + + this._initialized = false; + this._initPromise = null; + + this._compilationPromises = null; + + // backwards compatibility + + this.shadowMap = { + enabled: false, + type: null, + }; + + this.xr = { + enabled: false, + }; + } + + async init() { + if (this._initialized) { + throw new Error('Renderer: Backend has already been initialized.'); + } + + if (this._initPromise !== null) { + return this._initPromise; + } + + this._initPromise = new Promise(async (resolve, reject) => { + const backend = this.backend; + + try { + await backend.init(this); + } catch (error) { + reject(error); + return; + } + + this._nodes = new Nodes(this, backend); + this._animation = new Animation(this._nodes, this.info); + this._attributes = new Attributes(backend); + this._background = new Background(this, this._nodes); + this._geometries = new Geometries(this._attributes, this.info); + this._textures = new Textures(this, backend, this.info); + this._pipelines = new Pipelines(backend, this._nodes); + this._bindings = new Bindings( + backend, + this._nodes, + this._textures, + this._attributes, + this._pipelines, + this.info, + ); + this._objects = new RenderObjects( + this, + this._nodes, + this._geometries, + this._pipelines, + this._bindings, + this.info, + ); + this._renderLists = new RenderLists(); + this._renderContexts = new RenderContexts(); + + // + + this._initialized = true; + + resolve(); + }); + + return this._initPromise; + } + + get coordinateSystem() { + return this.backend.coordinateSystem; + } + + async compileAsync(scene, camera, targetScene = null) { + if (this._initialized === false) await this.init(); + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + const previousCompilationPromises = this._compilationPromises; + + // + + const sceneRef = scene.isScene === true ? scene : _scene; + + if (targetScene === null) targetScene = scene; + + const renderTarget = this._renderTarget; + const renderContext = this._renderContexts.get(targetScene, camera, renderTarget); + const activeMipmapLevel = this._activeMipmapLevel; + + const compilationPromises = []; + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this.renderObject; + + this._handleObjectFunction = this._createObjectPipeline; + + this._compilationPromises = compilationPromises; + + nodeFrame.renderId++; + + // + + nodeFrame.update(); + + // + + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + + if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal(this, camera); + + // + + sceneRef.onBeforeRender(this, scene, camera, renderTarget); + + // + + const renderList = this._renderLists.get(scene, camera); + renderList.begin(); + + this._projectObject(scene, camera, 0, renderList); + + // include lights from target scene + if (targetScene !== scene) { + targetScene.traverseVisible(function (object) { + if (object.isLight && object.layers.test(camera.layers)) { + renderList.pushLight(object); + } + }); + } + + renderList.finish(); + + // + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); + + const renderTargetData = this._textures.get(renderTarget); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + } else { + renderContext.textures = null; + renderContext.depthTexture = null; + } + + // + + this._nodes.updateScene(sceneRef); + + // + + this._background.update(sceneRef, renderList, renderContext); + + // process render lists + + const opaqueObjects = renderList.opaque; + const transparentObjects = renderList.transparent; + const lightsNode = renderList.lightsNode; + + if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); + if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + this._compilationPromises = previousCompilationPromises; + + this._handleObjectFunction = this._renderObjectDirect; + + // wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete + + await Promise.all(compilationPromises); + } + + async renderAsync(scene, camera) { + if (this._initialized === false) await this.init(); + + const renderContext = this._renderScene(scene, camera); + + await this.backend.resolveTimestampAsync(renderContext, 'render'); + } + + render(scene, camera) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', + ); + + return this.renderAsync(scene, camera); + } + + this._renderScene(scene, camera); + } + + _getFrameBufferTarget() { + const { currentColorSpace } = this; + + const useToneMapping = + this._renderTarget === null && (this.toneMapping !== NoToneMapping || this.toneMappingNode !== null); + const useColorSpace = currentColorSpace !== LinearSRGBColorSpace && currentColorSpace !== NoColorSpace; + + if (useToneMapping === false && useColorSpace === false) return null; + + const { width, height } = this.getDrawingBufferSize(_drawingBufferSize); + const { depth, stencil } = this; + + let frameBufferTarget = this._frameBufferTarget; + + if (frameBufferTarget === null) { + frameBufferTarget = new RenderTarget(width, height, { + depthBuffer: depth, + stencilBuffer: stencil, + type: HalfFloatType, // FloatType + format: RGBAFormat, + colorSpace: LinearSRGBColorSpace, + generateMipmaps: false, + minFilter: LinearFilter, + magFilter: LinearFilter, + samples: this.backend.parameters.antialias ? 4 : 0, + }); + + frameBufferTarget.isPostProcessingRenderTarget = true; + + this._frameBufferTarget = frameBufferTarget; + } + + frameBufferTarget.depthBuffer = depth; + frameBufferTarget.stencilBuffer = stencil; + frameBufferTarget.setSize(width, height); + frameBufferTarget.viewport.copy(this._viewport); + frameBufferTarget.scissor.copy(this._scissor); + frameBufferTarget.viewport.multiplyScalar(this._pixelRatio); + frameBufferTarget.scissor.multiplyScalar(this._pixelRatio); + frameBufferTarget.scissorTest = this._scissorTest; + + return frameBufferTarget; + } + + _renderScene(scene, camera, useFrameBufferTarget = true) { + const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; + + // preserve render tree + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + const previousRenderContext = this._currentRenderContext; + const previousRenderObjectFunction = this._currentRenderObjectFunction; + + // + + const sceneRef = scene.isScene === true ? scene : _scene; + + const outputRenderTarget = this._renderTarget; + + const activeCubeFace = this._activeCubeFace; + const activeMipmapLevel = this._activeMipmapLevel; + + // + + let renderTarget; + + if (frameBufferTarget !== null) { + renderTarget = frameBufferTarget; + + this.setRenderTarget(renderTarget); + } else { + renderTarget = outputRenderTarget; + } + + // + + const renderContext = this._renderContexts.get(scene, camera, renderTarget); + + this._currentRenderContext = renderContext; + this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; + + // + + this.info.calls++; + this.info.render.calls++; + + nodeFrame.renderId = this.info.calls; + + // + + const coordinateSystem = this.coordinateSystem; + + if (camera.coordinateSystem !== coordinateSystem) { + camera.coordinateSystem = coordinateSystem; + + camera.updateProjectionMatrix(); + } + + // + + if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld(); + + if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld(); + + // + + let viewport = this._viewport; + let scissor = this._scissor; + let pixelRatio = this._pixelRatio; + + if (renderTarget !== null) { + viewport = renderTarget.viewport; + scissor = renderTarget.scissor; + pixelRatio = 1; + } + + this.getDrawingBufferSize(_drawingBufferSize); + + _screen.set(0, 0, _drawingBufferSize.width, _drawingBufferSize.height); + + const minDepth = viewport.minDepth === undefined ? 0 : viewport.minDepth; + const maxDepth = viewport.maxDepth === undefined ? 1 : viewport.maxDepth; + + renderContext.viewportValue.copy(viewport).multiplyScalar(pixelRatio).floor(); + renderContext.viewportValue.width >>= activeMipmapLevel; + renderContext.viewportValue.height >>= activeMipmapLevel; + renderContext.viewportValue.minDepth = minDepth; + renderContext.viewportValue.maxDepth = maxDepth; + renderContext.viewport = renderContext.viewportValue.equals(_screen) === false; + + renderContext.scissorValue.copy(scissor).multiplyScalar(pixelRatio).floor(); + renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals(_screen) === false; + renderContext.scissorValue.width >>= activeMipmapLevel; + renderContext.scissorValue.height >>= activeMipmapLevel; + + if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); + renderContext.clippingContext.updateGlobal(this, camera); + + // + + sceneRef.onBeforeRender(this, scene, camera, renderTarget); + + // + + _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); + _frustum.setFromProjectionMatrix(_projScreenMatrix, coordinateSystem); + + const renderList = this._renderLists.get(scene, camera); + renderList.begin(); + + this._projectObject(scene, camera, 0, renderList); + + renderList.finish(); + + if (this.sortObjects === true) { + renderList.sort(this._opaqueSort, this._transparentSort); + } + + // + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); + + const renderTargetData = this._textures.get(renderTarget); + + renderContext.textures = renderTargetData.textures; + renderContext.depthTexture = renderTargetData.depthTexture; + renderContext.width = renderTargetData.width; + renderContext.height = renderTargetData.height; + renderContext.renderTarget = renderTarget; + renderContext.depth = renderTarget.depthBuffer; + renderContext.stencil = renderTarget.stencilBuffer; + } else { + renderContext.textures = null; + renderContext.depthTexture = null; + renderContext.width = this.domElement.width; + renderContext.height = this.domElement.height; + renderContext.depth = this.depth; + renderContext.stencil = this.stencil; + } + + renderContext.width >>= activeMipmapLevel; + renderContext.height >>= activeMipmapLevel; + renderContext.activeCubeFace = activeCubeFace; + renderContext.activeMipmapLevel = activeMipmapLevel; + renderContext.occlusionQueryCount = renderList.occlusionQueryCount; + + // + + this._nodes.updateScene(sceneRef); + + // + + this._background.update(sceneRef, renderList, renderContext); + + // + + this.backend.beginRender(renderContext); + + // process render lists + + const opaqueObjects = renderList.opaque; + const transparentObjects = renderList.transparent; + const lightsNode = renderList.lightsNode; + + if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); + if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); + + // finish render pass + + this.backend.finishRender(renderContext); + + // restore render tree + + nodeFrame.renderId = previousRenderId; + + this._currentRenderContext = previousRenderContext; + this._currentRenderObjectFunction = previousRenderObjectFunction; + + // + + if (frameBufferTarget !== null) { + this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); + + _quad.material.fragmentNode = this._nodes.getOutputNode(renderTarget.texture); + + this._renderScene(_quad, _quad.camera, false); + } + + // + + sceneRef.onAfterRender(this, scene, camera, renderTarget); + + // + + return renderContext; + } + + getMaxAnisotropy() { + return this.backend.getMaxAnisotropy(); + } + + getActiveCubeFace() { + return this._activeCubeFace; + } + + getActiveMipmapLevel() { + return this._activeMipmapLevel; + } + + async setAnimationLoop(callback) { + if (this._initialized === false) await this.init(); + + this._animation.setAnimationLoop(callback); + } + + getArrayBuffer(attribute) { + // @deprecated, r155 + + console.warn('THREE.Renderer: getArrayBuffer() is deprecated. Use getArrayBufferAsync() instead.'); + + return this.getArrayBufferAsync(attribute); + } + + async getArrayBufferAsync(attribute) { + return await this.backend.getArrayBufferAsync(attribute); + } + + getContext() { + return this.backend.getContext(); + } + + getPixelRatio() { + return this._pixelRatio; + } + + getDrawingBufferSize(target) { + return target.set(this._width * this._pixelRatio, this._height * this._pixelRatio).floor(); + } + + getSize(target) { + return target.set(this._width, this._height); + } + + setPixelRatio(value = 1) { + this._pixelRatio = value; + + this.setSize(this._width, this._height, false); + } + + setDrawingBufferSize(width, height, pixelRatio) { + this._width = width; + this._height = height; + + this._pixelRatio = pixelRatio; + + this.domElement.width = Math.floor(width * pixelRatio); + this.domElement.height = Math.floor(height * pixelRatio); + + this.setViewport(0, 0, width, height); + + if (this._initialized) this.backend.updateSize(); + } + + setSize(width, height, updateStyle = true) { + this._width = width; + this._height = height; + + this.domElement.width = Math.floor(width * this._pixelRatio); + this.domElement.height = Math.floor(height * this._pixelRatio); + + if (updateStyle === true) { + this.domElement.style.width = width + 'px'; + this.domElement.style.height = height + 'px'; + } + + this.setViewport(0, 0, width, height); + + if (this._initialized) this.backend.updateSize(); + } + + setOpaqueSort(method) { + this._opaqueSort = method; + } + + setTransparentSort(method) { + this._transparentSort = method; + } + + getScissor(target) { + const scissor = this._scissor; + + target.x = scissor.x; + target.y = scissor.y; + target.width = scissor.width; + target.height = scissor.height; + + return target; + } + + setScissor(x, y, width, height) { + const scissor = this._scissor; + + if (x.isVector4) { + scissor.copy(x); + } else { + scissor.set(x, y, width, height); + } + } + + getScissorTest() { + return this._scissorTest; + } + + setScissorTest(boolean) { + this._scissorTest = boolean; + + this.backend.setScissorTest(boolean); + } + + getViewport(target) { + return target.copy(this._viewport); + } + + setViewport(x, y, width, height, minDepth = 0, maxDepth = 1) { + const viewport = this._viewport; + + if (x.isVector4) { + viewport.copy(x); + } else { + viewport.set(x, y, width, height); + } + + viewport.minDepth = minDepth; + viewport.maxDepth = maxDepth; + } + + getClearColor(target) { + return target.copy(this._clearColor); + } + + setClearColor(color, alpha = 1) { + this._clearColor.set(color); + this._clearColor.a = alpha; + } + + getClearAlpha() { + return this._clearColor.a; + } + + setClearAlpha(alpha) { + this._clearColor.a = alpha; + } + + getClearDepth() { + return this._clearDepth; + } + + setClearDepth(depth) { + this._clearDepth = depth; + } + + getClearStencil() { + return this._clearStencil; + } + + setClearStencil(stencil) { + this._clearStencil = stencil; + } + + isOccluded(object) { + const renderContext = this._currentRenderContext; + + return renderContext && this.backend.isOccluded(renderContext, object); + } + + clear(color = true, depth = true, stencil = true) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead.', + ); + + return this.clearAsync(color, depth, stencil); + } + + const renderTarget = this._renderTarget || this._getFrameBufferTarget(); + + let renderTargetData = null; + + if (renderTarget !== null) { + this._textures.updateRenderTarget(renderTarget); + + renderTargetData = this._textures.get(renderTarget); + } + + this.backend.clear(color, depth, stencil, renderTargetData); + } + + clearColor() { + return this.clear(true, false, false); + } + + clearDepth() { + return this.clear(false, true, false); + } + + clearStencil() { + return this.clear(false, false, true); + } + + async clearAsync(color = true, depth = true, stencil = true) { + if (this._initialized === false) await this.init(); + + this.clear(color, depth, stencil); + } + + clearColorAsync() { + return this.clearAsync(true, false, false); + } + + clearDepthAsync() { + return this.clearAsync(false, true, false); + } + + clearStencilAsync() { + return this.clearAsync(false, false, true); + } + + get currentColorSpace() { + const renderTarget = this._renderTarget; + + if (renderTarget !== null) { + const texture = renderTarget.texture; + + return (Array.isArray(texture) ? texture[0] : texture).colorSpace; + } + + return this.outputColorSpace; + } + + dispose() { + this.info.dispose(); + + this._animation.dispose(); + this._objects.dispose(); + this._pipelines.dispose(); + this._nodes.dispose(); + this._bindings.dispose(); + this._renderLists.dispose(); + this._renderContexts.dispose(); + this._textures.dispose(); + + this.setRenderTarget(null); + this.setAnimationLoop(null); + } + + setRenderTarget(renderTarget, activeCubeFace = 0, activeMipmapLevel = 0) { + this._renderTarget = renderTarget; + this._activeCubeFace = activeCubeFace; + this._activeMipmapLevel = activeMipmapLevel; + } + + getRenderTarget() { + return this._renderTarget; + } + + setRenderObjectFunction(renderObjectFunction) { + this._renderObjectFunction = renderObjectFunction; + } + + getRenderObjectFunction() { + return this._renderObjectFunction; + } + + async computeAsync(computeNodes) { + if (this._initialized === false) await this.init(); + + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + + // + + this.info.calls++; + this.info.compute.calls++; + this.info.compute.computeCalls++; + + nodeFrame.renderId = this.info.calls; + + // + + const backend = this.backend; + const pipelines = this._pipelines; + const bindings = this._bindings; + const nodes = this._nodes; + const computeList = Array.isArray(computeNodes) ? computeNodes : [computeNodes]; + + if (computeList[0] === undefined || computeList[0].isComputeNode !== true) { + throw new Error('THREE.Renderer: .compute() expects a ComputeNode.'); + } + + backend.beginCompute(computeNodes); + + for (const computeNode of computeList) { + // onInit + + if (pipelines.has(computeNode) === false) { + const dispose = () => { + computeNode.removeEventListener('dispose', dispose); + + pipelines.delete(computeNode); + bindings.delete(computeNode); + nodes.delete(computeNode); + }; + + computeNode.addEventListener('dispose', dispose); + + // + + computeNode.onInit({ renderer: this }); + } + + nodes.updateForCompute(computeNode); + bindings.updateForCompute(computeNode); + + const computeBindings = bindings.getForCompute(computeNode); + const computePipeline = pipelines.getForCompute(computeNode, computeBindings); + + backend.compute(computeNodes, computeNode, computeBindings, computePipeline); + } + + backend.finishCompute(computeNodes); + + await this.backend.resolveTimestampAsync(computeNodes, 'compute'); + + // + + nodeFrame.renderId = previousRenderId; + } + + async hasFeatureAsync(name) { + if (this._initialized === false) await this.init(); + + return this.backend.hasFeature(name); + } + + hasFeature(name) { + if (this._initialized === false) { + console.warn( + 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', + ); + + return false; + } + + return this.backend.hasFeature(name); + } + + copyFramebufferToTexture(framebufferTexture) { + const renderContext = this._currentRenderContext; + + this._textures.updateTexture(framebufferTexture); + + this.backend.copyFramebufferToTexture(framebufferTexture, renderContext); + } + + copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0) { + this._textures.updateTexture(srcTexture); + this._textures.updateTexture(dstTexture); + + this.backend.copyTextureToTexture(srcTexture, dstTexture, srcRegion, dstPosition, level); + } + + readRenderTargetPixelsAsync(renderTarget, x, y, width, height, index = 0) { + return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); + } + + _projectObject(object, camera, groupOrder, renderList) { + if (object.visible === false) return; + + const visible = object.layers.test(camera.layers); + + if (visible) { + if (object.isGroup) { + groupOrder = object.renderOrder; + } else if (object.isLOD) { + if (object.autoUpdate === true) object.update(camera); + } else if (object.isLight) { + renderList.pushLight(object); + } else if (object.isSprite) { + if (!object.frustumCulled || _frustum.intersectsSprite(object)) { + if (this.sortObjects === true) { + _vector3.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix); + } + + const geometry = object.geometry; + const material = object.material; + + if (material.visible) { + renderList.push(object, geometry, material, groupOrder, _vector3.z, null); + } + } + } else if (object.isLineLoop) { + console.error( + 'THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.', + ); + } else if (object.isMesh || object.isLine || object.isPoints) { + if (!object.frustumCulled || _frustum.intersectsObject(object)) { + const geometry = object.geometry; + const material = object.material; + + if (this.sortObjects === true) { + if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); + + _vector3 + .copy(geometry.boundingSphere.center) + .applyMatrix4(object.matrixWorld) + .applyMatrix4(_projScreenMatrix); + } + + if (Array.isArray(material)) { + const groups = geometry.groups; + + for (let i = 0, l = groups.length; i < l; i++) { + const group = groups[i]; + const groupMaterial = material[group.materialIndex]; + + if (groupMaterial && groupMaterial.visible) { + renderList.push(object, geometry, groupMaterial, groupOrder, _vector3.z, group); + } + } + } else if (material.visible) { + renderList.push(object, geometry, material, groupOrder, _vector3.z, null); + } + } + } + } + + const children = object.children; + + for (let i = 0, l = children.length; i < l; i++) { + this._projectObject(children[i], camera, groupOrder, renderList); + } + } + + _renderObjects(renderList, camera, scene, lightsNode) { + // process renderable objects + + for (let i = 0, il = renderList.length; i < il; i++) { + const renderItem = renderList[i]; + + // @TODO: Add support for multiple materials per object. This will require to extract + // the material from the renderItem object and pass it with its group data to renderObject(). + + const { object, geometry, material, group } = renderItem; + + if (camera.isArrayCamera) { + const cameras = camera.cameras; + + for (let j = 0, jl = cameras.length; j < jl; j++) { + const camera2 = cameras[j]; + + if (object.layers.test(camera2.layers)) { + const vp = camera2.viewport; + const minDepth = vp.minDepth === undefined ? 0 : vp.minDepth; + const maxDepth = vp.maxDepth === undefined ? 1 : vp.maxDepth; + + const viewportValue = this._currentRenderContext.viewportValue; + viewportValue.copy(vp).multiplyScalar(this._pixelRatio).floor(); + viewportValue.minDepth = minDepth; + viewportValue.maxDepth = maxDepth; + + this.backend.updateViewport(this._currentRenderContext); + + this._currentRenderObjectFunction( + object, + scene, + camera2, + geometry, + material, + group, + lightsNode, + ); + } + } + } else { + this._currentRenderObjectFunction(object, scene, camera, geometry, material, group, lightsNode); + } + } + } + + renderObject(object, scene, camera, geometry, material, group, lightsNode) { + let overridePositionNode; + let overrideFragmentNode; + let overrideDepthNode; + + // + + object.onBeforeRender(this, scene, camera, geometry, material, group); + + material.onBeforeRender(this, scene, camera, geometry, material, group); + + // + + if (scene.overrideMaterial !== null) { + const overrideMaterial = scene.overrideMaterial; + + if (material.positionNode && material.positionNode.isNode) { + overridePositionNode = overrideMaterial.positionNode; + overrideMaterial.positionNode = material.positionNode; + } + + if (overrideMaterial.isShadowNodeMaterial) { + overrideMaterial.side = material.shadowSide === null ? material.side : material.shadowSide; + + if (material.depthNode && material.depthNode.isNode) { + overrideDepthNode = overrideMaterial.depthNode; + overrideMaterial.depthNode = material.depthNode; + } + + if (material.shadowNode && material.shadowNode.isNode) { + overrideFragmentNode = overrideMaterial.fragmentNode; + overrideMaterial.fragmentNode = material.shadowNode; + } + + if (this.localClippingEnabled) { + if (material.clipShadows) { + if (overrideMaterial.clippingPlanes !== material.clippingPlanes) { + overrideMaterial.clippingPlanes = material.clippingPlanes; + overrideMaterial.needsUpdate = true; + } + + if (overrideMaterial.clipIntersection !== material.clipIntersection) { + overrideMaterial.clipIntersection = material.clipIntersection; + } + } else if (Array.isArray(overrideMaterial.clippingPlanes)) { + overrideMaterial.clippingPlanes = null; + overrideMaterial.needsUpdate = true; + } + } + } + + material = overrideMaterial; + } + + // + + if (material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false) { + material.side = BackSide; + this._handleObjectFunction(object, material, scene, camera, lightsNode, group, 'backSide'); // create backSide pass id + + material.side = FrontSide; + this._handleObjectFunction(object, material, scene, camera, lightsNode, group); // use default pass id + + material.side = DoubleSide; + } else { + this._handleObjectFunction(object, material, scene, camera, lightsNode, group); + } + + // + + if (overridePositionNode !== undefined) { + scene.overrideMaterial.positionNode = overridePositionNode; + } + + if (overrideDepthNode !== undefined) { + scene.overrideMaterial.depthNode = overrideDepthNode; + } + + if (overrideFragmentNode !== undefined) { + scene.overrideMaterial.fragmentNode = overrideFragmentNode; + } + + // + + object.onAfterRender(this, scene, camera, geometry, material, group); + } + + _renderObjectDirect(object, material, scene, camera, lightsNode, group, passId) { + const renderObject = this._objects.get( + object, + material, + scene, + camera, + lightsNode, + this._currentRenderContext, + passId, + ); + renderObject.drawRange = group || object.geometry.drawRange; + + // + + this._nodes.updateBefore(renderObject); + + // + + object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld); + object.normalMatrix.getNormalMatrix(object.modelViewMatrix); + + // + + this._nodes.updateForRender(renderObject); + this._geometries.updateForRender(renderObject); + this._bindings.updateForRender(renderObject); + this._pipelines.updateForRender(renderObject); + + // + + this.backend.draw(renderObject, this.info); + } + + _createObjectPipeline(object, material, scene, camera, lightsNode, passId) { + const renderObject = this._objects.get( + object, + material, + scene, + camera, + lightsNode, + this._currentRenderContext, + passId, + ); + + // + + this._nodes.updateBefore(renderObject); + + // + + this._nodes.updateForRender(renderObject); + this._geometries.updateForRender(renderObject); + this._bindings.updateForRender(renderObject); + + this._pipelines.getForRender(renderObject, this._compilationPromises); + } + + get compute() { + return this.computeAsync; + } + + get compile() { + return this.compileAsync; + } +} + +export default Renderer; diff --git a/examples-jsm/index.js b/examples-jsm/index.js index 72e1b224b..6c7cabcb9 100644 --- a/examples-jsm/index.js +++ b/examples-jsm/index.js @@ -6,7 +6,9 @@ import prettier from 'prettier'; const files = [ 'nodes/core/Node', 'nodes/core/NodeBuilder', + 'nodes/core/NodeCache', 'nodes/core/NodeFrame', + 'nodes/core/NodeKeywords', 'nodes/core/NodeParser', 'nodes/core/constants', 'nodes/fog/FogNode', From a032d0b1ddbbd46bca92821c724fc18bf433460b Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 21:58:51 -0400 Subject: [PATCH 16/18] Update patch --- examples-jsm/changes.patch | 226 ++- examples-jsm/examples/nodes/core/Node.ts | 411 ------ .../examples/nodes/core/NodeBuilder.ts | 1011 -------------- examples-jsm/examples/nodes/core/NodeCache.ts | 18 - examples-jsm/examples/nodes/core/NodeFrame.ts | 101 -- .../examples/nodes/core/NodeKeywords.ts | 58 - .../examples/nodes/core/NodeParser.ts | 7 - examples-jsm/examples/nodes/core/constants.ts | 28 - examples-jsm/examples/nodes/fog/FogNode.ts | 38 - .../nodes/lighting/EnvironmentNode.ts | 118 -- .../examples/nodes/lighting/LightsNode.ts | 157 --- .../examples/renderers/common/Renderer.ts | 1225 ----------------- 12 files changed, 223 insertions(+), 3175 deletions(-) delete mode 100644 examples-jsm/examples/nodes/core/Node.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeBuilder.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeCache.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeFrame.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeKeywords.ts delete mode 100644 examples-jsm/examples/nodes/core/NodeParser.ts delete mode 100644 examples-jsm/examples/nodes/core/constants.ts delete mode 100644 examples-jsm/examples/nodes/fog/FogNode.ts delete mode 100644 examples-jsm/examples/nodes/lighting/EnvironmentNode.ts delete mode 100644 examples-jsm/examples/nodes/lighting/LightsNode.ts delete mode 100644 examples-jsm/examples/renderers/common/Renderer.ts diff --git a/examples-jsm/changes.patch b/examples-jsm/changes.patch index b25ac2d31..c569cac62 100644 --- a/examples-jsm/changes.patch +++ b/examples-jsm/changes.patch @@ -151,7 +151,7 @@ index 438c44dd..dd88fd34 100644 if (this !== refNode) { diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts -index ebdc13ff..1304d0da 100644 +index ebdc13ff..99c48afa 100644 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts @@ -30,6 +30,11 @@ import { @@ -179,7 +179,7 @@ index ebdc13ff..1304d0da 100644 const uniformsGroupCache = new ChainMap(); -@@ -67,10 +78,63 @@ const toFloat = value => { +@@ -67,10 +78,68 @@ const toFloat = value => { }; class NodeBuilder { @@ -222,8 +222,8 @@ index ebdc13ff..1304d0da 100644 + // codes + // vars + flow: { code: string }; ++ chaining: Node[]; + // TODO -+ // chaining + // stack + // stacks + tab: string; @@ -231,6 +231,11 @@ index ebdc13ff..1304d0da 100644 + // TODO + // currentFunctionNode + ++ context: { keywords: NodeKeywords; material: Material | Material[] }; ++ ++ cache: NodeCache; ++ globalCache: NodeCache; ++ + constructor( + object: Object3D, + renderer: Renderer, @@ -246,6 +251,200 @@ index ebdc13ff..1304d0da 100644 this.renderer = renderer; this.parser = parser; this.scene = scene; +@@ -138,7 +207,7 @@ class NodeBuilder { + return new PMREMGenerator(this.renderer); + } + +- includes(node) { ++ includes(node: Node) { + return this.nodes.includes(node); + } + +@@ -181,11 +250,11 @@ class NodeBuilder { + return bindingsArray; + } + +- setHashNode(node, hash) { ++ setHashNode(node: Node, hash: string) { + this.hashNodes[hash] = node; + } + +- addNode(node) { ++ addNode(node: Node) { + if (this.nodes.includes(node) === false) { + this.nodes.push(node); + +@@ -212,7 +281,7 @@ class NodeBuilder { + return this.chaining[this.chaining.length - 1]; + } + +- addChain(node) { ++ addChain(node: Node) { + /* + if ( this.chaining.indexOf( node ) !== - 1 ) { + +@@ -224,7 +293,7 @@ class NodeBuilder { + this.chaining.push(node); + } + +- removeChain(node) { ++ removeChain(node: Node) { + const lastChain = this.chaining.pop(); + + if (lastChain !== node) { +@@ -232,11 +301,11 @@ class NodeBuilder { + } + } + +- getMethod(method) { ++ getMethod(method: string) { + return method; + } + +- getNodeFromHash(hash) { ++ getNodeFromHash(hash: string) { + return this.hashNodes[hash]; + } + +@@ -262,7 +331,7 @@ class NodeBuilder { + return this.cache; + } + +- isAvailable(/*name*/) { ++ isAvailable(name: string) { + return false; + } + +@@ -648,7 +717,7 @@ class NodeBuilder { + return nodeCode; + } + +- addLineFlowCode(code) { ++ addLineFlowCode(code: string) { + if (code === '') return this; + + code = this.tab + code; +@@ -662,7 +731,7 @@ class NodeBuilder { + return this; + } + +- addFlowCode(code) { ++ addFlowCode(code: string) { + this.flow.code += code; + + return this; +@@ -684,7 +753,7 @@ class NodeBuilder { + return this.flowsData.get(node); + } + +- flowNode(node) { ++ flowNode(node: Node) { + const output = node.getNodeType(this); + + const flowData = this.flowChildNode(node, output); +diff --git a/examples-jsm/examples/nodes/core/NodeCache.ts b/examples-jsm/examples/nodes/core/NodeCache.ts +index 96a7e0c7..656fdf12 100644 +--- a/examples-jsm/examples/nodes/core/NodeCache.ts ++++ b/examples-jsm/examples/nodes/core/NodeCache.ts +@@ -1,6 +1,8 @@ + let id = 0; + + class NodeCache { ++ id: number; ++ + constructor() { + this.id = id++; + this.nodesData = new WeakMap(); +diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts +index b8e8d37b..399c788b 100644 +--- a/examples-jsm/examples/nodes/core/NodeFrame.ts ++++ b/examples-jsm/examples/nodes/core/NodeFrame.ts +@@ -1,6 +1,16 @@ + import { NodeUpdateType } from './constants.js'; ++import Node from './Node.js'; + + class NodeFrame { ++ time: number; ++ deltaTime: number; ++ ++ frameId: number; ++ renderId: number; ++ ++ // TODO ++ // startTime ++ + constructor() { + this.time = 0; + this.deltaTime = 0; +@@ -35,7 +45,7 @@ class NodeFrame { + return maps; + } + +- updateBeforeNode(node) { ++ updateBeforeNode(node: Node) { + const updateType = node.getUpdateBeforeType(); + const reference = node.updateReference(this); + +@@ -60,7 +70,7 @@ class NodeFrame { + } + } + +- updateNode(node) { ++ updateNode(node: Node) { + const updateType = node.getUpdateType(); + const reference = node.updateReference(this); + +diff --git a/examples-jsm/examples/nodes/core/NodeKeywords.ts b/examples-jsm/examples/nodes/core/NodeKeywords.ts +index 53da9bf5..36bdcabe 100644 +--- a/examples-jsm/examples/nodes/core/NodeKeywords.ts ++++ b/examples-jsm/examples/nodes/core/NodeKeywords.ts +@@ -1,11 +1,18 @@ ++import Node from './Node.js'; ++import NodeBuilder from './NodeBuilder.js'; ++ + class NodeKeywords { ++ keywords: string[]; ++ nodes: { [name: string]: Node }; ++ keywordsCallback: { [name: string]: (name: string) => Node }; ++ + constructor() { + this.keywords = []; +- this.nodes = []; ++ this.nodes = {}; + this.keywordsCallback = {}; + } + +- getNode(name) { ++ getNode(name: string) { + let node = this.nodes[name]; + + if (node === undefined && this.keywordsCallback[name] !== undefined) { +@@ -17,14 +24,14 @@ class NodeKeywords { + return node; + } + +- addKeyword(name, callback) { ++ addKeyword(name: string, callback: (name: string) => Node) { + this.keywords.push(name); + this.keywordsCallback[name] = callback; + + return this; + } + +- parse(code) { ++ parse(code: string) { + const keywordNames = this.keywords; + + const regExp = new RegExp(`\\b${keywordNames.join('\\b|\\b')}\\b`, 'g'); +@@ -46,7 +53,7 @@ class NodeKeywords { + return keywordNodes; + } + +- include(builder, code) { ++ include(builder: NodeBuilder, code: string) { + const keywordNodes = this.parse(code); + + for (const keywordNode of keywordNodes) { diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts index 3b01a9a6..3391a4be 100644 --- a/examples-jsm/examples/nodes/core/constants.ts @@ -298,3 +497,24 @@ index 3b01a9a6..3391a4be 100644 export const defaultShaderStages = ['fragment', 'vertex']; export const defaultBuildStages = ['setup', 'analyze', 'generate']; +diff --git a/examples-jsm/examples/nodes/fog/FogNode.ts b/examples-jsm/examples/nodes/fog/FogNode.ts +index 9417df5a..43761555 100644 +--- a/examples-jsm/examples/nodes/fog/FogNode.ts ++++ b/examples-jsm/examples/nodes/fog/FogNode.ts +@@ -1,6 +1,7 @@ + import Node, { addNodeClass } from '../core/Node.js'; + import { positionView } from '../accessors/PositionNode.js'; + import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; ++import NodeBuilder from '../core/NodeBuilder.js'; + + class FogNode extends Node { + constructor(colorNode, factorNode) { +@@ -12,7 +13,7 @@ class FogNode extends Node { + this.factorNode = factorNode; + } + +- getViewZNode(builder) { ++ getViewZNode(builder: NodeBuilder) { + let viewZ; + + const getViewZ = builder.context.getViewZ; diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts deleted file mode 100644 index 438c44dd1..000000000 --- a/examples-jsm/examples/nodes/core/Node.ts +++ /dev/null @@ -1,411 +0,0 @@ -import { EventDispatcher } from 'three'; -import { NodeUpdateType } from './constants.js'; -import { getNodeChildren, getCacheKey } from './NodeUtils.js'; -import { MathUtils } from 'three'; - -const NodeClasses = new Map(); - -let _nodeId = 0; - -class Node extends EventDispatcher { - constructor(nodeType = null) { - super(); - - this.nodeType = nodeType; - - this.updateType = NodeUpdateType.NONE; - this.updateBeforeType = NodeUpdateType.NONE; - - this.uuid = MathUtils.generateUUID(); - - this.version = 0; - - this._cacheKey = null; - this._cacheKeyVersion = 0; - - this.isNode = true; - - Object.defineProperty(this, 'id', { value: _nodeId++ }); - } - - set needsUpdate(value) { - if (value === true) { - this.version++; - } - } - - get type() { - return this.constructor.type; - } - - onUpdate(callback, updateType) { - this.updateType = updateType; - this.update = callback.bind(this.getSelf()); - - return this; - } - - onFrameUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.FRAME); - } - - onRenderUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.RENDER); - } - - onObjectUpdate(callback) { - return this.onUpdate(callback, NodeUpdateType.OBJECT); - } - - onReference(callback) { - this.updateReference = callback.bind(this.getSelf()); - - return this; - } - - getSelf() { - // Returns non-node object. - - return this.self || this; - } - - updateReference(/*state*/) { - return this; - } - - isGlobal(/*builder*/) { - return false; - } - - *getChildren() { - for (const { childNode } of getNodeChildren(this)) { - yield childNode; - } - } - - dispose() { - this.dispatchEvent({ type: 'dispose' }); - } - - traverse(callback) { - callback(this); - - for (const childNode of this.getChildren()) { - childNode.traverse(callback); - } - } - - getCacheKey(force = false) { - force = force || this.version !== this._cacheKeyVersion; - - if (force === true || this._cacheKey === null) { - this._cacheKey = getCacheKey(this, force); - this._cacheKeyVersion = this.version; - } - - return this._cacheKey; - } - - getHash(/*builder*/) { - return this.uuid; - } - - getUpdateType() { - return this.updateType; - } - - getUpdateBeforeType() { - return this.updateBeforeType; - } - - getElementType(builder) { - const type = this.getNodeType(builder); - const elementType = builder.getElementType(type); - - return elementType; - } - - getNodeType(builder) { - const nodeProperties = builder.getNodeProperties(this); - - if (nodeProperties.outputNode) { - return nodeProperties.outputNode.getNodeType(builder); - } - - return this.nodeType; - } - - getShared(builder) { - const hash = this.getHash(builder); - const nodeFromHash = builder.getNodeFromHash(hash); - - return nodeFromHash || this; - } - - setup(builder) { - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of this.getChildren()) { - nodeProperties['_node' + childNode.id] = childNode; - } - - // return a outputNode if exists - return null; - } - - construct(builder) { - // @deprecated, r157 - - console.warn('THREE.Node: construct() is deprecated. Use setup() instead.'); - - return this.setup(builder); - } - - increaseUsage(builder) { - const nodeData = builder.getDataFromNode(this); - nodeData.usageCount = nodeData.usageCount === undefined ? 1 : nodeData.usageCount + 1; - - return nodeData.usageCount; - } - - analyze(builder) { - const usageCount = this.increaseUsage(builder); - - if (usageCount === 1) { - // node flow children - - const nodeProperties = builder.getNodeProperties(this); - - for (const childNode of Object.values(nodeProperties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } - - generate(builder, output) { - const { outputNode } = builder.getNodeProperties(this); - - if (outputNode && outputNode.isNode === true) { - return outputNode.build(builder, output); - } - } - - updateBefore(/*frame*/) { - console.warn('Abstract function.'); - } - - update(/*frame*/) { - console.warn('Abstract function.'); - } - - build(builder, output = null) { - const refNode = this.getShared(builder); - - if (this !== refNode) { - return refNode.build(builder, output); - } - - builder.addNode(this); - builder.addChain(this); - - /* Build stages expected results: - - "setup" -> Node - - "analyze" -> null - - "generate" -> String - */ - let result = null; - - const buildStage = builder.getBuildStage(); - - if (buildStage === 'setup') { - this.updateReference(builder); - - const properties = builder.getNodeProperties(this); - - if (properties.initialized !== true || builder.context.tempRead === false) { - const stackNodesBeforeSetup = builder.stack.nodes.length; - - properties.initialized = true; - properties.outputNode = this.setup(builder); - - if (properties.outputNode !== null && builder.stack.nodes.length !== stackNodesBeforeSetup) { - properties.outputNode = builder.stack; - } - - for (const childNode of Object.values(properties)) { - if (childNode && childNode.isNode === true) { - childNode.build(builder); - } - } - } - } else if (buildStage === 'analyze') { - this.analyze(builder); - } else if (buildStage === 'generate') { - const isGenerateOnce = this.generate.length === 1; - - if (isGenerateOnce) { - const type = this.getNodeType(builder); - const nodeData = builder.getDataFromNode(this); - - result = nodeData.snippet; - - if (result === undefined /*|| builder.context.tempRead === false*/) { - result = this.generate(builder) || ''; - - nodeData.snippet = result; - } - - result = builder.format(result, type, output); - } else { - result = this.generate(builder, output) || ''; - } - } - - builder.removeChain(this); - - return result; - } - - getSerializeChildren() { - return getNodeChildren(this); - } - - serialize(json) { - const nodeChildren = this.getSerializeChildren(); - - const inputNodes = {}; - - for (const { property, index, childNode } of nodeChildren) { - if (index !== undefined) { - if (inputNodes[property] === undefined) { - inputNodes[property] = Number.isInteger(index) ? [] : {}; - } - - inputNodes[property][index] = childNode.toJSON(json.meta).uuid; - } else { - inputNodes[property] = childNode.toJSON(json.meta).uuid; - } - } - - if (Object.keys(inputNodes).length > 0) { - json.inputNodes = inputNodes; - } - } - - deserialize(json) { - if (json.inputNodes !== undefined) { - const nodes = json.meta.nodes; - - for (const property in json.inputNodes) { - if (Array.isArray(json.inputNodes[property])) { - const inputArray = []; - - for (const uuid of json.inputNodes[property]) { - inputArray.push(nodes[uuid]); - } - - this[property] = inputArray; - } else if (typeof json.inputNodes[property] === 'object') { - const inputObject = {}; - - for (const subProperty in json.inputNodes[property]) { - const uuid = json.inputNodes[property][subProperty]; - - inputObject[subProperty] = nodes[uuid]; - } - - this[property] = inputObject; - } else { - const uuid = json.inputNodes[property]; - - this[property] = nodes[uuid]; - } - } - } - } - - toJSON(meta) { - const { uuid, type } = this; - const isRoot = meta === undefined || typeof meta === 'string'; - - if (isRoot) { - meta = { - textures: {}, - images: {}, - nodes: {}, - }; - } - - // serialize - - let data = meta.nodes[uuid]; - - if (data === undefined) { - data = { - uuid, - type, - meta, - metadata: { - version: 4.6, - type: 'Node', - generator: 'Node.toJSON', - }, - }; - - if (isRoot !== true) meta.nodes[data.uuid] = data; - - this.serialize(data); - - delete data.meta; - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache(cache) { - const values = []; - - for (const key in cache) { - const data = cache[key]; - delete data.metadata; - values.push(data); - } - - return values; - } - - if (isRoot) { - const textures = extractFromCache(meta.textures); - const images = extractFromCache(meta.images); - const nodes = extractFromCache(meta.nodes); - - if (textures.length > 0) data.textures = textures; - if (images.length > 0) data.images = images; - if (nodes.length > 0) data.nodes = nodes; - } - - return data; - } -} - -export default Node; - -export function addNodeClass(type, nodeClass) { - if (typeof nodeClass !== 'function' || !type) throw new Error(`Node class ${type} is not a class`); - if (NodeClasses.has(type)) { - console.warn(`Redefinition of node class ${type}`); - return; - } - - NodeClasses.set(type, nodeClass); - nodeClass.type = type; -} - -export function createNodeFromType(type) { - const Class = NodeClasses.get(type); - - if (Class !== undefined) { - return new Class(); - } -} diff --git a/examples-jsm/examples/nodes/core/NodeBuilder.ts b/examples-jsm/examples/nodes/core/NodeBuilder.ts deleted file mode 100644 index ebdc13ff1..000000000 --- a/examples-jsm/examples/nodes/core/NodeBuilder.ts +++ /dev/null @@ -1,1011 +0,0 @@ -import NodeUniform from './NodeUniform.js'; -import NodeAttribute from './NodeAttribute.js'; -import NodeVarying from './NodeVarying.js'; -import NodeVar from './NodeVar.js'; -import NodeCode from './NodeCode.js'; -import NodeKeywords from './NodeKeywords.js'; -import NodeCache from './NodeCache.js'; -import ParameterNode from './ParameterNode.js'; -import FunctionNode from '../code/FunctionNode.js'; -import { createNodeMaterialFromType, default as NodeMaterial } from '../materials/NodeMaterial.js'; -import { NodeUpdateType, defaultBuildStages, shaderStages } from './constants.js'; - -import { - FloatNodeUniform, - Vector2NodeUniform, - Vector3NodeUniform, - Vector4NodeUniform, - ColorNodeUniform, - Matrix3NodeUniform, - Matrix4NodeUniform, -} from '../../renderers/common/nodes/NodeUniform.js'; - -import { - REVISION, - RenderTarget, - Color, - Vector2, - Vector3, - Vector4, - IntType, - UnsignedIntType, - Float16BufferAttribute, -} from 'three'; - -import { stack } from './StackNode.js'; -import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; - -import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; -import ChainMap from '../../renderers/common/ChainMap.js'; - -import PMREMGenerator from '../../renderers/common/extras/PMREMGenerator.js'; - -const uniformsGroupCache = new ChainMap(); - -const typeFromLength = new Map([ - [2, 'vec2'], - [3, 'vec3'], - [4, 'vec4'], - [9, 'mat3'], - [16, 'mat4'], -]); - -const typeFromArray = new Map([ - [Int8Array, 'int'], - [Int16Array, 'int'], - [Int32Array, 'int'], - [Uint8Array, 'uint'], - [Uint16Array, 'uint'], - [Uint32Array, 'uint'], - [Float32Array, 'float'], -]); - -const toFloat = value => { - value = Number(value); - - return value + (value % 1 ? '' : '.0'); -}; - -class NodeBuilder { - constructor(object, renderer, parser, scene = null, material = null) { - this.object = object; - this.material = material || (object && object.material) || null; - this.geometry = (object && object.geometry) || null; - this.renderer = renderer; - this.parser = parser; - this.scene = scene; - - this.nodes = []; - this.updateNodes = []; - this.updateBeforeNodes = []; - this.hashNodes = {}; - - this.lightsNode = null; - this.environmentNode = null; - this.fogNode = null; - - this.clippingContext = null; - - this.vertexShader = null; - this.fragmentShader = null; - this.computeShader = null; - - this.flowNodes = { vertex: [], fragment: [], compute: [] }; - this.flowCode = { vertex: '', fragment: '', compute: [] }; - this.uniforms = { vertex: [], fragment: [], compute: [], index: 0 }; - this.structs = { vertex: [], fragment: [], compute: [], index: 0 }; - this.bindings = { vertex: [], fragment: [], compute: [] }; - this.bindingsOffset = { vertex: 0, fragment: 0, compute: 0 }; - this.bindingsArray = null; - this.attributes = []; - this.bufferAttributes = []; - this.varyings = []; - this.codes = {}; - this.vars = {}; - this.flow = { code: '' }; - this.chaining = []; - this.stack = stack(); - this.stacks = []; - this.tab = '\t'; - - this.currentFunctionNode = null; - - this.context = { - keywords: new NodeKeywords(), - material: this.material, - }; - - this.cache = new NodeCache(); - this.globalCache = this.cache; - - this.flowsData = new WeakMap(); - - this.shaderStage = null; - this.buildStage = null; - } - - createRenderTarget(width, height, options) { - return new RenderTarget(width, height, options); - } - - createCubeRenderTarget(size, options) { - return new CubeRenderTarget(size, options); - } - - createPMREMGenerator() { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return new PMREMGenerator(this.renderer); - } - - includes(node) { - return this.nodes.includes(node); - } - - _getSharedBindings(bindings) { - const shared = []; - - for (const binding of bindings) { - if (binding.shared === true) { - // nodes is the chainmap key - const nodes = binding.getNodes(); - - let sharedBinding = uniformsGroupCache.get(nodes); - - if (sharedBinding === undefined) { - uniformsGroupCache.set(nodes, binding); - - sharedBinding = binding; - } - - shared.push(sharedBinding); - } else { - shared.push(binding); - } - } - - return shared; - } - - getBindings() { - let bindingsArray = this.bindingsArray; - - if (bindingsArray === null) { - const bindings = this.bindings; - - this.bindingsArray = bindingsArray = this._getSharedBindings( - this.material !== null ? [...bindings.vertex, ...bindings.fragment] : bindings.compute, - ); - } - - return bindingsArray; - } - - setHashNode(node, hash) { - this.hashNodes[hash] = node; - } - - addNode(node) { - if (this.nodes.includes(node) === false) { - this.nodes.push(node); - - this.setHashNode(node, node.getHash(this)); - } - } - - buildUpdateNodes() { - for (const node of this.nodes) { - const updateType = node.getUpdateType(); - const updateBeforeType = node.getUpdateBeforeType(); - - if (updateType !== NodeUpdateType.NONE) { - this.updateNodes.push(node.getSelf()); - } - - if (updateBeforeType !== NodeUpdateType.NONE) { - this.updateBeforeNodes.push(node); - } - } - } - - get currentNode() { - return this.chaining[this.chaining.length - 1]; - } - - addChain(node) { - /* - if ( this.chaining.indexOf( node ) !== - 1 ) { - - console.warn( 'Recursive node: ', node ); - - } - */ - - this.chaining.push(node); - } - - removeChain(node) { - const lastChain = this.chaining.pop(); - - if (lastChain !== node) { - throw new Error('NodeBuilder: Invalid node chaining!'); - } - } - - getMethod(method) { - return method; - } - - getNodeFromHash(hash) { - return this.hashNodes[hash]; - } - - addFlow(shaderStage, node) { - this.flowNodes[shaderStage].push(node); - - return node; - } - - setContext(context) { - this.context = context; - } - - getContext() { - return this.context; - } - - setCache(cache) { - this.cache = cache; - } - - getCache() { - return this.cache; - } - - isAvailable(/*name*/) { - return false; - } - - getVertexIndex() { - console.warn('Abstract function.'); - } - - getInstanceIndex() { - console.warn('Abstract function.'); - } - - getFrontFacing() { - console.warn('Abstract function.'); - } - - getFragCoord() { - console.warn('Abstract function.'); - } - - isFlipY() { - return false; - } - - generateTexture(/* texture, textureProperty, uvSnippet */) { - console.warn('Abstract function.'); - } - - generateTextureLod(/* texture, textureProperty, uvSnippet, levelSnippet */) { - console.warn('Abstract function.'); - } - - generateConst(type, value = null) { - if (value === null) { - if (type === 'float' || type === 'int' || type === 'uint') value = 0; - else if (type === 'bool') value = false; - else if (type === 'color') value = new Color(); - else if (type === 'vec2') value = new Vector2(); - else if (type === 'vec3') value = new Vector3(); - else if (type === 'vec4') value = new Vector4(); - } - - if (type === 'float') return toFloat(value); - if (type === 'int') return `${Math.round(value)}`; - if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u'; - if (type === 'bool') return value ? 'true' : 'false'; - if (type === 'color') - return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )`; - - const typeLength = this.getTypeLength(type); - - const componentType = this.getComponentType(type); - - const generateConst = value => this.generateConst(componentType, value); - - if (typeLength === 2) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)} )`; - } else if (typeLength === 3) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)} )`; - } else if (typeLength === 4) { - return `${this.getType(type)}( ${generateConst(value.x)}, ${generateConst(value.y)}, ${generateConst(value.z)}, ${generateConst(value.w)} )`; - } else if (typeLength > 4 && value && (value.isMatrix3 || value.isMatrix4)) { - return `${this.getType(type)}( ${value.elements.map(generateConst).join(', ')} )`; - } else if (typeLength > 4) { - return `${this.getType(type)}()`; - } - - throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`); - } - - getType(type) { - if (type === 'color') return 'vec3'; - - return type; - } - - generateMethod(method) { - return method; - } - - hasGeometryAttribute(name) { - return this.geometry && this.geometry.getAttribute(name) !== undefined; - } - - getAttribute(name, type) { - const attributes = this.attributes; - - // find attribute - - for (const attribute of attributes) { - if (attribute.name === name) { - return attribute; - } - } - - // create a new if no exist - - const attribute = new NodeAttribute(name, type); - - attributes.push(attribute); - - return attribute; - } - - getPropertyName(node /*, shaderStage*/) { - return node.name; - } - - isVector(type) { - return /vec\d/.test(type); - } - - isMatrix(type) { - return /mat\d/.test(type); - } - - isReference(type) { - return ( - type === 'void' || - type === 'property' || - type === 'sampler' || - type === 'texture' || - type === 'cubeTexture' || - type === 'storageTexture' || - type === 'texture3D' - ); - } - - needsColorSpaceToLinear(/*texture*/) { - return false; - } - - getComponentTypeFromTexture(texture) { - const type = texture.type; - - if (texture.isDataTexture) { - if (type === IntType) return 'int'; - if (type === UnsignedIntType) return 'uint'; - } - - return 'float'; - } - - getElementType(type) { - if (type === 'mat2') return 'vec2'; - if (type === 'mat3') return 'vec3'; - if (type === 'mat4') return 'vec4'; - - return this.getComponentType(type); - } - - getComponentType(type) { - type = this.getVectorType(type); - - if (type === 'float' || type === 'bool' || type === 'int' || type === 'uint') return type; - - const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type); - - if (componentType === null) return null; - - if (componentType[1] === 'b') return 'bool'; - if (componentType[1] === 'i') return 'int'; - if (componentType[1] === 'u') return 'uint'; - - return 'float'; - } - - getVectorType(type) { - if (type === 'color') return 'vec3'; - if (type === 'texture' || type === 'cubeTexture' || type === 'storageTexture' || type === 'texture3D') - return 'vec4'; - - return type; - } - - getTypeFromLength(length, componentType = 'float') { - if (length === 1) return componentType; - - const baseType = typeFromLength.get(length); - const prefix = componentType === 'float' ? '' : componentType[0]; - - return prefix + baseType; - } - - getTypeFromArray(array) { - return typeFromArray.get(array.constructor); - } - - getTypeFromAttribute(attribute) { - let dataAttribute = attribute; - - if (attribute.isInterleavedBufferAttribute) dataAttribute = attribute.data; - - const array = dataAttribute.array; - const itemSize = attribute.itemSize; - const normalized = attribute.normalized; - - let arrayType; - - if (!(attribute instanceof Float16BufferAttribute) && normalized !== true) { - arrayType = this.getTypeFromArray(array); - } - - return this.getTypeFromLength(itemSize, arrayType); - } - - getTypeLength(type) { - const vecType = this.getVectorType(type); - const vecNum = /vec([2-4])/.exec(vecType); - - if (vecNum !== null) return Number(vecNum[1]); - if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1; - if (/mat2/.test(type) === true) return 4; - if (/mat3/.test(type) === true) return 9; - if (/mat4/.test(type) === true) return 16; - - return 0; - } - - getVectorFromMatrix(type) { - return type.replace('mat', 'vec'); - } - - changeComponentType(type, newComponentType) { - return this.getTypeFromLength(this.getTypeLength(type), newComponentType); - } - - getIntegerType(type) { - const componentType = this.getComponentType(type); - - if (componentType === 'int' || componentType === 'uint') return type; - - return this.changeComponentType(type, 'int'); - } - - addStack() { - this.stack = stack(this.stack); - - this.stacks.push(getCurrentStack() || this.stack); - setCurrentStack(this.stack); - - return this.stack; - } - - removeStack() { - const lastStack = this.stack; - this.stack = lastStack.parent; - - setCurrentStack(this.stacks.pop()); - - return lastStack; - } - - getDataFromNode(node, shaderStage = this.shaderStage, cache = null) { - cache = cache === null ? (node.isGlobal(this) ? this.globalCache : this.cache) : cache; - - let nodeData = cache.getNodeData(node); - - if (nodeData === undefined) { - nodeData = {}; - - cache.setNodeData(node, nodeData); - } - - if (nodeData[shaderStage] === undefined) nodeData[shaderStage] = {}; - - return nodeData[shaderStage]; - } - - getNodeProperties(node, shaderStage = 'any') { - const nodeData = this.getDataFromNode(node, shaderStage); - - return nodeData.properties || (nodeData.properties = { outputNode: null }); - } - - getBufferAttributeFromNode(node, type) { - const nodeData = this.getDataFromNode(node); - - let bufferAttribute = nodeData.bufferAttribute; - - if (bufferAttribute === undefined) { - const index = this.uniforms.index++; - - bufferAttribute = new NodeAttribute('nodeAttribute' + index, type, node); - - this.bufferAttributes.push(bufferAttribute); - - nodeData.bufferAttribute = bufferAttribute; - } - - return bufferAttribute; - } - - getStructTypeFromNode(node, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - if (nodeData.structType === undefined) { - const index = this.structs.index++; - - node.name = `StructType${index}`; - this.structs[shaderStage].push(node); - - nodeData.structType = node; - } - - return node; - } - - getUniformFromNode(node, type, shaderStage = this.shaderStage, name = null) { - const nodeData = this.getDataFromNode(node, shaderStage, this.globalCache); - - let nodeUniform = nodeData.uniform; - - if (nodeUniform === undefined) { - const index = this.uniforms.index++; - - nodeUniform = new NodeUniform(name || 'nodeUniform' + index, type, node); - - this.uniforms[shaderStage].push(nodeUniform); - - nodeData.uniform = nodeUniform; - } - - return nodeUniform; - } - - getVarFromNode(node, name = null, type = node.getNodeType(this), shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage); - - let nodeVar = nodeData.variable; - - if (nodeVar === undefined) { - const vars = this.vars[shaderStage] || (this.vars[shaderStage] = []); - - if (name === null) name = 'nodeVar' + vars.length; - - nodeVar = new NodeVar(name, type); - - vars.push(nodeVar); - - nodeData.variable = nodeVar; - } - - return nodeVar; - } - - getVaryingFromNode(node, name = null, type = node.getNodeType(this)) { - const nodeData = this.getDataFromNode(node, 'any'); - - let nodeVarying = nodeData.varying; - - if (nodeVarying === undefined) { - const varyings = this.varyings; - const index = varyings.length; - - if (name === null) name = 'nodeVarying' + index; - - nodeVarying = new NodeVarying(name, type); - - varyings.push(nodeVarying); - - nodeData.varying = nodeVarying; - } - - return nodeVarying; - } - - getCodeFromNode(node, type, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node); - - let nodeCode = nodeData.code; - - if (nodeCode === undefined) { - const codes = this.codes[shaderStage] || (this.codes[shaderStage] = []); - const index = codes.length; - - nodeCode = new NodeCode('nodeCode' + index, type); - - codes.push(nodeCode); - - nodeData.code = nodeCode; - } - - return nodeCode; - } - - addLineFlowCode(code) { - if (code === '') return this; - - code = this.tab + code; - - if (!/;\s*$/.test(code)) { - code = code + ';\n'; - } - - this.flow.code += code; - - return this; - } - - addFlowCode(code) { - this.flow.code += code; - - return this; - } - - addFlowTab() { - this.tab += '\t'; - - return this; - } - - removeFlowTab() { - this.tab = this.tab.slice(0, -1); - - return this; - } - - getFlowData(node /*, shaderStage*/) { - return this.flowsData.get(node); - } - - flowNode(node) { - const output = node.getNodeType(this); - - const flowData = this.flowChildNode(node, output); - - this.flowsData.set(node, flowData); - - return flowData; - } - - buildFunctionNode(shaderNode) { - const fn = new FunctionNode(); - - const previous = this.currentFunctionNode; - - this.currentFunctionNode = fn; - - fn.code = this.buildFunctionCode(shaderNode); - - this.currentFunctionNode = previous; - - return fn; - } - - flowShaderNode(shaderNode) { - const layout = shaderNode.layout; - - let inputs; - - if (shaderNode.isArrayInput) { - inputs = []; - - for (const input of layout.inputs) { - inputs.push(new ParameterNode(input.type, input.name)); - } - } else { - inputs = {}; - - for (const input of layout.inputs) { - inputs[input.name] = new ParameterNode(input.type, input.name); - } - } - - // - - shaderNode.layout = null; - - const callNode = shaderNode.call(inputs); - const flowData = this.flowStagesNode(callNode, layout.type); - - shaderNode.layout = layout; - - return flowData; - } - - flowStagesNode(node, output = null) { - const previousFlow = this.flow; - const previousVars = this.vars; - const previousBuildStage = this.buildStage; - - const flow = { - code: '', - }; - - this.flow = flow; - this.vars = {}; - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - flow.result = node.build(this, output); - } - - flow.vars = this.getVars(this.shaderStage); - - this.flow = previousFlow; - this.vars = previousVars; - this.setBuildStage(previousBuildStage); - - return flow; - } - - getFunctionOperator() { - return null; - } - - flowChildNode(node, output = null) { - const previousFlow = this.flow; - - const flow = { - code: '', - }; - - this.flow = flow; - - flow.result = node.build(this, output); - - this.flow = previousFlow; - - return flow; - } - - flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { - const previousShaderStage = this.shaderStage; - - this.setShaderStage(shaderStage); - - const flowData = this.flowChildNode(node, output); - - if (propertyName !== null) { - flowData.code += `${this.tab + propertyName} = ${flowData.result};\n`; - } - - this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code; - - this.setShaderStage(previousShaderStage); - - return flowData; - } - - getAttributesArray() { - return this.attributes.concat(this.bufferAttributes); - } - - getAttributes(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVaryings(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getVar(type, name) { - return `${this.getType(type)} ${name}`; - } - - getVars(shaderStage) { - let snippet = ''; - - const vars = this.vars[shaderStage]; - - if (vars !== undefined) { - for (const variable of vars) { - snippet += `${this.getVar(variable.type, variable.name)}; `; - } - } - - return snippet; - } - - getUniforms(/*shaderStage*/) { - console.warn('Abstract function.'); - } - - getCodes(shaderStage) { - const codes = this.codes[shaderStage]; - - let code = ''; - - if (codes !== undefined) { - for (const nodeCode of codes) { - code += nodeCode.code + '\n'; - } - } - - return code; - } - - getHash() { - return this.vertexShader + this.fragmentShader + this.computeShader; - } - - setShaderStage(shaderStage) { - this.shaderStage = shaderStage; - } - - getShaderStage() { - return this.shaderStage; - } - - setBuildStage(buildStage) { - this.buildStage = buildStage; - } - - getBuildStage() { - return this.buildStage; - } - - buildCode() { - console.warn('Abstract function.'); - } - - build() { - const { object, material } = this; - - if (material !== null) { - NodeMaterial.fromMaterial(material).build(this); - } else { - this.addFlow('compute', object); - } - - // setup() -> stage 1: create possible new nodes and returns an output reference node - // analyze() -> stage 2: analyze nodes to possible optimization and validation - // generate() -> stage 3: generate shader - - for (const buildStage of defaultBuildStages) { - this.setBuildStage(buildStage); - - if (this.context.vertex && this.context.vertex.isNode) { - this.flowNodeFromShaderStage('vertex', this.context.vertex); - } - - for (const shaderStage of shaderStages) { - this.setShaderStage(shaderStage); - - const flowNodes = this.flowNodes[shaderStage]; - - for (const node of flowNodes) { - if (buildStage === 'generate') { - this.flowNode(node); - } else { - node.build(this); - } - } - } - } - - this.setBuildStage(null); - this.setShaderStage(null); - - // stage 4: build code for a specific output - - this.buildCode(); - this.buildUpdateNodes(); - - return this; - } - - getNodeUniform(uniformNode, type) { - if (type === 'float') return new FloatNodeUniform(uniformNode); - if (type === 'vec2') return new Vector2NodeUniform(uniformNode); - if (type === 'vec3') return new Vector3NodeUniform(uniformNode); - if (type === 'vec4') return new Vector4NodeUniform(uniformNode); - if (type === 'color') return new ColorNodeUniform(uniformNode); - if (type === 'mat3') return new Matrix3NodeUniform(uniformNode); - if (type === 'mat4') return new Matrix4NodeUniform(uniformNode); - - throw new Error(`Uniform "${type}" not declared.`); - } - - createNodeMaterial(type = 'NodeMaterial') { - // TODO: Move Materials.js to outside of the Nodes.js in order to remove this function and improve tree-shaking support - - return createNodeMaterialFromType(type); - } - - format(snippet, fromType, toType) { - fromType = this.getVectorType(fromType); - toType = this.getVectorType(toType); - - if (fromType === toType || toType === null || this.isReference(toType)) { - return snippet; - } - - const fromTypeLength = this.getTypeLength(fromType); - const toTypeLength = this.getTypeLength(toType); - - if (fromTypeLength > 4) { - // fromType is matrix-like - - // @TODO: ignore for now - - return snippet; - } - - if (toTypeLength > 4 || toTypeLength === 0) { - // toType is matrix-like or unknown - - // @TODO: ignore for now - - return snippet; - } - - if (fromTypeLength === toTypeLength) { - return `${this.getType(toType)}( ${snippet} )`; - } - - if (fromTypeLength > toTypeLength) { - return this.format( - `${snippet}.${'xyz'.slice(0, toTypeLength)}`, - this.getTypeFromLength(toTypeLength, this.getComponentType(fromType)), - toType, - ); - } - - if (toTypeLength === 4 && fromTypeLength > 1) { - // toType is vec4-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )`; - } - - if (fromTypeLength === 2) { - // fromType is vec2-like and toType is vec3-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )`; - } - - if (fromTypeLength === 1 && toTypeLength > 1 && fromType[0] !== toType[0]) { - // fromType is float-like - - // convert a number value to vector type, e.g: - // vec3( 1u ) -> vec3( float( 1u ) ) - - snippet = `${this.getType(this.getComponentType(toType))}( ${snippet} )`; - } - - return `${this.getType(toType)}( ${snippet} )`; // fromType is float-like - } - - getSignature() { - return `// Three.js r${REVISION} - NodeMaterial System\n`; - } -} - -export default NodeBuilder; diff --git a/examples-jsm/examples/nodes/core/NodeCache.ts b/examples-jsm/examples/nodes/core/NodeCache.ts deleted file mode 100644 index 96a7e0c76..000000000 --- a/examples-jsm/examples/nodes/core/NodeCache.ts +++ /dev/null @@ -1,18 +0,0 @@ -let id = 0; - -class NodeCache { - constructor() { - this.id = id++; - this.nodesData = new WeakMap(); - } - - getNodeData(node) { - return this.nodesData.get(node); - } - - setNodeData(node, data) { - this.nodesData.set(node, data); - } -} - -export default NodeCache; diff --git a/examples-jsm/examples/nodes/core/NodeFrame.ts b/examples-jsm/examples/nodes/core/NodeFrame.ts deleted file mode 100644 index b8e8d37b6..000000000 --- a/examples-jsm/examples/nodes/core/NodeFrame.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { NodeUpdateType } from './constants.js'; - -class NodeFrame { - constructor() { - this.time = 0; - this.deltaTime = 0; - - this.frameId = 0; - this.renderId = 0; - - this.startTime = null; - - this.updateMap = new WeakMap(); - this.updateBeforeMap = new WeakMap(); - - this.renderer = null; - this.material = null; - this.camera = null; - this.object = null; - this.scene = null; - } - - _getMaps(referenceMap, nodeRef) { - let maps = referenceMap.get(nodeRef); - - if (maps === undefined) { - maps = { - renderMap: new WeakMap(), - frameMap: new WeakMap(), - }; - - referenceMap.set(nodeRef, maps); - } - - return maps; - } - - updateBeforeNode(node) { - const updateType = node.getUpdateBeforeType(); - const reference = node.updateReference(this); - - if (updateType === NodeUpdateType.FRAME) { - const { frameMap } = this._getMaps(this.updateBeforeMap, reference); - - if (frameMap.get(reference) !== this.frameId) { - if (node.updateBefore(this) !== false) { - frameMap.set(reference, this.frameId); - } - } - } else if (updateType === NodeUpdateType.RENDER) { - const { renderMap } = this._getMaps(this.updateBeforeMap, reference); - - if (renderMap.get(reference) !== this.renderId) { - if (node.updateBefore(this) !== false) { - renderMap.set(reference, this.renderId); - } - } - } else if (updateType === NodeUpdateType.OBJECT) { - node.updateBefore(this); - } - } - - updateNode(node) { - const updateType = node.getUpdateType(); - const reference = node.updateReference(this); - - if (updateType === NodeUpdateType.FRAME) { - const { frameMap } = this._getMaps(this.updateMap, reference); - - if (frameMap.get(reference) !== this.frameId) { - if (node.update(this) !== false) { - frameMap.set(reference, this.frameId); - } - } - } else if (updateType === NodeUpdateType.RENDER) { - const { renderMap } = this._getMaps(this.updateMap, reference); - - if (renderMap.get(reference) !== this.renderId) { - if (node.update(this) !== false) { - renderMap.set(reference, this.renderId); - } - } - } else if (updateType === NodeUpdateType.OBJECT) { - node.update(this); - } - } - - update() { - this.frameId++; - - if (this.lastTime === undefined) this.lastTime = performance.now(); - - this.deltaTime = (performance.now() - this.lastTime) / 1000; - - this.lastTime = performance.now(); - - this.time += this.deltaTime; - } -} - -export default NodeFrame; diff --git a/examples-jsm/examples/nodes/core/NodeKeywords.ts b/examples-jsm/examples/nodes/core/NodeKeywords.ts deleted file mode 100644 index 53da9bf50..000000000 --- a/examples-jsm/examples/nodes/core/NodeKeywords.ts +++ /dev/null @@ -1,58 +0,0 @@ -class NodeKeywords { - constructor() { - this.keywords = []; - this.nodes = []; - this.keywordsCallback = {}; - } - - getNode(name) { - let node = this.nodes[name]; - - if (node === undefined && this.keywordsCallback[name] !== undefined) { - node = this.keywordsCallback[name](name); - - this.nodes[name] = node; - } - - return node; - } - - addKeyword(name, callback) { - this.keywords.push(name); - this.keywordsCallback[name] = callback; - - return this; - } - - parse(code) { - const keywordNames = this.keywords; - - const regExp = new RegExp(`\\b${keywordNames.join('\\b|\\b')}\\b`, 'g'); - - const codeKeywords = code.match(regExp); - - const keywordNodes = []; - - if (codeKeywords !== null) { - for (const keyword of codeKeywords) { - const node = this.getNode(keyword); - - if (node !== undefined && keywordNodes.indexOf(node) === -1) { - keywordNodes.push(node); - } - } - } - - return keywordNodes; - } - - include(builder, code) { - const keywordNodes = this.parse(code); - - for (const keywordNode of keywordNodes) { - keywordNode.build(builder); - } - } -} - -export default NodeKeywords; diff --git a/examples-jsm/examples/nodes/core/NodeParser.ts b/examples-jsm/examples/nodes/core/NodeParser.ts deleted file mode 100644 index 9849452f1..000000000 --- a/examples-jsm/examples/nodes/core/NodeParser.ts +++ /dev/null @@ -1,7 +0,0 @@ -class NodeParser { - parseFunction(/*source*/) { - console.warn('Abstract function.'); - } -} - -export default NodeParser; diff --git a/examples-jsm/examples/nodes/core/constants.ts b/examples-jsm/examples/nodes/core/constants.ts deleted file mode 100644 index 3b01a9a6d..000000000 --- a/examples-jsm/examples/nodes/core/constants.ts +++ /dev/null @@ -1,28 +0,0 @@ -export const NodeShaderStage = { - VERTEX: 'vertex', - FRAGMENT: 'fragment', -}; - -export const NodeUpdateType = { - NONE: 'none', - FRAME: 'frame', - RENDER: 'render', - OBJECT: 'object', -}; - -export const NodeType = { - BOOLEAN: 'bool', - INTEGER: 'int', - FLOAT: 'float', - VECTOR2: 'vec2', - VECTOR3: 'vec3', - VECTOR4: 'vec4', - MATRIX2: 'mat2', - MATRIX3: 'mat3', - MATRIX4: 'mat4', -}; - -export const defaultShaderStages = ['fragment', 'vertex']; -export const defaultBuildStages = ['setup', 'analyze', 'generate']; -export const shaderStages = [...defaultShaderStages, 'compute']; -export const vectorComponents = ['x', 'y', 'z', 'w']; diff --git a/examples-jsm/examples/nodes/fog/FogNode.ts b/examples-jsm/examples/nodes/fog/FogNode.ts deleted file mode 100644 index 9417df5a5..000000000 --- a/examples-jsm/examples/nodes/fog/FogNode.ts +++ /dev/null @@ -1,38 +0,0 @@ -import Node, { addNodeClass } from '../core/Node.js'; -import { positionView } from '../accessors/PositionNode.js'; -import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js'; - -class FogNode extends Node { - constructor(colorNode, factorNode) { - super('float'); - - this.isFogNode = true; - - this.colorNode = colorNode; - this.factorNode = factorNode; - } - - getViewZNode(builder) { - let viewZ; - - const getViewZ = builder.context.getViewZ; - - if (getViewZ !== undefined) { - viewZ = getViewZ(this); - } - - return (viewZ || positionView.z).negate(); - } - - setup() { - return this.factorNode; - } -} - -export default FogNode; - -export const fog = nodeProxy(FogNode); - -addNodeElement('fog', fog); - -addNodeClass('FogNode', FogNode); diff --git a/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts b/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts deleted file mode 100644 index 56f8109c2..000000000 --- a/examples-jsm/examples/nodes/lighting/EnvironmentNode.ts +++ /dev/null @@ -1,118 +0,0 @@ -import LightingNode from './LightingNode.js'; -import { cache } from '../core/CacheNode.js'; -import { context } from '../core/ContextNode.js'; -import { roughness, clearcoatRoughness } from '../core/PropertyNode.js'; -import { cameraViewMatrix } from '../accessors/CameraNode.js'; -import { - transformedClearcoatNormalView, - transformedNormalView, - transformedNormalWorld, -} from '../accessors/NormalNode.js'; -import { positionViewDirection } from '../accessors/PositionNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { float } from '../shadernode/ShaderNode.js'; -import { reference } from '../accessors/ReferenceNode.js'; -import { transformedBentNormalView } from '../accessors/AccessorsUtils.js'; -import { pmremTexture } from '../pmrem/PMREMNode.js'; - -const envNodeCache = new WeakMap(); - -class EnvironmentNode extends LightingNode { - constructor(envNode = null) { - super(); - - this.envNode = envNode; - } - - setup(builder) { - let envNode = this.envNode; - - if (envNode.isTextureNode) { - let cacheEnvNode = envNodeCache.get(envNode.value); - - if (cacheEnvNode === undefined) { - cacheEnvNode = pmremTexture(envNode.value); - - envNodeCache.set(envNode.value, cacheEnvNode); - } - - envNode = cacheEnvNode; - } - - // - - const { material } = builder; - - const envMap = material.envMap; - const intensity = envMap - ? reference('envMapIntensity', 'float', builder.material) - : reference('environmentIntensity', 'float', builder.scene); // @TODO: Add materialEnvIntensity in MaterialNode - - const useAnisotropy = material.useAnisotropy === true || material.anisotropy > 0; - const radianceNormalView = useAnisotropy ? transformedBentNormalView : transformedNormalView; - - const radiance = context(envNode, createRadianceContext(roughness, radianceNormalView)).mul(intensity); - const irradiance = context(envNode, createIrradianceContext(transformedNormalWorld)) - .mul(Math.PI) - .mul(intensity); - - const isolateRadiance = cache(radiance); - - // - - builder.context.radiance.addAssign(isolateRadiance); - - builder.context.iblIrradiance.addAssign(irradiance); - - // - - const clearcoatRadiance = builder.context.lightingModel.clearcoatRadiance; - - if (clearcoatRadiance) { - const clearcoatRadianceContext = context( - envNode, - createRadianceContext(clearcoatRoughness, transformedClearcoatNormalView), - ).mul(intensity); - const isolateClearcoatRadiance = cache(clearcoatRadianceContext); - - clearcoatRadiance.addAssign(isolateClearcoatRadiance); - } - } -} - -const createRadianceContext = (roughnessNode, normalViewNode) => { - let reflectVec = null; - - return { - getUV: () => { - if (reflectVec === null) { - reflectVec = positionViewDirection.negate().reflect(normalViewNode); - - // Mixing the reflection with the normal is more accurate and keeps rough objects from gathering light from behind their tangent plane. - reflectVec = roughnessNode.mul(roughnessNode).mix(reflectVec, normalViewNode).normalize(); - - reflectVec = reflectVec.transformDirection(cameraViewMatrix); - } - - return reflectVec; - }, - getTextureLevel: () => { - return roughnessNode; - }, - }; -}; - -const createIrradianceContext = normalWorldNode => { - return { - getUV: () => { - return normalWorldNode; - }, - getTextureLevel: () => { - return float(1.0); - }, - }; -}; - -export default EnvironmentNode; - -addNodeClass('EnvironmentNode', EnvironmentNode); diff --git a/examples-jsm/examples/nodes/lighting/LightsNode.ts b/examples-jsm/examples/nodes/lighting/LightsNode.ts deleted file mode 100644 index b3695ea8b..000000000 --- a/examples-jsm/examples/nodes/lighting/LightsNode.ts +++ /dev/null @@ -1,157 +0,0 @@ -import Node from '../core/Node.js'; -import AnalyticLightNode from './AnalyticLightNode.js'; -import { nodeObject, nodeProxy, vec3 } from '../shadernode/ShaderNode.js'; - -const LightNodes = new WeakMap(); - -const sortLights = lights => { - return lights.sort((a, b) => a.id - b.id); -}; - -class LightsNode extends Node { - constructor(lightNodes = []) { - super('vec3'); - - this.totalDiffuseNode = vec3().temp('totalDiffuse'); - this.totalSpecularNode = vec3().temp('totalSpecular'); - - this.outgoingLightNode = vec3().temp('outgoingLight'); - - this.lightNodes = lightNodes; - - this._hash = null; - } - - get hasLight() { - return this.lightNodes.length > 0; - } - - getHash() { - if (this._hash === null) { - const hash = []; - - for (const lightNode of this.lightNodes) { - hash.push(lightNode.getHash()); - } - - this._hash = 'lights-' + hash.join(','); - } - - return this._hash; - } - - setup(builder) { - const context = builder.context; - const lightingModel = context.lightingModel; - - let outgoingLightNode = this.outgoingLightNode; - - if (lightingModel) { - const { lightNodes, totalDiffuseNode, totalSpecularNode } = this; - - context.outgoingLight = outgoingLightNode; - - const stack = builder.addStack(); - - // - - lightingModel.start(context, stack, builder); - - // lights - - for (const lightNode of lightNodes) { - lightNode.build(builder); - } - - // - - lightingModel.indirectDiffuse(context, stack, builder); - lightingModel.indirectSpecular(context, stack, builder); - lightingModel.ambientOcclusion(context, stack, builder); - - // - - const { backdrop, backdropAlpha } = context; - const { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular } = context.reflectedLight; - - let totalDiffuse = directDiffuse.add(indirectDiffuse); - - if (backdrop !== null) { - if (backdropAlpha !== null) { - totalDiffuse = vec3(backdropAlpha.mix(totalDiffuse, backdrop)); - } else { - totalDiffuse = vec3(backdrop); - } - - context.material.transparent = true; - } - - totalDiffuseNode.assign(totalDiffuse); - totalSpecularNode.assign(directSpecular.add(indirectSpecular)); - - outgoingLightNode.assign(totalDiffuseNode.add(totalSpecularNode)); - - // - - lightingModel.finish(context, stack, builder); - - // - - outgoingLightNode = outgoingLightNode.bypass(builder.removeStack()); - } - - return outgoingLightNode; - } - - _getLightNodeById(id) { - for (const lightNode of this.lightNodes) { - if (lightNode.isAnalyticLightNode && lightNode.light.id === id) { - return lightNode; - } - } - - return null; - } - - fromLights(lights = []) { - const lightNodes = []; - - lights = sortLights(lights); - - for (const light of lights) { - let lightNode = this._getLightNodeById(light.id); - - if (lightNode === null) { - const lightClass = light.constructor; - const lightNodeClass = LightNodes.has(lightClass) ? LightNodes.get(lightClass) : AnalyticLightNode; - - lightNode = nodeObject(new lightNodeClass(light)); - } - - lightNodes.push(lightNode); - } - - this.lightNodes = lightNodes; - this._hash = null; - - return this; - } -} - -export default LightsNode; - -export const lights = lights => nodeObject(new LightsNode().fromLights(lights)); -export const lightsNode = nodeProxy(LightsNode); - -export function addLightNode(lightClass, lightNodeClass) { - if (LightNodes.has(lightClass)) { - console.warn(`Redefinition of light node ${lightNodeClass.type}`); - return; - } - - if (typeof lightClass !== 'function') throw new Error(`Light ${lightClass.name} is not a class`); - if (typeof lightNodeClass !== 'function' || !lightNodeClass.type) - throw new Error(`Light node ${lightNodeClass.type} is not a class`); - - LightNodes.set(lightClass, lightNodeClass); -} diff --git a/examples-jsm/examples/renderers/common/Renderer.ts b/examples-jsm/examples/renderers/common/Renderer.ts deleted file mode 100644 index 85713cfe6..000000000 --- a/examples-jsm/examples/renderers/common/Renderer.ts +++ /dev/null @@ -1,1225 +0,0 @@ -import Animation from './Animation.js'; -import RenderObjects from './RenderObjects.js'; -import Attributes from './Attributes.js'; -import Geometries from './Geometries.js'; -import Info from './Info.js'; -import Pipelines from './Pipelines.js'; -import Bindings from './Bindings.js'; -import RenderLists from './RenderLists.js'; -import RenderContexts from './RenderContexts.js'; -import Textures from './Textures.js'; -import Background from './Background.js'; -import Nodes from './nodes/Nodes.js'; -import Color4 from './Color4.js'; -import ClippingContext from './ClippingContext.js'; -import { - Scene, - Frustum, - Matrix4, - Vector2, - Vector3, - Vector4, - DoubleSide, - BackSide, - FrontSide, - SRGBColorSpace, - NoColorSpace, - NoToneMapping, - LinearFilter, - LinearSRGBColorSpace, - RenderTarget, - HalfFloatType, - RGBAFormat, -} from 'three'; -import { NodeMaterial } from '../../nodes/Nodes.js'; -import QuadMesh from '../../objects/QuadMesh.js'; - -const _scene = new Scene(); -const _drawingBufferSize = new Vector2(); -const _screen = new Vector4(); -const _frustum = new Frustum(); -const _projScreenMatrix = new Matrix4(); -const _vector3 = new Vector3(); -const _quad = new QuadMesh(new NodeMaterial()); - -class Renderer { - constructor(backend, parameters = {}) { - this.isRenderer = true; - - // - - const { logarithmicDepthBuffer = false, alpha = true } = parameters; - - // public - - this.domElement = backend.getDomElement(); - - this.backend = backend; - - this.autoClear = true; - this.autoClearColor = true; - this.autoClearDepth = true; - this.autoClearStencil = true; - - this.alpha = alpha; - - this.logarithmicDepthBuffer = logarithmicDepthBuffer; - - this.outputColorSpace = SRGBColorSpace; - - this.toneMapping = NoToneMapping; - this.toneMappingExposure = 1.0; - - this.sortObjects = true; - - this.depth = true; - this.stencil = false; - - this.clippingPlanes = []; - - this.info = new Info(); - - // nodes - - this.toneMappingNode = null; - - // internals - - this._pixelRatio = 1; - this._width = this.domElement.width; - this._height = this.domElement.height; - - this._viewport = new Vector4(0, 0, this._width, this._height); - this._scissor = new Vector4(0, 0, this._width, this._height); - this._scissorTest = false; - - this._attributes = null; - this._geometries = null; - this._nodes = null; - this._animation = null; - this._bindings = null; - this._objects = null; - this._pipelines = null; - this._renderLists = null; - this._renderContexts = null; - this._textures = null; - this._background = null; - - this._currentRenderContext = null; - - this._opaqueSort = null; - this._transparentSort = null; - - this._frameBufferTarget = null; - - const alphaClear = this.alpha === true ? 0 : 1; - - this._clearColor = new Color4(0, 0, 0, alphaClear); - this._clearDepth = 1; - this._clearStencil = 0; - - this._renderTarget = null; - this._activeCubeFace = 0; - this._activeMipmapLevel = 0; - - this._renderObjectFunction = null; - this._currentRenderObjectFunction = null; - - this._handleObjectFunction = this._renderObjectDirect; - - this._initialized = false; - this._initPromise = null; - - this._compilationPromises = null; - - // backwards compatibility - - this.shadowMap = { - enabled: false, - type: null, - }; - - this.xr = { - enabled: false, - }; - } - - async init() { - if (this._initialized) { - throw new Error('Renderer: Backend has already been initialized.'); - } - - if (this._initPromise !== null) { - return this._initPromise; - } - - this._initPromise = new Promise(async (resolve, reject) => { - const backend = this.backend; - - try { - await backend.init(this); - } catch (error) { - reject(error); - return; - } - - this._nodes = new Nodes(this, backend); - this._animation = new Animation(this._nodes, this.info); - this._attributes = new Attributes(backend); - this._background = new Background(this, this._nodes); - this._geometries = new Geometries(this._attributes, this.info); - this._textures = new Textures(this, backend, this.info); - this._pipelines = new Pipelines(backend, this._nodes); - this._bindings = new Bindings( - backend, - this._nodes, - this._textures, - this._attributes, - this._pipelines, - this.info, - ); - this._objects = new RenderObjects( - this, - this._nodes, - this._geometries, - this._pipelines, - this._bindings, - this.info, - ); - this._renderLists = new RenderLists(); - this._renderContexts = new RenderContexts(); - - // - - this._initialized = true; - - resolve(); - }); - - return this._initPromise; - } - - get coordinateSystem() { - return this.backend.coordinateSystem; - } - - async compileAsync(scene, camera, targetScene = null) { - if (this._initialized === false) await this.init(); - - // preserve render tree - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - const previousRenderContext = this._currentRenderContext; - const previousRenderObjectFunction = this._currentRenderObjectFunction; - const previousCompilationPromises = this._compilationPromises; - - // - - const sceneRef = scene.isScene === true ? scene : _scene; - - if (targetScene === null) targetScene = scene; - - const renderTarget = this._renderTarget; - const renderContext = this._renderContexts.get(targetScene, camera, renderTarget); - const activeMipmapLevel = this._activeMipmapLevel; - - const compilationPromises = []; - - this._currentRenderContext = renderContext; - this._currentRenderObjectFunction = this.renderObject; - - this._handleObjectFunction = this._createObjectPipeline; - - this._compilationPromises = compilationPromises; - - nodeFrame.renderId++; - - // - - nodeFrame.update(); - - // - - renderContext.depth = this.depth; - renderContext.stencil = this.stencil; - - if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); - renderContext.clippingContext.updateGlobal(this, camera); - - // - - sceneRef.onBeforeRender(this, scene, camera, renderTarget); - - // - - const renderList = this._renderLists.get(scene, camera); - renderList.begin(); - - this._projectObject(scene, camera, 0, renderList); - - // include lights from target scene - if (targetScene !== scene) { - targetScene.traverseVisible(function (object) { - if (object.isLight && object.layers.test(camera.layers)) { - renderList.pushLight(object); - } - }); - } - - renderList.finish(); - - // - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); - - const renderTargetData = this._textures.get(renderTarget); - - renderContext.textures = renderTargetData.textures; - renderContext.depthTexture = renderTargetData.depthTexture; - } else { - renderContext.textures = null; - renderContext.depthTexture = null; - } - - // - - this._nodes.updateScene(sceneRef); - - // - - this._background.update(sceneRef, renderList, renderContext); - - // process render lists - - const opaqueObjects = renderList.opaque; - const transparentObjects = renderList.transparent; - const lightsNode = renderList.lightsNode; - - if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); - if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); - - // restore render tree - - nodeFrame.renderId = previousRenderId; - - this._currentRenderContext = previousRenderContext; - this._currentRenderObjectFunction = previousRenderObjectFunction; - this._compilationPromises = previousCompilationPromises; - - this._handleObjectFunction = this._renderObjectDirect; - - // wait for all promises setup by backends awaiting compilation/linking/pipeline creation to complete - - await Promise.all(compilationPromises); - } - - async renderAsync(scene, camera) { - if (this._initialized === false) await this.init(); - - const renderContext = this._renderScene(scene, camera); - - await this.backend.resolveTimestampAsync(renderContext, 'render'); - } - - render(scene, camera) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .render() called before the backend is initialized. Try using .renderAsync() instead.', - ); - - return this.renderAsync(scene, camera); - } - - this._renderScene(scene, camera); - } - - _getFrameBufferTarget() { - const { currentColorSpace } = this; - - const useToneMapping = - this._renderTarget === null && (this.toneMapping !== NoToneMapping || this.toneMappingNode !== null); - const useColorSpace = currentColorSpace !== LinearSRGBColorSpace && currentColorSpace !== NoColorSpace; - - if (useToneMapping === false && useColorSpace === false) return null; - - const { width, height } = this.getDrawingBufferSize(_drawingBufferSize); - const { depth, stencil } = this; - - let frameBufferTarget = this._frameBufferTarget; - - if (frameBufferTarget === null) { - frameBufferTarget = new RenderTarget(width, height, { - depthBuffer: depth, - stencilBuffer: stencil, - type: HalfFloatType, // FloatType - format: RGBAFormat, - colorSpace: LinearSRGBColorSpace, - generateMipmaps: false, - minFilter: LinearFilter, - magFilter: LinearFilter, - samples: this.backend.parameters.antialias ? 4 : 0, - }); - - frameBufferTarget.isPostProcessingRenderTarget = true; - - this._frameBufferTarget = frameBufferTarget; - } - - frameBufferTarget.depthBuffer = depth; - frameBufferTarget.stencilBuffer = stencil; - frameBufferTarget.setSize(width, height); - frameBufferTarget.viewport.copy(this._viewport); - frameBufferTarget.scissor.copy(this._scissor); - frameBufferTarget.viewport.multiplyScalar(this._pixelRatio); - frameBufferTarget.scissor.multiplyScalar(this._pixelRatio); - frameBufferTarget.scissorTest = this._scissorTest; - - return frameBufferTarget; - } - - _renderScene(scene, camera, useFrameBufferTarget = true) { - const frameBufferTarget = useFrameBufferTarget ? this._getFrameBufferTarget() : null; - - // preserve render tree - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - const previousRenderContext = this._currentRenderContext; - const previousRenderObjectFunction = this._currentRenderObjectFunction; - - // - - const sceneRef = scene.isScene === true ? scene : _scene; - - const outputRenderTarget = this._renderTarget; - - const activeCubeFace = this._activeCubeFace; - const activeMipmapLevel = this._activeMipmapLevel; - - // - - let renderTarget; - - if (frameBufferTarget !== null) { - renderTarget = frameBufferTarget; - - this.setRenderTarget(renderTarget); - } else { - renderTarget = outputRenderTarget; - } - - // - - const renderContext = this._renderContexts.get(scene, camera, renderTarget); - - this._currentRenderContext = renderContext; - this._currentRenderObjectFunction = this._renderObjectFunction || this.renderObject; - - // - - this.info.calls++; - this.info.render.calls++; - - nodeFrame.renderId = this.info.calls; - - // - - const coordinateSystem = this.coordinateSystem; - - if (camera.coordinateSystem !== coordinateSystem) { - camera.coordinateSystem = coordinateSystem; - - camera.updateProjectionMatrix(); - } - - // - - if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld(); - - if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld(); - - // - - let viewport = this._viewport; - let scissor = this._scissor; - let pixelRatio = this._pixelRatio; - - if (renderTarget !== null) { - viewport = renderTarget.viewport; - scissor = renderTarget.scissor; - pixelRatio = 1; - } - - this.getDrawingBufferSize(_drawingBufferSize); - - _screen.set(0, 0, _drawingBufferSize.width, _drawingBufferSize.height); - - const minDepth = viewport.minDepth === undefined ? 0 : viewport.minDepth; - const maxDepth = viewport.maxDepth === undefined ? 1 : viewport.maxDepth; - - renderContext.viewportValue.copy(viewport).multiplyScalar(pixelRatio).floor(); - renderContext.viewportValue.width >>= activeMipmapLevel; - renderContext.viewportValue.height >>= activeMipmapLevel; - renderContext.viewportValue.minDepth = minDepth; - renderContext.viewportValue.maxDepth = maxDepth; - renderContext.viewport = renderContext.viewportValue.equals(_screen) === false; - - renderContext.scissorValue.copy(scissor).multiplyScalar(pixelRatio).floor(); - renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals(_screen) === false; - renderContext.scissorValue.width >>= activeMipmapLevel; - renderContext.scissorValue.height >>= activeMipmapLevel; - - if (!renderContext.clippingContext) renderContext.clippingContext = new ClippingContext(); - renderContext.clippingContext.updateGlobal(this, camera); - - // - - sceneRef.onBeforeRender(this, scene, camera, renderTarget); - - // - - _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); - _frustum.setFromProjectionMatrix(_projScreenMatrix, coordinateSystem); - - const renderList = this._renderLists.get(scene, camera); - renderList.begin(); - - this._projectObject(scene, camera, 0, renderList); - - renderList.finish(); - - if (this.sortObjects === true) { - renderList.sort(this._opaqueSort, this._transparentSort); - } - - // - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget, activeMipmapLevel); - - const renderTargetData = this._textures.get(renderTarget); - - renderContext.textures = renderTargetData.textures; - renderContext.depthTexture = renderTargetData.depthTexture; - renderContext.width = renderTargetData.width; - renderContext.height = renderTargetData.height; - renderContext.renderTarget = renderTarget; - renderContext.depth = renderTarget.depthBuffer; - renderContext.stencil = renderTarget.stencilBuffer; - } else { - renderContext.textures = null; - renderContext.depthTexture = null; - renderContext.width = this.domElement.width; - renderContext.height = this.domElement.height; - renderContext.depth = this.depth; - renderContext.stencil = this.stencil; - } - - renderContext.width >>= activeMipmapLevel; - renderContext.height >>= activeMipmapLevel; - renderContext.activeCubeFace = activeCubeFace; - renderContext.activeMipmapLevel = activeMipmapLevel; - renderContext.occlusionQueryCount = renderList.occlusionQueryCount; - - // - - this._nodes.updateScene(sceneRef); - - // - - this._background.update(sceneRef, renderList, renderContext); - - // - - this.backend.beginRender(renderContext); - - // process render lists - - const opaqueObjects = renderList.opaque; - const transparentObjects = renderList.transparent; - const lightsNode = renderList.lightsNode; - - if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, sceneRef, lightsNode); - if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, sceneRef, lightsNode); - - // finish render pass - - this.backend.finishRender(renderContext); - - // restore render tree - - nodeFrame.renderId = previousRenderId; - - this._currentRenderContext = previousRenderContext; - this._currentRenderObjectFunction = previousRenderObjectFunction; - - // - - if (frameBufferTarget !== null) { - this.setRenderTarget(outputRenderTarget, activeCubeFace, activeMipmapLevel); - - _quad.material.fragmentNode = this._nodes.getOutputNode(renderTarget.texture); - - this._renderScene(_quad, _quad.camera, false); - } - - // - - sceneRef.onAfterRender(this, scene, camera, renderTarget); - - // - - return renderContext; - } - - getMaxAnisotropy() { - return this.backend.getMaxAnisotropy(); - } - - getActiveCubeFace() { - return this._activeCubeFace; - } - - getActiveMipmapLevel() { - return this._activeMipmapLevel; - } - - async setAnimationLoop(callback) { - if (this._initialized === false) await this.init(); - - this._animation.setAnimationLoop(callback); - } - - getArrayBuffer(attribute) { - // @deprecated, r155 - - console.warn('THREE.Renderer: getArrayBuffer() is deprecated. Use getArrayBufferAsync() instead.'); - - return this.getArrayBufferAsync(attribute); - } - - async getArrayBufferAsync(attribute) { - return await this.backend.getArrayBufferAsync(attribute); - } - - getContext() { - return this.backend.getContext(); - } - - getPixelRatio() { - return this._pixelRatio; - } - - getDrawingBufferSize(target) { - return target.set(this._width * this._pixelRatio, this._height * this._pixelRatio).floor(); - } - - getSize(target) { - return target.set(this._width, this._height); - } - - setPixelRatio(value = 1) { - this._pixelRatio = value; - - this.setSize(this._width, this._height, false); - } - - setDrawingBufferSize(width, height, pixelRatio) { - this._width = width; - this._height = height; - - this._pixelRatio = pixelRatio; - - this.domElement.width = Math.floor(width * pixelRatio); - this.domElement.height = Math.floor(height * pixelRatio); - - this.setViewport(0, 0, width, height); - - if (this._initialized) this.backend.updateSize(); - } - - setSize(width, height, updateStyle = true) { - this._width = width; - this._height = height; - - this.domElement.width = Math.floor(width * this._pixelRatio); - this.domElement.height = Math.floor(height * this._pixelRatio); - - if (updateStyle === true) { - this.domElement.style.width = width + 'px'; - this.domElement.style.height = height + 'px'; - } - - this.setViewport(0, 0, width, height); - - if (this._initialized) this.backend.updateSize(); - } - - setOpaqueSort(method) { - this._opaqueSort = method; - } - - setTransparentSort(method) { - this._transparentSort = method; - } - - getScissor(target) { - const scissor = this._scissor; - - target.x = scissor.x; - target.y = scissor.y; - target.width = scissor.width; - target.height = scissor.height; - - return target; - } - - setScissor(x, y, width, height) { - const scissor = this._scissor; - - if (x.isVector4) { - scissor.copy(x); - } else { - scissor.set(x, y, width, height); - } - } - - getScissorTest() { - return this._scissorTest; - } - - setScissorTest(boolean) { - this._scissorTest = boolean; - - this.backend.setScissorTest(boolean); - } - - getViewport(target) { - return target.copy(this._viewport); - } - - setViewport(x, y, width, height, minDepth = 0, maxDepth = 1) { - const viewport = this._viewport; - - if (x.isVector4) { - viewport.copy(x); - } else { - viewport.set(x, y, width, height); - } - - viewport.minDepth = minDepth; - viewport.maxDepth = maxDepth; - } - - getClearColor(target) { - return target.copy(this._clearColor); - } - - setClearColor(color, alpha = 1) { - this._clearColor.set(color); - this._clearColor.a = alpha; - } - - getClearAlpha() { - return this._clearColor.a; - } - - setClearAlpha(alpha) { - this._clearColor.a = alpha; - } - - getClearDepth() { - return this._clearDepth; - } - - setClearDepth(depth) { - this._clearDepth = depth; - } - - getClearStencil() { - return this._clearStencil; - } - - setClearStencil(stencil) { - this._clearStencil = stencil; - } - - isOccluded(object) { - const renderContext = this._currentRenderContext; - - return renderContext && this.backend.isOccluded(renderContext, object); - } - - clear(color = true, depth = true, stencil = true) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .clear() called before the backend is initialized. Try using .clearAsync() instead.', - ); - - return this.clearAsync(color, depth, stencil); - } - - const renderTarget = this._renderTarget || this._getFrameBufferTarget(); - - let renderTargetData = null; - - if (renderTarget !== null) { - this._textures.updateRenderTarget(renderTarget); - - renderTargetData = this._textures.get(renderTarget); - } - - this.backend.clear(color, depth, stencil, renderTargetData); - } - - clearColor() { - return this.clear(true, false, false); - } - - clearDepth() { - return this.clear(false, true, false); - } - - clearStencil() { - return this.clear(false, false, true); - } - - async clearAsync(color = true, depth = true, stencil = true) { - if (this._initialized === false) await this.init(); - - this.clear(color, depth, stencil); - } - - clearColorAsync() { - return this.clearAsync(true, false, false); - } - - clearDepthAsync() { - return this.clearAsync(false, true, false); - } - - clearStencilAsync() { - return this.clearAsync(false, false, true); - } - - get currentColorSpace() { - const renderTarget = this._renderTarget; - - if (renderTarget !== null) { - const texture = renderTarget.texture; - - return (Array.isArray(texture) ? texture[0] : texture).colorSpace; - } - - return this.outputColorSpace; - } - - dispose() { - this.info.dispose(); - - this._animation.dispose(); - this._objects.dispose(); - this._pipelines.dispose(); - this._nodes.dispose(); - this._bindings.dispose(); - this._renderLists.dispose(); - this._renderContexts.dispose(); - this._textures.dispose(); - - this.setRenderTarget(null); - this.setAnimationLoop(null); - } - - setRenderTarget(renderTarget, activeCubeFace = 0, activeMipmapLevel = 0) { - this._renderTarget = renderTarget; - this._activeCubeFace = activeCubeFace; - this._activeMipmapLevel = activeMipmapLevel; - } - - getRenderTarget() { - return this._renderTarget; - } - - setRenderObjectFunction(renderObjectFunction) { - this._renderObjectFunction = renderObjectFunction; - } - - getRenderObjectFunction() { - return this._renderObjectFunction; - } - - async computeAsync(computeNodes) { - if (this._initialized === false) await this.init(); - - const nodeFrame = this._nodes.nodeFrame; - - const previousRenderId = nodeFrame.renderId; - - // - - this.info.calls++; - this.info.compute.calls++; - this.info.compute.computeCalls++; - - nodeFrame.renderId = this.info.calls; - - // - - const backend = this.backend; - const pipelines = this._pipelines; - const bindings = this._bindings; - const nodes = this._nodes; - const computeList = Array.isArray(computeNodes) ? computeNodes : [computeNodes]; - - if (computeList[0] === undefined || computeList[0].isComputeNode !== true) { - throw new Error('THREE.Renderer: .compute() expects a ComputeNode.'); - } - - backend.beginCompute(computeNodes); - - for (const computeNode of computeList) { - // onInit - - if (pipelines.has(computeNode) === false) { - const dispose = () => { - computeNode.removeEventListener('dispose', dispose); - - pipelines.delete(computeNode); - bindings.delete(computeNode); - nodes.delete(computeNode); - }; - - computeNode.addEventListener('dispose', dispose); - - // - - computeNode.onInit({ renderer: this }); - } - - nodes.updateForCompute(computeNode); - bindings.updateForCompute(computeNode); - - const computeBindings = bindings.getForCompute(computeNode); - const computePipeline = pipelines.getForCompute(computeNode, computeBindings); - - backend.compute(computeNodes, computeNode, computeBindings, computePipeline); - } - - backend.finishCompute(computeNodes); - - await this.backend.resolveTimestampAsync(computeNodes, 'compute'); - - // - - nodeFrame.renderId = previousRenderId; - } - - async hasFeatureAsync(name) { - if (this._initialized === false) await this.init(); - - return this.backend.hasFeature(name); - } - - hasFeature(name) { - if (this._initialized === false) { - console.warn( - 'THREE.Renderer: .hasFeature() called before the backend is initialized. Try using .hasFeatureAsync() instead.', - ); - - return false; - } - - return this.backend.hasFeature(name); - } - - copyFramebufferToTexture(framebufferTexture) { - const renderContext = this._currentRenderContext; - - this._textures.updateTexture(framebufferTexture); - - this.backend.copyFramebufferToTexture(framebufferTexture, renderContext); - } - - copyTextureToTexture(srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0) { - this._textures.updateTexture(srcTexture); - this._textures.updateTexture(dstTexture); - - this.backend.copyTextureToTexture(srcTexture, dstTexture, srcRegion, dstPosition, level); - } - - readRenderTargetPixelsAsync(renderTarget, x, y, width, height, index = 0) { - return this.backend.copyTextureToBuffer(renderTarget.textures[index], x, y, width, height); - } - - _projectObject(object, camera, groupOrder, renderList) { - if (object.visible === false) return; - - const visible = object.layers.test(camera.layers); - - if (visible) { - if (object.isGroup) { - groupOrder = object.renderOrder; - } else if (object.isLOD) { - if (object.autoUpdate === true) object.update(camera); - } else if (object.isLight) { - renderList.pushLight(object); - } else if (object.isSprite) { - if (!object.frustumCulled || _frustum.intersectsSprite(object)) { - if (this.sortObjects === true) { - _vector3.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix); - } - - const geometry = object.geometry; - const material = object.material; - - if (material.visible) { - renderList.push(object, geometry, material, groupOrder, _vector3.z, null); - } - } - } else if (object.isLineLoop) { - console.error( - 'THREE.Renderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.', - ); - } else if (object.isMesh || object.isLine || object.isPoints) { - if (!object.frustumCulled || _frustum.intersectsObject(object)) { - const geometry = object.geometry; - const material = object.material; - - if (this.sortObjects === true) { - if (geometry.boundingSphere === null) geometry.computeBoundingSphere(); - - _vector3 - .copy(geometry.boundingSphere.center) - .applyMatrix4(object.matrixWorld) - .applyMatrix4(_projScreenMatrix); - } - - if (Array.isArray(material)) { - const groups = geometry.groups; - - for (let i = 0, l = groups.length; i < l; i++) { - const group = groups[i]; - const groupMaterial = material[group.materialIndex]; - - if (groupMaterial && groupMaterial.visible) { - renderList.push(object, geometry, groupMaterial, groupOrder, _vector3.z, group); - } - } - } else if (material.visible) { - renderList.push(object, geometry, material, groupOrder, _vector3.z, null); - } - } - } - } - - const children = object.children; - - for (let i = 0, l = children.length; i < l; i++) { - this._projectObject(children[i], camera, groupOrder, renderList); - } - } - - _renderObjects(renderList, camera, scene, lightsNode) { - // process renderable objects - - for (let i = 0, il = renderList.length; i < il; i++) { - const renderItem = renderList[i]; - - // @TODO: Add support for multiple materials per object. This will require to extract - // the material from the renderItem object and pass it with its group data to renderObject(). - - const { object, geometry, material, group } = renderItem; - - if (camera.isArrayCamera) { - const cameras = camera.cameras; - - for (let j = 0, jl = cameras.length; j < jl; j++) { - const camera2 = cameras[j]; - - if (object.layers.test(camera2.layers)) { - const vp = camera2.viewport; - const minDepth = vp.minDepth === undefined ? 0 : vp.minDepth; - const maxDepth = vp.maxDepth === undefined ? 1 : vp.maxDepth; - - const viewportValue = this._currentRenderContext.viewportValue; - viewportValue.copy(vp).multiplyScalar(this._pixelRatio).floor(); - viewportValue.minDepth = minDepth; - viewportValue.maxDepth = maxDepth; - - this.backend.updateViewport(this._currentRenderContext); - - this._currentRenderObjectFunction( - object, - scene, - camera2, - geometry, - material, - group, - lightsNode, - ); - } - } - } else { - this._currentRenderObjectFunction(object, scene, camera, geometry, material, group, lightsNode); - } - } - } - - renderObject(object, scene, camera, geometry, material, group, lightsNode) { - let overridePositionNode; - let overrideFragmentNode; - let overrideDepthNode; - - // - - object.onBeforeRender(this, scene, camera, geometry, material, group); - - material.onBeforeRender(this, scene, camera, geometry, material, group); - - // - - if (scene.overrideMaterial !== null) { - const overrideMaterial = scene.overrideMaterial; - - if (material.positionNode && material.positionNode.isNode) { - overridePositionNode = overrideMaterial.positionNode; - overrideMaterial.positionNode = material.positionNode; - } - - if (overrideMaterial.isShadowNodeMaterial) { - overrideMaterial.side = material.shadowSide === null ? material.side : material.shadowSide; - - if (material.depthNode && material.depthNode.isNode) { - overrideDepthNode = overrideMaterial.depthNode; - overrideMaterial.depthNode = material.depthNode; - } - - if (material.shadowNode && material.shadowNode.isNode) { - overrideFragmentNode = overrideMaterial.fragmentNode; - overrideMaterial.fragmentNode = material.shadowNode; - } - - if (this.localClippingEnabled) { - if (material.clipShadows) { - if (overrideMaterial.clippingPlanes !== material.clippingPlanes) { - overrideMaterial.clippingPlanes = material.clippingPlanes; - overrideMaterial.needsUpdate = true; - } - - if (overrideMaterial.clipIntersection !== material.clipIntersection) { - overrideMaterial.clipIntersection = material.clipIntersection; - } - } else if (Array.isArray(overrideMaterial.clippingPlanes)) { - overrideMaterial.clippingPlanes = null; - overrideMaterial.needsUpdate = true; - } - } - } - - material = overrideMaterial; - } - - // - - if (material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false) { - material.side = BackSide; - this._handleObjectFunction(object, material, scene, camera, lightsNode, group, 'backSide'); // create backSide pass id - - material.side = FrontSide; - this._handleObjectFunction(object, material, scene, camera, lightsNode, group); // use default pass id - - material.side = DoubleSide; - } else { - this._handleObjectFunction(object, material, scene, camera, lightsNode, group); - } - - // - - if (overridePositionNode !== undefined) { - scene.overrideMaterial.positionNode = overridePositionNode; - } - - if (overrideDepthNode !== undefined) { - scene.overrideMaterial.depthNode = overrideDepthNode; - } - - if (overrideFragmentNode !== undefined) { - scene.overrideMaterial.fragmentNode = overrideFragmentNode; - } - - // - - object.onAfterRender(this, scene, camera, geometry, material, group); - } - - _renderObjectDirect(object, material, scene, camera, lightsNode, group, passId) { - const renderObject = this._objects.get( - object, - material, - scene, - camera, - lightsNode, - this._currentRenderContext, - passId, - ); - renderObject.drawRange = group || object.geometry.drawRange; - - // - - this._nodes.updateBefore(renderObject); - - // - - object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld); - object.normalMatrix.getNormalMatrix(object.modelViewMatrix); - - // - - this._nodes.updateForRender(renderObject); - this._geometries.updateForRender(renderObject); - this._bindings.updateForRender(renderObject); - this._pipelines.updateForRender(renderObject); - - // - - this.backend.draw(renderObject, this.info); - } - - _createObjectPipeline(object, material, scene, camera, lightsNode, passId) { - const renderObject = this._objects.get( - object, - material, - scene, - camera, - lightsNode, - this._currentRenderContext, - passId, - ); - - // - - this._nodes.updateBefore(renderObject); - - // - - this._nodes.updateForRender(renderObject); - this._geometries.updateForRender(renderObject); - this._bindings.updateForRender(renderObject); - - this._pipelines.getForRender(renderObject, this._compilationPromises); - } - - get compute() { - return this.computeAsync; - } - - get compile() { - return this.compileAsync; - } -} - -export default Renderer; From a858beb1c772fb3ef4ddc71b2fbf90cde417c567 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 22:00:25 -0400 Subject: [PATCH 17/18] Update --- .dprint.jsonc | 1 + 1 file changed, 1 insertion(+) diff --git a/.dprint.jsonc b/.dprint.jsonc index c295cabf1..0d8c1c1f1 100644 --- a/.dprint.jsonc +++ b/.dprint.jsonc @@ -1,5 +1,6 @@ { "excludes": [ + "./examples-jsm", "./examples-testing", "./three.js", "pnpm-lock.yaml" From fcbd86e6bb593da3aa1347e9b6e266bf965abd09 Mon Sep 17 00:00:00 2001 From: Nathan Bierema Date: Thu, 23 May 2024 22:01:11 -0400 Subject: [PATCH 18/18] Add CI --- .github/workflows/CI.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index dff1381fe..198101694 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -67,3 +67,23 @@ jobs: working-directory: examples-testing - run: pnpm run format-check working-directory: examples-testing + test-examples-jsm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + cache: 'pnpm' + - run: pnpm install + - run: pnpm run create-examples + working-directory: examples-jsm + - run: git apply changes.patch + working-directory: examples-jsm + - run: pnpm run format-check + working-directory: examples-jsm