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" 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 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/changes.patch b/examples-jsm/changes.patch new file mode 100644 index 000000000..c569cac62 --- /dev/null +++ b/examples-jsm/changes.patch @@ -0,0 +1,520 @@ +diff --git a/examples-jsm/examples/nodes/core/Node.ts b/examples-jsm/examples/nodes/core/Node.ts +index 438c44dd..dd88fd34 100644 +--- a/examples-jsm/examples/nodes/core/Node.ts ++++ b/examples-jsm/examples/nodes/core/Node.ts +@@ -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(); + + 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 +48,7 @@ class Node extends EventDispatcher { + Object.defineProperty(this, 'id', { value: _nodeId++ }); + } + +- set needsUpdate(value) { ++ set needsUpdate(value: boolean) { + if (value === true) { + this.version++; + } +@@ -73,7 +93,7 @@ class Node extends EventDispatcher { + return this; + } + +- isGlobal(/*builder*/) { ++ isGlobal(builder: NodeBuilder) { + return false; + } + +@@ -106,7 +126,7 @@ class Node extends EventDispatcher { + return this._cacheKey; + } + +- getHash(/*builder*/) { ++ getHash(builder: NodeBuilder) { + return this.uuid; + } + +@@ -118,14 +138,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 +155,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 +173,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 +181,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 +204,7 @@ class Node extends EventDispatcher { + } + } + +- generate(builder, output) { ++ generate(builder: NodeBuilder, output?: string | null) { + 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..99c48afa 100644 +--- a/examples-jsm/examples/nodes/core/NodeBuilder.ts ++++ b/examples-jsm/examples/nodes/core/NodeBuilder.ts +@@ -30,6 +30,11 @@ import { + IntType, + UnsignedIntType, + Float16BufferAttribute, ++ Object3D, ++ Material, ++ Mesh, ++ BufferGeometry, ++ Scene, + } from 'three'; + + import { stack } from './StackNode.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 +78,68 @@ const toFloat = value => { + }; + + class NodeBuilder { +- constructor(object, renderer, parser, scene = null, material = null) { ++ object: Object3D; ++ material: Material | Material[]; ++ geometry: BufferGeometry; ++ renderer: Renderer; ++ parser: NodeParser; ++ scene: Scene | null; ++ ++ nodes: Node[]; ++ updateNodes: Node[]; ++ 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 }; ++ chaining: Node[]; ++ // TODO ++ // stack ++ // stacks ++ tab: string; ++ ++ // TODO ++ // currentFunctionNode ++ ++ context: { keywords: NodeKeywords; material: Material | Material[] }; ++ ++ cache: NodeCache; ++ globalCache: NodeCache; ++ ++ 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; ++ 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; +@@ -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 ++++ 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']; +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/index.js b/examples-jsm/index.js new file mode 100644 index 000000000..6c7cabcb9 --- /dev/null +++ b/examples-jsm/index.js @@ -0,0 +1,36 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; + +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', + 'nodes/lighting/EnvironmentNode', + 'nodes/lighting/LightsNode', + 'renderers/common/Renderer', +]; + +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'