@babylonjs/smart-filters 0.1.0-alpha → 0.3.0-alpha

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.
Files changed (111) hide show
  1. package/dist/blocks/baseBlock.d.ts +1 -1
  2. package/dist/blocks/baseBlock.d.ts.map +1 -1
  3. package/dist/blocks/baseBlock.js +1 -1
  4. package/dist/blocks/baseBlock.js.map +1 -1
  5. package/dist/blocks/copyBlock.d.ts +5 -7
  6. package/dist/blocks/copyBlock.d.ts.map +1 -1
  7. package/dist/blocks/copyBlock.js +4 -24
  8. package/dist/blocks/copyBlock.js.map +1 -1
  9. package/dist/blocks/copyBlock.shader.d.ts +13 -0
  10. package/dist/blocks/copyBlock.shader.d.ts.map +1 -0
  11. package/dist/blocks/copyBlock.shader.js +31 -0
  12. package/dist/blocks/copyBlock.shader.js.map +1 -0
  13. package/dist/blocks/inputBlock.d.ts +15 -5
  14. package/dist/blocks/inputBlock.d.ts.map +1 -1
  15. package/dist/blocks/inputBlock.deserializer.d.ts +14 -0
  16. package/dist/blocks/inputBlock.deserializer.d.ts.map +1 -0
  17. package/dist/blocks/inputBlock.deserializer.js +23 -0
  18. package/dist/blocks/inputBlock.deserializer.js.map +1 -0
  19. package/dist/blocks/inputBlock.js +12 -6
  20. package/dist/blocks/inputBlock.js.map +1 -1
  21. package/dist/blocks/inputBlock.serialization.types.d.ts +62 -0
  22. package/dist/blocks/inputBlock.serialization.types.d.ts.map +1 -0
  23. package/dist/blocks/inputBlock.serialization.types.js +2 -0
  24. package/dist/blocks/inputBlock.serialization.types.js.map +1 -0
  25. package/dist/blocks/inputBlock.serializer.d.ts +6 -0
  26. package/dist/blocks/inputBlock.serializer.d.ts.map +1 -0
  27. package/dist/blocks/inputBlock.serializer.js +110 -0
  28. package/dist/blocks/inputBlock.serializer.js.map +1 -0
  29. package/dist/connection/connectionPointType.d.ts +6 -3
  30. package/dist/connection/connectionPointType.d.ts.map +1 -1
  31. package/dist/connection/connectionPointType.js +2 -0
  32. package/dist/connection/connectionPointType.js.map +1 -1
  33. package/dist/index.d.ts +1 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +1 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/optimization/optimizedShaderBlock.d.ts +3 -3
  38. package/dist/optimization/optimizedShaderBlock.d.ts.map +1 -1
  39. package/dist/optimization/optimizedShaderBlock.js.map +1 -1
  40. package/dist/runtime/renderTargetGenerator.d.ts.map +1 -1
  41. package/dist/runtime/renderTargetGenerator.js +3 -1
  42. package/dist/runtime/renderTargetGenerator.js.map +1 -1
  43. package/dist/serialization/index.d.ts +5 -0
  44. package/dist/serialization/index.d.ts.map +1 -0
  45. package/dist/serialization/index.js +5 -0
  46. package/dist/serialization/index.js.map +1 -0
  47. package/dist/serialization/serializedSmartFilter.d.ts +6 -0
  48. package/dist/serialization/serializedSmartFilter.d.ts.map +1 -0
  49. package/dist/serialization/serializedSmartFilter.js +2 -0
  50. package/dist/serialization/serializedSmartFilter.js.map +1 -0
  51. package/dist/serialization/smartFilterDeserializer.d.ts +25 -0
  52. package/dist/serialization/smartFilterDeserializer.d.ts.map +1 -0
  53. package/dist/serialization/smartFilterDeserializer.js +94 -0
  54. package/dist/serialization/smartFilterDeserializer.js.map +1 -0
  55. package/dist/serialization/smartFilterSerializer.d.ts +23 -0
  56. package/dist/serialization/smartFilterSerializer.d.ts.map +1 -0
  57. package/dist/serialization/smartFilterSerializer.js +91 -0
  58. package/dist/serialization/smartFilterSerializer.js.map +1 -0
  59. package/dist/serialization/v1/defaultBlockSerializer.d.ts +9 -0
  60. package/dist/serialization/v1/defaultBlockSerializer.d.ts.map +1 -0
  61. package/dist/serialization/v1/defaultBlockSerializer.js +16 -0
  62. package/dist/serialization/v1/defaultBlockSerializer.js.map +1 -0
  63. package/dist/serialization/v1/index.d.ts +3 -0
  64. package/dist/serialization/v1/index.d.ts.map +1 -0
  65. package/dist/serialization/v1/index.js +3 -0
  66. package/dist/serialization/v1/index.js.map +1 -0
  67. package/dist/serialization/v1/serialization.types.d.ts +83 -0
  68. package/dist/serialization/v1/serialization.types.d.ts.map +1 -0
  69. package/dist/serialization/v1/serialization.types.js +2 -0
  70. package/dist/serialization/v1/serialization.types.js.map +1 -0
  71. package/dist/smartFilter.d.ts +2 -2
  72. package/dist/smartFilter.d.ts.map +1 -1
  73. package/dist/smartFilter.js +0 -1
  74. package/dist/smartFilter.js.map +1 -1
  75. package/dist/utils/buildTools/shaderConverter.d.ts +2 -0
  76. package/dist/utils/buildTools/shaderConverter.d.ts.map +1 -0
  77. package/dist/utils/buildTools/shaderConverter.js +271 -0
  78. package/dist/utils/buildTools/shaderConverter.js.map +1 -0
  79. package/dist/utils/textureLoaders.d.ts +2 -1
  80. package/dist/utils/textureLoaders.d.ts.map +1 -1
  81. package/dist/utils/textureLoaders.js +3 -2
  82. package/dist/utils/textureLoaders.js.map +1 -1
  83. package/dist/utils/uniqueIdGenerator.d.ts +19 -0
  84. package/dist/utils/uniqueIdGenerator.d.ts.map +1 -0
  85. package/dist/utils/uniqueIdGenerator.js +27 -0
  86. package/dist/utils/uniqueIdGenerator.js.map +1 -0
  87. package/package.json +11 -5
  88. package/readme.md +12 -0
  89. package/src/blocks/baseBlock.ts +3 -4
  90. package/src/blocks/copyBlock.fragment.glsl +5 -0
  91. package/src/blocks/copyBlock.shader.ts +33 -0
  92. package/src/blocks/copyBlock.ts +7 -33
  93. package/src/blocks/inputBlock.deserializer.ts +38 -0
  94. package/src/blocks/inputBlock.serialization.types.ts +80 -0
  95. package/src/blocks/inputBlock.serializer.ts +127 -0
  96. package/src/blocks/inputBlock.ts +18 -7
  97. package/src/connection/connectionPointType.ts +6 -2
  98. package/src/index.ts +1 -0
  99. package/src/optimization/optimizedShaderBlock.ts +6 -5
  100. package/src/runtime/renderTargetGenerator.ts +5 -1
  101. package/src/serialization/index.ts +4 -0
  102. package/src/serialization/serializedSmartFilter.ts +6 -0
  103. package/src/serialization/smartFilterDeserializer.ts +127 -0
  104. package/src/serialization/smartFilterSerializer.ts +113 -0
  105. package/src/serialization/v1/defaultBlockSerializer.ts +18 -0
  106. package/src/serialization/v1/index.ts +2 -0
  107. package/src/serialization/v1/serialization.types.ts +108 -0
  108. package/src/smartFilter.ts +2 -2
  109. package/src/utils/buildTools/shaderConverter.ts +388 -0
  110. package/src/utils/textureLoaders.ts +16 -2
  111. package/src/utils/uniqueIdGenerator.ts +28 -0
