@typegpu/three 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Software Mansion <swmansion.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ <div align="center">
2
+
3
+ # @typegpu/three
4
+
5
+ 🚧 **Under Construction** 🚧
6
+
7
+ </div>
8
+
9
+ A helper library for using TypeGPU with Three.js.
10
+
11
+ ```ts
12
+ import * as TSL from 'three/tsl';
13
+ import * as t3 from '@typegpu/three';
14
+ import { fract } from 'typegpu/std';
15
+
16
+ const material1 = new THREE.MeshBasicNodeMaterial();
17
+ const pattern = TSL.texture(detailMap, TSL.uv().mul(10));
18
+ // `fromTSL` can be used to access any TSL node from a TypeGPU function
19
+ const patternAccess = t3.fromTSL(pattern, d.vec4f);
20
+ material1.colorNode = t3.toTSL(() => {
21
+ 'use gpu';
22
+ return patternAccess.$;
23
+ });
24
+
25
+ const material2 = new THREE.MeshBasicNodeMaterial();
26
+ material2.colorNode = t3.toTSL(() => {
27
+ 'use gpu';
28
+ // Many builtin TSL nodes are already reexported as `accessors`
29
+ const uv = t3.uv().$;
30
+
31
+ if (uv.x < 0.5) {
32
+ return d.vec4f(fract(uv.mul(4)), 0, 1);
33
+ }
34
+
35
+ return d.vec4f(1, 0, 0, 1);
36
+ });
37
+ ```
package/index.cjs ADDED
@@ -0,0 +1,283 @@
1
+ 'use strict';
2
+
3
+ const TSL = require('three/tsl');
4
+ const tgpu = require('typegpu');
5
+ const d = require('typegpu/data');
6
+ const THREE = require('three/webgpu');
7
+ const WGSLNodeBuilder = require('three/src/renderers/webgpu/nodes/WGSLNodeBuilder.js');
8
+
9
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
10
+
11
+ function _interopNamespaceCompat(e) {
12
+ if (e && typeof e === 'object' && 'default' in e) return e;
13
+ const n = Object.create(null);
14
+ if (e) {
15
+ for (const k in e) {
16
+ n[k] = e[k];
17
+ }
18
+ }
19
+ n.default = e;
20
+ return n;
21
+ }
22
+
23
+ const TSL__namespace = /*#__PURE__*/_interopNamespaceCompat(TSL);
24
+ const tgpu__default = /*#__PURE__*/_interopDefaultCompat(tgpu);
25
+ const d__namespace = /*#__PURE__*/_interopNamespaceCompat(d);
26
+ const THREE__namespace = /*#__PURE__*/_interopNamespaceCompat(THREE);
27
+ const WGSLNodeBuilder__default = /*#__PURE__*/_interopDefaultCompat(WGSLNodeBuilder);
28
+
29
+ class StageData {
30
+ stage;
31
+ names;
32
+ namespace;
33
+ codeGeneratedThusFar;
34
+ constructor(stage) {
35
+ this.stage = stage;
36
+ this.names = /* @__PURE__ */ new WeakMap();
37
+ this.namespace = tgpu__default["~unstable"].namespace();
38
+ this.codeGeneratedThusFar = "";
39
+ this.namespace.on("name", (event) => {
40
+ if (tgpu.isVariable(event.target)) {
41
+ this.names.set(event.target, event.name);
42
+ }
43
+ });
44
+ }
45
+ }
46
+ class BuilderData {
47
+ stageDataMap;
48
+ constructor() {
49
+ this.stageDataMap = /* @__PURE__ */ new Map();
50
+ }
51
+ getStageData(stage) {
52
+ let stageData = this.stageDataMap.get(stage);
53
+ if (!stageData) {
54
+ stageData = new StageData(stage);
55
+ this.stageDataMap.set(stage, stageData);
56
+ }
57
+ return stageData;
58
+ }
59
+ }
60
+ const builderDataMap = /* @__PURE__ */ new WeakMap();
61
+ let currentlyGeneratingFnNodeCtx;
62
+ function forceExplicitVoidReturn(codeIn) {
63
+ if (codeIn.includes("->")) {
64
+ return codeIn;
65
+ }
66
+ const closingParen = codeIn.indexOf(")");
67
+ if (closingParen === -1) {
68
+ throw new Error("Invalid code: missing closing parenthesis");
69
+ }
70
+ return codeIn.substring(0, closingParen + 1) + "-> void" + codeIn.substring(closingParen + 1);
71
+ }
72
+ class TgpuFnNode extends THREE__namespace.Node {
73
+ #impl;
74
+ constructor(impl) {
75
+ super("typegpu-fn-node");
76
+ this.#impl = impl;
77
+ this.global = true;
78
+ }
79
+ static get type() {
80
+ return "TgpuFnNode";
81
+ }
82
+ getNodeType(builder) {
83
+ return this.#getNodeFunction(builder).type;
84
+ }
85
+ #getNodeFunction(builder) {
86
+ const nodeData = builder.getDataFromNode(this);
87
+ let builderData = builderDataMap.get(builder);
88
+ if (!builderData) {
89
+ builderData = new BuilderData();
90
+ builderDataMap.set(builder, builderData);
91
+ }
92
+ const stageData = builderData.getStageData(builder.shaderStage);
93
+ if (!nodeData.custom) {
94
+ if (currentlyGeneratingFnNodeCtx !== void 0) {
95
+ console.warn("[@typegpu/three] Nested function generation detected");
96
+ }
97
+ const ctx = {
98
+ builder,
99
+ stageData,
100
+ dependencies: []
101
+ };
102
+ currentlyGeneratingFnNodeCtx = ctx;
103
+ let resolved;
104
+ try {
105
+ resolved = tgpu__default.resolve({
106
+ names: stageData.namespace,
107
+ template: "___ID___ fnName",
108
+ externals: { fnName: this.#impl }
109
+ });
110
+ } finally {
111
+ currentlyGeneratingFnNodeCtx = void 0;
112
+ }
113
+ const [code = "", functionId] = resolved.split("___ID___").map(
114
+ (s) => s.trim()
115
+ );
116
+ stageData.codeGeneratedThusFar += code;
117
+ let lastFnStart = stageData.codeGeneratedThusFar.indexOf(
118
+ `
119
+ fn ${functionId}`
120
+ );
121
+ if (lastFnStart === -1) {
122
+ lastFnStart = 0;
123
+ }
124
+ const fnCode = stageData.codeGeneratedThusFar.slice(lastFnStart).trim();
125
+ nodeData.custom = {
126
+ functionId: functionId ?? "",
127
+ nodeFunction: builder.parser.parseFunction(
128
+ // TODO: Upstream a fix to Three.js that accepts functions with no return type
129
+ forceExplicitVoidReturn(fnCode)
130
+ ),
131
+ // Including code that was resolved before the function as another node
132
+ // that this node depends on
133
+ priorCode: TSL__namespace.code(code),
134
+ dependencies: ctx.dependencies
135
+ };
136
+ }
137
+ return nodeData.custom.nodeFunction;
138
+ }
139
+ generate(builder, output) {
140
+ this.#getNodeFunction(builder);
141
+ const nodeData = builder.getDataFromNode(this);
142
+ const builderData = builderDataMap.get(builder);
143
+ const stageData = builderData.getStageData(builder.shaderStage);
144
+ for (const dep of nodeData.custom.dependencies) {
145
+ dep.node.build(builder);
146
+ }
147
+ nodeData.custom.priorCode.build(builder);
148
+ for (const dep of nodeData.custom.dependencies) {
149
+ if (!dep.var) {
150
+ continue;
151
+ }
152
+ const varName = stageData.names.get(dep.var);
153
+ const varValue = dep.node.build(builder);
154
+ builder.addLineFlowCode(`${varName} = ${varValue};
155
+ `, this);
156
+ }
157
+ if (output === "property") {
158
+ return nodeData.custom.functionId;
159
+ }
160
+ return `${nodeData.custom.functionId}()`;
161
+ }
162
+ }
163
+ function toTSL(fn) {
164
+ return TSL__namespace.nodeObject(new TgpuFnNode(fn));
165
+ }
166
+ class TSLAccessor {
167
+ #dataType;
168
+ var;
169
+ node;
170
+ constructor(node, dataType) {
171
+ this.node = node;
172
+ this.#dataType = dataType;
173
+ if (
174
+ // @ts-expect-error: The properties exist on the node
175
+ !node.isStorageBufferNode && !node.isUniformNode || node.isTextureNode
176
+ ) {
177
+ this.var = ((globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu__default.privateVar(dataType), "var"));
178
+ }
179
+ }
180
+ get $() {
181
+ const ctx = currentlyGeneratingFnNodeCtx;
182
+ if (!ctx) {
183
+ throw new Error("Can only access TSL nodes on the GPU.");
184
+ }
185
+ ctx.dependencies.push(this);
186
+ if (this.var) {
187
+ return this.var.$;
188
+ }
189
+ return tgpu__default["~unstable"].rawCodeSnippet(
190
+ this.node.build(ctx.builder),
191
+ this.#dataType
192
+ ).$;
193
+ }
194
+ }
195
+ const typeMap = {
196
+ "f": "f32",
197
+ "h": "f16",
198
+ "i": "i32",
199
+ "u": "u32",
200
+ "b": "bool"
201
+ };
202
+ function convertTypeToExplicit(type) {
203
+ if (type.startsWith("vec") && type.indexOf("<") === -1) {
204
+ const itemCount = type.charAt(3);
205
+ const itemType = typeMap[type.charAt(4)];
206
+ return `vec${itemCount}<${itemType}>`;
207
+ }
208
+ if (type.startsWith("mat") && type.indexOf("<") === -1) {
209
+ const itemCount = type.charAt(3);
210
+ const itemType = typeMap[type.charAt(6)];
211
+ return `mat${itemCount}x${itemCount}<${itemType}>`;
212
+ }
213
+ return type;
214
+ }
215
+ let sharedBuilder;
216
+ const fromTSL = ((globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu__default["~unstable"].comptime((node, type) => {
217
+ const tgpuType = d__namespace.isData(type) ? type : type(0);
218
+ const wgslTypeFromTgpu = convertTypeToExplicit(
219
+ `${d__namespace.isWgslArray(tgpuType) ? tgpuType.elementType : tgpuType}`
220
+ );
221
+ if (!sharedBuilder) {
222
+ sharedBuilder = new WGSLNodeBuilder__default();
223
+ }
224
+ const nodeType = node.getNodeType(sharedBuilder);
225
+ if (nodeType) {
226
+ const wgslTypeFromTSL = sharedBuilder.getType(nodeType);
227
+ if (wgslTypeFromTSL !== wgslTypeFromTgpu) {
228
+ const vec4warn = wgslTypeFromTSL.startsWith("vec4") ? " Sometimes three.js promotes elements in arrays to align to 16 bytes." : "";
229
+ console.warn(
230
+ `Suspected type mismatch between TSL type '${wgslTypeFromTSL}' (originally '${nodeType}') and TypeGPU type '${wgslTypeFromTgpu}'.${vec4warn}`
231
+ );
232
+ }
233
+ }
234
+ return new TSLAccessor(node, tgpuType);
235
+ }), "fromTSL"));
236
+
237
+ const uv = ((globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu__default["~unstable"].comptime(
238
+ (index) => fromTSL(TSL__namespace.uv(index), d__namespace.vec2f)
239
+ ), "uv"));
240
+ const time = fromTSL(TSL__namespace.time, d__namespace.f32);
241
+ const instanceIndex = fromTSL(TSL__namespace.instanceIndex, d__namespace.u32);
242
+ const vertexIndex = fromTSL(TSL__namespace.vertexIndex, d__namespace.u32);
243
+
244
+ const wgslTypeToGlslType = {
245
+ u32: "uint",
246
+ i32: "int",
247
+ f32: "float",
248
+ vec2u: "uvec2",
249
+ vec2i: "ivec2",
250
+ vec2f: "vec2",
251
+ vec3u: "uvec3",
252
+ vec3i: "ivec3",
253
+ vec3f: "vec3",
254
+ vec4u: "uvec4",
255
+ vec4i: "ivec4",
256
+ vec4f: "vec4"
257
+ };
258
+
259
+ function uniform(value, dataType) {
260
+ let glslType = wgslTypeToGlslType[dataType.type];
261
+ if (value.isNode) {
262
+ glslType = void 0;
263
+ }
264
+ return fromTSL(TSL.uniform(value, glslType), dataType);
265
+ }
266
+ function uniformArray(values, elementType) {
267
+ return fromTSL(TSL.uniformArray(values), d__namespace.arrayOf(elementType));
268
+ }
269
+
270
+ function instancedArray(count, elementType) {
271
+ const glslType = wgslTypeToGlslType[elementType.type];
272
+ return fromTSL(TSL.instancedArray(count, glslType), d__namespace.arrayOf(elementType));
273
+ }
274
+
275
+ exports.fromTSL = fromTSL;
276
+ exports.instanceIndex = instanceIndex;
277
+ exports.instancedArray = instancedArray;
278
+ exports.time = time;
279
+ exports.toTSL = toTSL;
280
+ exports.uniform = uniform;
281
+ exports.uniformArray = uniformArray;
282
+ exports.uv = uv;
283
+ exports.vertexIndex = vertexIndex;
package/index.d.cts ADDED
@@ -0,0 +1,54 @@
1
+ import * as typegpu from 'typegpu';
2
+ import { TgpuVar } from 'typegpu';
3
+ import * as THREE from 'three/webgpu';
4
+ import { UniformNode, UniformArrayNode, TypedArray, StorageBufferNode } from 'three/webgpu';
5
+ import * as d from 'typegpu/data';
6
+ import InputNode from 'three/src/nodes/core/InputNode.js';
7
+
8
+ declare function toTSL(fn: () => unknown): THREE.TSL.NodeObject<THREE.Node>;
9
+ declare class TSLAccessor<T extends d.AnyWgslData, TNode extends THREE.Node> {
10
+ #private;
11
+ readonly var: TgpuVar<'private', T> | undefined;
12
+ readonly node: THREE.TSL.NodeObject<TNode>;
13
+ constructor(node: THREE.TSL.NodeObject<TNode>, dataType: T);
14
+ get $(): d.InferGPU<T>;
15
+ }
16
+ declare const fromTSL: typegpu.TgpuComptime<(<T extends d.AnyWgslData, TNode extends THREE.Node>(node: THREE.TSL.NodeObject<TNode>, type: (length: number) => T) => TSLAccessor<T, TNode>) & (<T extends d.AnyWgslData, TNode extends THREE.Node>(node: THREE.TSL.NodeObject<TNode>, type: T) => TSLAccessor<T, TNode>)>;
17
+
18
+ declare const uv: typegpu.TgpuComptime<(index?: number | undefined) => TSLAccessor<d.Vec2f, THREE.AttributeNode>>;
19
+ declare const time: TSLAccessor<d.F32, THREE.Node>;
20
+ declare const instanceIndex: TSLAccessor<d.U32, THREE.IndexNode>;
21
+ declare const vertexIndex: TSLAccessor<d.U32, THREE.IndexNode>;
22
+
23
+ /**
24
+ * Shorthand for `t3.fromTSL(uniform(...), ...)`
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const attractorsLength = t3.uniform(attractorsPositions.array.length, d.u32);
29
+ * // Equivalent to:
30
+ * // const attractorsLength = t3.fromTSL(
31
+ * // uniform(attractorsPositions.array.length, 'uint'),
32
+ * // d.u32,
33
+ * // );
34
+ * ```
35
+ */
36
+ declare function uniform<TValue, TDataType extends d.AnyWgslData>(value: TValue | InputNode<TValue>, dataType: TDataType): TSLAccessor<TDataType, UniformNode<TValue>>;
37
+ declare function uniformArray<TDataType extends d.AnyWgslData>(values: unknown[], elementType: TDataType): TSLAccessor<d.WgslArray<TDataType>, UniformArrayNode>;
38
+
39
+ /**
40
+ * Shorthand for `t3.fromTSL(instancedArray(...), ...)`
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const velocityBuffer = t3.instancedArray(count, d.vec3f);
45
+ * // Equivalent to:
46
+ * // const velocityBuffer = t3.fromTSL(
47
+ * // instancedArray(count, 'vec3'),
48
+ * // d.arrayOf(d.vec3f),
49
+ * // );
50
+ * ```
51
+ */
52
+ declare function instancedArray<TDataType extends d.AnyWgslData>(count: number | TypedArray, elementType: TDataType): TSLAccessor<d.WgslArray<TDataType>, StorageBufferNode>;
53
+
54
+ export { TSLAccessor, fromTSL, instanceIndex, instancedArray, time, toTSL, uniform, uniformArray, uv, vertexIndex };
package/index.d.mts ADDED
@@ -0,0 +1,54 @@
1
+ import * as typegpu from 'typegpu';
2
+ import { TgpuVar } from 'typegpu';
3
+ import * as THREE from 'three/webgpu';
4
+ import { UniformNode, UniformArrayNode, TypedArray, StorageBufferNode } from 'three/webgpu';
5
+ import * as d from 'typegpu/data';
6
+ import InputNode from 'three/src/nodes/core/InputNode.js';
7
+
8
+ declare function toTSL(fn: () => unknown): THREE.TSL.NodeObject<THREE.Node>;
9
+ declare class TSLAccessor<T extends d.AnyWgslData, TNode extends THREE.Node> {
10
+ #private;
11
+ readonly var: TgpuVar<'private', T> | undefined;
12
+ readonly node: THREE.TSL.NodeObject<TNode>;
13
+ constructor(node: THREE.TSL.NodeObject<TNode>, dataType: T);
14
+ get $(): d.InferGPU<T>;
15
+ }
16
+ declare const fromTSL: typegpu.TgpuComptime<(<T extends d.AnyWgslData, TNode extends THREE.Node>(node: THREE.TSL.NodeObject<TNode>, type: (length: number) => T) => TSLAccessor<T, TNode>) & (<T extends d.AnyWgslData, TNode extends THREE.Node>(node: THREE.TSL.NodeObject<TNode>, type: T) => TSLAccessor<T, TNode>)>;
17
+
18
+ declare const uv: typegpu.TgpuComptime<(index?: number | undefined) => TSLAccessor<d.Vec2f, THREE.AttributeNode>>;
19
+ declare const time: TSLAccessor<d.F32, THREE.Node>;
20
+ declare const instanceIndex: TSLAccessor<d.U32, THREE.IndexNode>;
21
+ declare const vertexIndex: TSLAccessor<d.U32, THREE.IndexNode>;
22
+
23
+ /**
24
+ * Shorthand for `t3.fromTSL(uniform(...), ...)`
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const attractorsLength = t3.uniform(attractorsPositions.array.length, d.u32);
29
+ * // Equivalent to:
30
+ * // const attractorsLength = t3.fromTSL(
31
+ * // uniform(attractorsPositions.array.length, 'uint'),
32
+ * // d.u32,
33
+ * // );
34
+ * ```
35
+ */
36
+ declare function uniform<TValue, TDataType extends d.AnyWgslData>(value: TValue | InputNode<TValue>, dataType: TDataType): TSLAccessor<TDataType, UniformNode<TValue>>;
37
+ declare function uniformArray<TDataType extends d.AnyWgslData>(values: unknown[], elementType: TDataType): TSLAccessor<d.WgslArray<TDataType>, UniformArrayNode>;
38
+
39
+ /**
40
+ * Shorthand for `t3.fromTSL(instancedArray(...), ...)`
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const velocityBuffer = t3.instancedArray(count, d.vec3f);
45
+ * // Equivalent to:
46
+ * // const velocityBuffer = t3.fromTSL(
47
+ * // instancedArray(count, 'vec3'),
48
+ * // d.arrayOf(d.vec3f),
49
+ * // );
50
+ * ```
51
+ */
52
+ declare function instancedArray<TDataType extends d.AnyWgslData>(count: number | TypedArray, elementType: TDataType): TSLAccessor<d.WgslArray<TDataType>, StorageBufferNode>;
53
+
54
+ export { TSLAccessor, fromTSL, instanceIndex, instancedArray, time, toTSL, uniform, uniformArray, uv, vertexIndex };
package/index.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import * as typegpu from 'typegpu';
2
+ import { TgpuVar } from 'typegpu';
3
+ import * as THREE from 'three/webgpu';
4
+ import { UniformNode, UniformArrayNode, TypedArray, StorageBufferNode } from 'three/webgpu';
5
+ import * as d from 'typegpu/data';
6
+ import InputNode from 'three/src/nodes/core/InputNode.js';
7
+
8
+ declare function toTSL(fn: () => unknown): THREE.TSL.NodeObject<THREE.Node>;
9
+ declare class TSLAccessor<T extends d.AnyWgslData, TNode extends THREE.Node> {
10
+ #private;
11
+ readonly var: TgpuVar<'private', T> | undefined;
12
+ readonly node: THREE.TSL.NodeObject<TNode>;
13
+ constructor(node: THREE.TSL.NodeObject<TNode>, dataType: T);
14
+ get $(): d.InferGPU<T>;
15
+ }
16
+ declare const fromTSL: typegpu.TgpuComptime<(<T extends d.AnyWgslData, TNode extends THREE.Node>(node: THREE.TSL.NodeObject<TNode>, type: (length: number) => T) => TSLAccessor<T, TNode>) & (<T extends d.AnyWgslData, TNode extends THREE.Node>(node: THREE.TSL.NodeObject<TNode>, type: T) => TSLAccessor<T, TNode>)>;
17
+
18
+ declare const uv: typegpu.TgpuComptime<(index?: number | undefined) => TSLAccessor<d.Vec2f, THREE.AttributeNode>>;
19
+ declare const time: TSLAccessor<d.F32, THREE.Node>;
20
+ declare const instanceIndex: TSLAccessor<d.U32, THREE.IndexNode>;
21
+ declare const vertexIndex: TSLAccessor<d.U32, THREE.IndexNode>;
22
+
23
+ /**
24
+ * Shorthand for `t3.fromTSL(uniform(...), ...)`
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const attractorsLength = t3.uniform(attractorsPositions.array.length, d.u32);
29
+ * // Equivalent to:
30
+ * // const attractorsLength = t3.fromTSL(
31
+ * // uniform(attractorsPositions.array.length, 'uint'),
32
+ * // d.u32,
33
+ * // );
34
+ * ```
35
+ */
36
+ declare function uniform<TValue, TDataType extends d.AnyWgslData>(value: TValue | InputNode<TValue>, dataType: TDataType): TSLAccessor<TDataType, UniformNode<TValue>>;
37
+ declare function uniformArray<TDataType extends d.AnyWgslData>(values: unknown[], elementType: TDataType): TSLAccessor<d.WgslArray<TDataType>, UniformArrayNode>;
38
+
39
+ /**
40
+ * Shorthand for `t3.fromTSL(instancedArray(...), ...)`
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const velocityBuffer = t3.instancedArray(count, d.vec3f);
45
+ * // Equivalent to:
46
+ * // const velocityBuffer = t3.fromTSL(
47
+ * // instancedArray(count, 'vec3'),
48
+ * // d.arrayOf(d.vec3f),
49
+ * // );
50
+ * ```
51
+ */
52
+ declare function instancedArray<TDataType extends d.AnyWgslData>(count: number | TypedArray, elementType: TDataType): TSLAccessor<d.WgslArray<TDataType>, StorageBufferNode>;
53
+
54
+ export { TSLAccessor, fromTSL, instanceIndex, instancedArray, time, toTSL, uniform, uniformArray, uv, vertexIndex };
package/index.mjs ADDED
@@ -0,0 +1,254 @@
1
+ import * as TSL from 'three/tsl';
2
+ import { uniform as uniform$1, uniformArray as uniformArray$1, instancedArray as instancedArray$1 } from 'three/tsl';
3
+ import tgpu, { isVariable } from 'typegpu';
4
+ import * as d from 'typegpu/data';
5
+ import * as THREE from 'three/webgpu';
6
+ import WGSLNodeBuilder from 'three/src/renderers/webgpu/nodes/WGSLNodeBuilder.js';
7
+
8
+ class StageData {
9
+ stage;
10
+ names;
11
+ namespace;
12
+ codeGeneratedThusFar;
13
+ constructor(stage) {
14
+ this.stage = stage;
15
+ this.names = /* @__PURE__ */ new WeakMap();
16
+ this.namespace = tgpu["~unstable"].namespace();
17
+ this.codeGeneratedThusFar = "";
18
+ this.namespace.on("name", (event) => {
19
+ if (isVariable(event.target)) {
20
+ this.names.set(event.target, event.name);
21
+ }
22
+ });
23
+ }
24
+ }
25
+ class BuilderData {
26
+ stageDataMap;
27
+ constructor() {
28
+ this.stageDataMap = /* @__PURE__ */ new Map();
29
+ }
30
+ getStageData(stage) {
31
+ let stageData = this.stageDataMap.get(stage);
32
+ if (!stageData) {
33
+ stageData = new StageData(stage);
34
+ this.stageDataMap.set(stage, stageData);
35
+ }
36
+ return stageData;
37
+ }
38
+ }
39
+ const builderDataMap = /* @__PURE__ */ new WeakMap();
40
+ let currentlyGeneratingFnNodeCtx;
41
+ function forceExplicitVoidReturn(codeIn) {
42
+ if (codeIn.includes("->")) {
43
+ return codeIn;
44
+ }
45
+ const closingParen = codeIn.indexOf(")");
46
+ if (closingParen === -1) {
47
+ throw new Error("Invalid code: missing closing parenthesis");
48
+ }
49
+ return codeIn.substring(0, closingParen + 1) + "-> void" + codeIn.substring(closingParen + 1);
50
+ }
51
+ class TgpuFnNode extends THREE.Node {
52
+ #impl;
53
+ constructor(impl) {
54
+ super("typegpu-fn-node");
55
+ this.#impl = impl;
56
+ this.global = true;
57
+ }
58
+ static get type() {
59
+ return "TgpuFnNode";
60
+ }
61
+ getNodeType(builder) {
62
+ return this.#getNodeFunction(builder).type;
63
+ }
64
+ #getNodeFunction(builder) {
65
+ const nodeData = builder.getDataFromNode(this);
66
+ let builderData = builderDataMap.get(builder);
67
+ if (!builderData) {
68
+ builderData = new BuilderData();
69
+ builderDataMap.set(builder, builderData);
70
+ }
71
+ const stageData = builderData.getStageData(builder.shaderStage);
72
+ if (!nodeData.custom) {
73
+ if (currentlyGeneratingFnNodeCtx !== void 0) {
74
+ console.warn("[@typegpu/three] Nested function generation detected");
75
+ }
76
+ const ctx = {
77
+ builder,
78
+ stageData,
79
+ dependencies: []
80
+ };
81
+ currentlyGeneratingFnNodeCtx = ctx;
82
+ let resolved;
83
+ try {
84
+ resolved = tgpu.resolve({
85
+ names: stageData.namespace,
86
+ template: "___ID___ fnName",
87
+ externals: { fnName: this.#impl }
88
+ });
89
+ } finally {
90
+ currentlyGeneratingFnNodeCtx = void 0;
91
+ }
92
+ const [code = "", functionId] = resolved.split("___ID___").map(
93
+ (s) => s.trim()
94
+ );
95
+ stageData.codeGeneratedThusFar += code;
96
+ let lastFnStart = stageData.codeGeneratedThusFar.indexOf(
97
+ `
98
+ fn ${functionId}`
99
+ );
100
+ if (lastFnStart === -1) {
101
+ lastFnStart = 0;
102
+ }
103
+ const fnCode = stageData.codeGeneratedThusFar.slice(lastFnStart).trim();
104
+ nodeData.custom = {
105
+ functionId: functionId ?? "",
106
+ nodeFunction: builder.parser.parseFunction(
107
+ // TODO: Upstream a fix to Three.js that accepts functions with no return type
108
+ forceExplicitVoidReturn(fnCode)
109
+ ),
110
+ // Including code that was resolved before the function as another node
111
+ // that this node depends on
112
+ priorCode: TSL.code(code),
113
+ dependencies: ctx.dependencies
114
+ };
115
+ }
116
+ return nodeData.custom.nodeFunction;
117
+ }
118
+ generate(builder, output) {
119
+ this.#getNodeFunction(builder);
120
+ const nodeData = builder.getDataFromNode(this);
121
+ const builderData = builderDataMap.get(builder);
122
+ const stageData = builderData.getStageData(builder.shaderStage);
123
+ for (const dep of nodeData.custom.dependencies) {
124
+ dep.node.build(builder);
125
+ }
126
+ nodeData.custom.priorCode.build(builder);
127
+ for (const dep of nodeData.custom.dependencies) {
128
+ if (!dep.var) {
129
+ continue;
130
+ }
131
+ const varName = stageData.names.get(dep.var);
132
+ const varValue = dep.node.build(builder);
133
+ builder.addLineFlowCode(`${varName} = ${varValue};
134
+ `, this);
135
+ }
136
+ if (output === "property") {
137
+ return nodeData.custom.functionId;
138
+ }
139
+ return `${nodeData.custom.functionId}()`;
140
+ }
141
+ }
142
+ function toTSL(fn) {
143
+ return TSL.nodeObject(new TgpuFnNode(fn));
144
+ }
145
+ class TSLAccessor {
146
+ #dataType;
147
+ var;
148
+ node;
149
+ constructor(node, dataType) {
150
+ this.node = node;
151
+ this.#dataType = dataType;
152
+ if (
153
+ // @ts-expect-error: The properties exist on the node
154
+ !node.isStorageBufferNode && !node.isUniformNode || node.isTextureNode
155
+ ) {
156
+ this.var = ((globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu.privateVar(dataType), "var"));
157
+ }
158
+ }
159
+ get $() {
160
+ const ctx = currentlyGeneratingFnNodeCtx;
161
+ if (!ctx) {
162
+ throw new Error("Can only access TSL nodes on the GPU.");
163
+ }
164
+ ctx.dependencies.push(this);
165
+ if (this.var) {
166
+ return this.var.$;
167
+ }
168
+ return tgpu["~unstable"].rawCodeSnippet(
169
+ this.node.build(ctx.builder),
170
+ this.#dataType
171
+ ).$;
172
+ }
173
+ }
174
+ const typeMap = {
175
+ "f": "f32",
176
+ "h": "f16",
177
+ "i": "i32",
178
+ "u": "u32",
179
+ "b": "bool"
180
+ };
181
+ function convertTypeToExplicit(type) {
182
+ if (type.startsWith("vec") && type.indexOf("<") === -1) {
183
+ const itemCount = type.charAt(3);
184
+ const itemType = typeMap[type.charAt(4)];
185
+ return `vec${itemCount}<${itemType}>`;
186
+ }
187
+ if (type.startsWith("mat") && type.indexOf("<") === -1) {
188
+ const itemCount = type.charAt(3);
189
+ const itemType = typeMap[type.charAt(6)];
190
+ return `mat${itemCount}x${itemCount}<${itemType}>`;
191
+ }
192
+ return type;
193
+ }
194
+ let sharedBuilder;
195
+ const fromTSL = ((globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu["~unstable"].comptime((node, type) => {
196
+ const tgpuType = d.isData(type) ? type : type(0);
197
+ const wgslTypeFromTgpu = convertTypeToExplicit(
198
+ `${d.isWgslArray(tgpuType) ? tgpuType.elementType : tgpuType}`
199
+ );
200
+ if (!sharedBuilder) {
201
+ sharedBuilder = new WGSLNodeBuilder();
202
+ }
203
+ const nodeType = node.getNodeType(sharedBuilder);
204
+ if (nodeType) {
205
+ const wgslTypeFromTSL = sharedBuilder.getType(nodeType);
206
+ if (wgslTypeFromTSL !== wgslTypeFromTgpu) {
207
+ const vec4warn = wgslTypeFromTSL.startsWith("vec4") ? " Sometimes three.js promotes elements in arrays to align to 16 bytes." : "";
208
+ console.warn(
209
+ `Suspected type mismatch between TSL type '${wgslTypeFromTSL}' (originally '${nodeType}') and TypeGPU type '${wgslTypeFromTgpu}'.${vec4warn}`
210
+ );
211
+ }
212
+ }
213
+ return new TSLAccessor(node, tgpuType);
214
+ }), "fromTSL"));
215
+
216
+ const uv = ((globalThis.__TYPEGPU_AUTONAME__ ?? (a => a))(tgpu["~unstable"].comptime(
217
+ (index) => fromTSL(TSL.uv(index), d.vec2f)
218
+ ), "uv"));
219
+ const time = fromTSL(TSL.time, d.f32);
220
+ const instanceIndex = fromTSL(TSL.instanceIndex, d.u32);
221
+ const vertexIndex = fromTSL(TSL.vertexIndex, d.u32);
222
+
223
+ const wgslTypeToGlslType = {
224
+ u32: "uint",
225
+ i32: "int",
226
+ f32: "float",
227
+ vec2u: "uvec2",
228
+ vec2i: "ivec2",
229
+ vec2f: "vec2",
230
+ vec3u: "uvec3",
231
+ vec3i: "ivec3",
232
+ vec3f: "vec3",
233
+ vec4u: "uvec4",
234
+ vec4i: "ivec4",
235
+ vec4f: "vec4"
236
+ };
237
+
238
+ function uniform(value, dataType) {
239
+ let glslType = wgslTypeToGlslType[dataType.type];
240
+ if (value.isNode) {
241
+ glslType = void 0;
242
+ }
243
+ return fromTSL(uniform$1(value, glslType), dataType);
244
+ }
245
+ function uniformArray(values, elementType) {
246
+ return fromTSL(uniformArray$1(values), d.arrayOf(elementType));
247
+ }
248
+
249
+ function instancedArray(count, elementType) {
250
+ const glslType = wgslTypeToGlslType[elementType.type];
251
+ return fromTSL(instancedArray$1(count, glslType), d.arrayOf(elementType));
252
+ }
253
+
254
+ export { fromTSL, instanceIndex, instancedArray, time, toTSL, uniform, uniformArray, uv, vertexIndex };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@typegpu/three",
3
+ "type": "module",
4
+ "version": "0.9.0",
5
+ "description": "Utilities for integrating TypeGPU with Three.js",
6
+ "exports": {
7
+ "./package.json": "./package.json",
8
+ ".": {
9
+ "types": "./index.d.ts",
10
+ "module": "./index.mjs",
11
+ "import": "./index.mjs",
12
+ "default": "./index.cjs"
13
+ }
14
+ },
15
+ "sideEffects": false,
16
+ "keywords": [],
17
+ "license": "MIT",
18
+ "peerDependencies": {
19
+ "typegpu": "^0.9.0",
20
+ "three": ">0.126.0"
21
+ },
22
+ "main": "./index.mjs",
23
+ "types": "./index.d.ts",
24
+ "private": false,
25
+ "dependencies": {},
26
+ "scripts": {}
27
+ }