@@ -0,0 +1,108 @@
1
+ import type { Nullable } from "@babylonjs/core/types.js";
2
+ import type { BaseBlock } from "../../blocks/baseBlock.js";
3
+ import type { SmartFilter } from "../../smartFilter.js";
4
+ import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine";
5
+ import type { IEditorData } from "@babylonjs/shared-ui-components/nodeGraphSystem/interfaces/nodeLocationInfo.js";
6
+
7
+ /**
8
+ * ----------------------------------------------------------------------------
9
+ * Serialized Data Types
10
+ * ----------------------------------------------------------------------------
11
+ */
12
+
13
+ /**
14
+ * V1 Serialized Smart Filter
15
+ */
16
+ export type SerializedSmartFilterV1 = {
17
+ /** The version of the serialized data */
18
+ version: 1;
19
+
20
+ /** The SmartFilter name */
21
+ name: string;
22
+
23
+ /** The SmartFilter comments */
24
+ comments: Nullable<string>;
25
+
26
+ /** The editor data for the SmartFilter */
27
+ editorData: Nullable<IEditorData>;
28
+
29
+ /** The serialized blocks */
30
+ blocks: ISerializedBlockV1[];
31
+
32
+ /** The serialized connections */
33
+ connections: ISerializedConnectionV1[];
34
+ };
35
+
36
+ /**
37
+ * V1 Serialized Block
38
+ */
39
+ export interface ISerializedBlockV1 {
40
+ /** The name of the block */
41
+ name: string;
42
+
43
+ /** The unique ID of the block - correlates with the ID in the editorData for block position, etc. */
44
+ uniqueId: number;
45
+
46
+ /** The class name of the block */
47
+ className: string;
48
+
49
+ /** The comments for the block */
50
+ comments: Nullable<string>;
51
+
52
+ /** Block specific serialized data */
53
+ data: any;
54
+ }
55
+
56
+ /**
57
+ * V1 Serialized Connection
58
+ */
59
+ export interface ISerializedConnectionV1 {
60
+ /** The name of the block that the connection is to */
61
+ outputBlock: string;
62
+
63
+ /** The name of the connectionPoint on the outputBlock */
64
+ outputConnectionPoint: string;
65
+
66
+ /** The name of the block that the connection is from */
67
+ inputBlock: string;
68
+
69
+ /** The name of the connectionPoint on the inputBlock */
70
+ inputConnectionPoint: string;
71
+ }
72
+
73
+ /**
74
+ * ----------------------------------------------------------------------------
75
+ * Serializer Types
76
+ * ----------------------------------------------------------------------------
77
+ */
78
+
79
+ /**
80
+ * A function that serializes a block to a V1 serialized block object
81
+ */
82
+ export type SerializeBlockV1 = (block: BaseBlock) => ISerializedBlockV1;
83
+
84
+ /**
85
+ * A V1 block serializer
86
+ */
87
+ export interface IBlockSerializerV1 {
88
+ /** The className of the block that this serializer can serialize */
89
+ className: string;
90
+
91
+ /** The function that serializes the block */
92
+ serialize: SerializeBlockV1;
93
+ }
94
+
95
+ /**
96
+ * ----------------------------------------------------------------------------
97
+ * Deserializer Types
98
+ * ----------------------------------------------------------------------------
99
+ */
100
+
101
+ /**
102
+ * A function that deserializes a block from a V1 serialized block object
103
+ */
104
+ export type DeserializeBlockV1 = (
105
+ smartFilter: SmartFilter,
106
+ serializedBlock: ISerializedBlockV1,
107
+ engine: ThinEngine
108
+ ) => Promise<BaseBlock>;
@@ -8,6 +8,7 @@ import { OutputBlock } from "./blocks/outputBlock.js";
8
8
  import { InternalSmartFilterRuntime } from "./runtime/smartFilterRuntime.js";
9
9
  import { RenderTargetGenerator } from "./runtime/renderTargetGenerator.js";
10
10
  import { AggregateBlock } from "./blocks/aggregateBlock.js";
11
+ import type { IEditorData } from "@babylonjs/shared-ui-components/nodeGraphSystem/interfaces/nodeLocationInfo";
11
12
 
12
13
  /**
13
14
  * How long to wait for shader compilation and texture loading to complete before erroring out.
@@ -61,9 +62,8 @@ export class SmartFilter {
61
62
 
62
63
  /**
63
64
  * Data used by the smart filter editor.
64
- * TODO. strong type and hide this.
65
65
  */
66
- public editorData: any = null;
66
+ public editorData: Nullable<IEditorData> = null;
67
67
 
68
68
  private readonly _attachedBlocks: Array<BaseBlock>;
69
69
  private readonly _outputBlock: OutputBlock;
@@ -0,0 +1,388 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ const TYPE_IMPORT_PATH = "@TYPE_IMPORT_PATH@";
5
+ const VERTEX_SHADER = "@VERTEX_SHADER@";
6
+ const UNIFORMS = "@UNIFORMS@";
7
+ const CONSTS_VALUE = "@CONSTS@";
8
+ const CONSTS_PROPERTY = "@CONSTS_PROPERTY@";
9
+ const MAIN_INPUT_NAME = "@MAIN_INPUT_NAME@";
10
+ const MAIN_FUNCTION_NAME = "@MAIN_FUNCTION_NAME@";
11
+ const FUNCTIONS = "@FUNCTIONS@";
12
+ const FUNCTION_NAME = "@FUNCTION_NAME@";
13
+ const FUNCTION_CODE = "@FUNCTION_CODE@";
14
+ const UNIFORM_NAMES = "@UNIFORM_NAMES@";
15
+
16
+ const ConstsTemplate = `
17
+ const: \`${CONSTS_VALUE}\`,`;
18
+
19
+ const FunctionTemplate = `
20
+ {
21
+ name: "${FUNCTION_NAME}",
22
+ code: \`
23
+ ${FUNCTION_CODE}
24
+ \`,
25
+ },`;
26
+
27
+ const CodeLinePrefix = " ";
28
+ const UniformLinePrefix = " ";
29
+ const ConstLinePrefix = " ";
30
+
31
+ const ShaderTemplate = `import type { ShaderProgram } from "${TYPE_IMPORT_PATH}";
32
+
33
+ /**
34
+ * The shader program for the block.
35
+ */
36
+ export const shaderProgram: ShaderProgram = {
37
+ vertex: ${VERTEX_SHADER},
38
+ fragment: {
39
+ uniform: \`${UNIFORMS}\`,${CONSTS_PROPERTY}
40
+ mainInputTexture: "${MAIN_INPUT_NAME}",
41
+ mainFunctionName: "${MAIN_FUNCTION_NAME}",
42
+ functions: [${FUNCTIONS}
43
+ ],
44
+ },
45
+ };
46
+
47
+ /**
48
+ * The uniform names for this shader, to be used in the shader binding so
49
+ * that the names are always in sync.
50
+ */
51
+ export const uniforms = {
52
+ ${UNIFORM_NAMES}
53
+ };
54
+ `;
55
+
56
+ const UniformNameLinePrefix = " ";
57
+
58
+ const GetFunctionNamesRegEx = /\S*\w+\s+(\w+)\s*\(/g;
59
+
60
+ /**
61
+ * Converts a single shader
62
+ * @param fragmentShaderPath - The path to the fragment file for the shader
63
+ * @param importPath - The path to import the ShaderProgram type from
64
+ */
65
+ function convertShader(fragmentShaderPath: string, importPath: string): void {
66
+ console.log(`Processing fragment shader: ${fragmentShaderPath}`);
67
+
68
+ // See if there is a corresponding vertex shader
69
+ let vertexShader: string | undefined = undefined;
70
+ const vertexShaderPath = fragmentShaderPath.replace(".fragment.glsl", ".vertex.glsl");
71
+ if (fs.existsSync(vertexShaderPath)) {
72
+ vertexShader = fs.readFileSync(vertexShaderPath, "utf8");
73
+ }
74
+ if (vertexShader) {
75
+ console.log("Found vertex shader");
76
+ }
77
+
78
+ // Read the fragment shader
79
+ const fragmentShader = fs.readFileSync(fragmentShaderPath, "utf8");
80
+
81
+ // Version check
82
+ const versionsFound = [...fragmentShader.matchAll(/\/\/\s+\[Smart Filter Shader Version\]\s*=\s*(\d+)/g)].map(
83
+ (match) => match[1]
84
+ );
85
+ let version: number = 1;
86
+ if (versionsFound.length === 1 && versionsFound[0]) {
87
+ version = parseInt(versionsFound[0]);
88
+ }
89
+ console.log(`Shader version: ${version}`);
90
+
91
+ let fragmentShaderInfo: FragmentShaderInfo;
92
+
93
+ switch (version) {
94
+ case 1:
95
+ {
96
+ fragmentShaderInfo = processFragmentShaderV1(fragmentShader);
97
+ }
98
+ break;
99
+ default: {
100
+ throw new Error(`Unsupported shader version: ${version}`);
101
+ }
102
+ }
103
+
104
+ // Write the shader TS file
105
+ const shaderFile = fragmentShaderPath.replace(".fragment.glsl", ".shader.ts");
106
+ const finalContents = ShaderTemplate.replace(VERTEX_SHADER, vertexShader ? `\`${vertexShader}\`` : "undefined")
107
+ .replace(TYPE_IMPORT_PATH, importPath)
108
+ .replace(UNIFORMS, "\n" + addLinePrefixes(fragmentShaderInfo.finalUniforms.join("\n"), UniformLinePrefix))
109
+ .replace(MAIN_FUNCTION_NAME, fragmentShaderInfo.mainFunctionName)
110
+ .replace(MAIN_INPUT_NAME, fragmentShaderInfo.mainInputName)
111
+ .replace(
112
+ CONSTS_PROPERTY,
113
+ fragmentShaderInfo.finalConsts.length > 0
114
+ ? ConstsTemplate.replace(
115
+ CONSTS_VALUE,
116
+ addLinePrefixes(fragmentShaderInfo.finalConsts.join("\n"), ConstLinePrefix)
117
+ )
118
+ : ""
119
+ )
120
+ .replace(FUNCTIONS, fragmentShaderInfo.extractedFunctions.join(""))
121
+ .replace(UNIFORM_NAMES, addLinePrefixes(fragmentShaderInfo.uniformNames.join("\n"), UniformNameLinePrefix));
122
+
123
+ fs.writeFileSync(shaderFile, finalContents);
124
+ }
125
+
126
+ /**
127
+ * Information about a fragment shader
128
+ */
129
+ type FragmentShaderInfo = {
130
+ /**
131
+ * The lines of code which declare the uniforms
132
+ */
133
+ finalUniforms: (string | undefined)[];
134
+
135
+ /**
136
+ * The main function name
137
+ */
138
+ mainFunctionName: string;
139
+
140
+ /**
141
+ * The main input name
142
+ */
143
+ mainInputName: string;
144
+
145
+ /**
146
+ * The lines of code which declare the consts
147
+ */
148
+ finalConsts: (string | undefined)[];
149
+
150
+ /**
151
+ * The lines of code which declare the functions
152
+ */
153
+ extractedFunctions: string[];
154
+
155
+ /**
156
+ * The lines of code which declare the uniform names
157
+ */
158
+ uniformNames: string[];
159
+ };
160
+
161
+ /**
162
+ * Processes a fragment shader
163
+ * @param fragmentShader - The fragment shader to process
164
+ * @returns The processed fragment shader
165
+ */
166
+ function processFragmentShaderV1(fragmentShader: string): FragmentShaderInfo {
167
+ const fragmentShaderWithNoFunctionBodies = removeFunctionBodies(fragmentShader);
168
+
169
+ // Collect uniform, const, and function names which need to be decorated
170
+ // eslint-disable-next-line prettier/prettier
171
+ const uniforms = [...fragmentShader.matchAll(/\S*uniform.*\s(\w*);/g)].map((match) => match[1]);
172
+ console.log(`Uniforms found: ${JSON.stringify(uniforms)}`);
173
+ const consts = [...fragmentShader.matchAll(/\S*const\s+\w*\s+(\w*)\s*=.*;/g)].map((match) => match[1]);
174
+ console.log(`Consts found: ${JSON.stringify(consts)}`);
175
+ const functionNames = [...fragmentShaderWithNoFunctionBodies.matchAll(GetFunctionNamesRegEx)].map(
176
+ (match) => match[1]
177
+ );
178
+ console.log(`Functions found: ${JSON.stringify(functionNames)}`);
179
+
180
+ // Decorate the uniforms, consts, and functions
181
+ const symbolsToDecorate = [...uniforms, ...consts, ...functionNames];
182
+ let fragmentShaderWithRenamedSymbols = fragmentShader;
183
+ for (const symbol of symbolsToDecorate) {
184
+ const regex = new RegExp(`(\\S*(?:\\s|;|,|\\)|\\()+)${symbol}((\\s|;|,|\\)|\\()+)`, "gs");
185
+ fragmentShaderWithRenamedSymbols = fragmentShaderWithRenamedSymbols.replace(regex, `$1_${symbol}_$2`);
186
+ }
187
+ console.log(`${symbolsToDecorate.length} symbol(s) renamed`);
188
+ const uniformNames = uniforms.map((uniform) => `${uniform}: "${uniform}",`);
189
+
190
+ // Extract all the uniforms
191
+ const finalUniforms = [...fragmentShaderWithRenamedSymbols.matchAll(/^\s*(uniform\s.*)/gm)].map(
192
+ (match) => match[1]
193
+ );
194
+
195
+ // Extract all the consts
196
+ const finalConsts = [...fragmentShaderWithRenamedSymbols.matchAll(/^\s*(const\s.*)/gm)].map((match) => match[1]);
197
+
198
+ // Extract all the functions
199
+ const { extractedFunctions, mainFunctionName } = extractFunctions(fragmentShaderWithRenamedSymbols);
200
+
201
+ // Find the main input
202
+ const mainInputs = [...fragmentShaderWithRenamedSymbols.matchAll(/\S*uniform.*\s(\w*);\s*\/\/\s*main/gm)].map(
203
+ (match) => match[1]
204
+ );
205
+ if (mainInputs.length !== 1 || !mainInputs[0]) {
206
+ throw new Error("Exactly one main input must be defined in the shader");
207
+ }
208
+ const mainInputName = mainInputs[0];
209
+
210
+ return {
211
+ finalUniforms,
212
+ mainFunctionName,
213
+ mainInputName,
214
+ finalConsts,
215
+ extractedFunctions,
216
+ uniformNames,
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Prefixes each line in the input
222
+ * @param input - The input string
223
+ * @param prefix - The prefix to add to each line
224
+ * @returns The input with each line prefixed
225
+ */
226
+ function addLinePrefixes(input: string, prefix: string): string {
227
+ return input
228
+ .split("\n")
229
+ .map((line) => prefix + line)
230
+ .join("\n");
231
+ }
232
+
233
+ /**
234
+ * Extracts all the functions from the shader
235
+ * @param fragment - The shader code to process
236
+ * @returns A list of functions
237
+ */
238
+ function extractFunctions(fragment: string): {
239
+ /**
240
+ * The extracted functions
241
+ */
242
+ extractedFunctions: string[];
243
+
244
+ /**
245
+ * The name of the main function
246
+ */
247
+ mainFunctionName: string;
248
+ } {
249
+ const lines = fragment.split("\n");
250
+ const extractedFunctions: string[] = [];
251
+ let mainFunctionName: string | undefined;
252
+ let inFunction = false;
253
+ let depth = 0;
254
+ let currentFunction = "";
255
+
256
+ for (const line of lines) {
257
+ if (!inFunction && line.includes("{")) {
258
+ inFunction = true;
259
+ }
260
+ if (inFunction) {
261
+ currentFunction += line + "\n";
262
+ }
263
+ for (let pos = 0; pos < line.length; pos++) {
264
+ if (line[pos] === "{") {
265
+ depth++;
266
+ } else if (line[pos] === "}") {
267
+ depth--;
268
+ }
269
+ }
270
+ if (inFunction && depth === 0) {
271
+ inFunction = false;
272
+ const { functionBody, functionName, isMainFunction } = processFunctionBody(currentFunction);
273
+ extractedFunctions.push(
274
+ FunctionTemplate.replace(FUNCTION_NAME, functionName).replace(
275
+ FUNCTION_CODE,
276
+ addLinePrefixes(functionBody, CodeLinePrefix)
277
+ )
278
+ );
279
+ if (isMainFunction) {
280
+ if (mainFunctionName) {
281
+ throw new Error("Multiple main functions found in shader code");
282
+ }
283
+ mainFunctionName = functionName;
284
+ }
285
+ currentFunction = "";
286
+ }
287
+ }
288
+
289
+ if (!mainFunctionName) {
290
+ throw new Error("No main function found in shader code");
291
+ }
292
+
293
+ return { extractedFunctions, mainFunctionName };
294
+ }
295
+
296
+ /**
297
+ * Creates code for a ShaderFunction instance from the body of a function
298
+ * @param functionBody - The body of the function
299
+ * @returns The body, name, and whether the function is the main function
300
+ */
301
+ function processFunctionBody(functionBody: string): {
302
+ /**
303
+ * The body of the function
304
+ */
305
+ functionBody: string;
306
+
307
+ /**
308
+ * The name of the function
309
+ */
310
+ functionName: string;
311
+
312
+ /**
313
+ * Whether the function is the main function
314
+ */
315
+ isMainFunction: boolean;
316
+ } {
317
+ // Extract the function name
318
+ const functionNamesFound = [...functionBody.matchAll(GetFunctionNamesRegEx)].map((match) => match[1]);
319
+ const functionName = functionNamesFound[0];
320
+ if (!functionName) {
321
+ throw new Error("No function name found in shader code");
322
+ }
323
+
324
+ const isMainFunction = functionBody.includes("// main");
325
+
326
+ return { functionBody, functionName, isMainFunction };
327
+ }
328
+
329
+ /**
330
+ * Removes all function bodies from the shader code, leaving just curly braces behind at the top level
331
+ * @param input - The shader code to process
332
+ * @returns The shader code with all function bodies removed
333
+ */
334
+ function removeFunctionBodies(input: string): string {
335
+ let output: string = "";
336
+ let depth: number = 0;
337
+
338
+ for (let pos = 0; pos < input.length; pos++) {
339
+ if (input[pos] === "{") {
340
+ depth++;
341
+ // Special case - if we just hit the first { then include it
342
+ if (depth === 1) {
343
+ output += "{";
344
+ }
345
+ } else if (input[pos] === "}") {
346
+ depth--;
347
+ // Special case - if we just hit the last } then include it
348
+ if (depth === 0) {
349
+ output += "}";
350
+ }
351
+ } else if (depth === 0) {
352
+ output += input[pos];
353
+ }
354
+ }
355
+
356
+ if (depth !== 0) {
357
+ console.error("Unbalanced curly braces in shader code");
358
+ }
359
+
360
+ return output;
361
+ }
362
+
363
+ /**
364
+ * Converts .fragment.glsl and vertex.glsl file pairs into .shader.ts files which export a ShaderProgram object.
365
+ * @param shaderPath - The path to the .glsl files to convert.
366
+ * @param importPath - The path to import the ShaderProgram type from.
367
+ */
368
+ function convertShaders(shaderPath: string, importPath: string) {
369
+ // Get all files in the path
370
+ const allFiles = fs.readdirSync(shaderPath, { withFileTypes: true, recursive: true });
371
+
372
+ // Find all fragment shaders (excluding the template)
373
+ const fragmentShaderFiles = allFiles.filter(
374
+ (file) => file.isFile() && file.name.endsWith(".fragment.glsl") && !file.name.endsWith("template.fragment.glsl")
375
+ );
376
+
377
+ // Convert all shaders
378
+ for (const fragmentShaderFile of fragmentShaderFiles) {
379
+ convertShader(path.join(fragmentShaderFile.path, fragmentShaderFile.name), importPath);
380
+ }
381
+ }
382
+
383
+ const externalArguments = process.argv.slice(2);
384
+ if (externalArguments.length >= 2 && externalArguments[0] && externalArguments[1]) {
385
+ convertShaders(externalArguments[0], externalArguments[1]);
386
+ }
387
+
388
+ // TODO: simple copy from shader file to .ts, get it to build (including import trick)
@@ -9,15 +9,29 @@ import { type ThinEngine } from "@babylonjs/core/Engines/thinEngine.js";
9
9
  * * A base64 string of in-line texture data, e.g. 'data:image/jpg;base64,/...'
10
10
  * @param flipY - Indicates if the Y axis should be flipped
11
11
  * @param samplingMode - The sampling mode to use
12
+ * @param forcedExtension - defines the extension to use to pick the right loader
12
13
  * @returns A ThinTexture of the image
13
14
  */
14
15
  export function createImageTexture(
15
16
  engine: ThinEngine,
16
17
  url: string,
17
18
  flipY: boolean = true,
18
- samplingMode: number | undefined = undefined
19
+ samplingMode: number | undefined = undefined,
20
+ forcedExtension: string | null = null
19
21
  ): ThinTexture {
20
- const internalTexture = engine.createTexture(url, true, flipY, null, samplingMode);
22
+ const internalTexture = engine.createTexture(
23
+ url,
24
+ true,
25
+ flipY,
26
+ null,
27
+ samplingMode,
28
+ null,
29
+ null,
30
+ null,
31
+ null,
32
+ null,
33
+ forcedExtension
34
+ );
21
35
  return new ThinTexture(internalTexture);
22
36
  }
23
37
 
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Helper class used to generate IDs unique to the current session
3
+ */
4
+ export class UniqueIdGenerator {
5
+ /**
6
+ * The next unique ID to be returned
7
+ */
8
+ public static _NextUniqueId = 1;
9
+
10
+ /**
11
+ * Gets a unique (relatively to the current session) Id
12
+ */
13
+ public static get UniqueId() {
14
+ const result = this._NextUniqueId;
15
+ this._NextUniqueId++;
16
+ return result;
17
+ }
18
+
19
+ /**
20
+ * Ensures future generated IDs are greater than the specified value
21
+ * @param minimum - The minimum value that future generated IDs should be greater than
22
+ */
23
+ public static EnsureIdsGreaterThan(minimum: number): void {
24
+ if (this._NextUniqueId <= minimum) {
25
+ this._NextUniqueId = minimum + 1;
26
+ }
27
+ }
28
+ }