@nice2dev/ui-3d 1.3.15 → 1.3.17

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 (108) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/collaborative/collaborativeScene.js +160 -206
  3. package/dist/cjs/core/editorAsk.js +51 -41
  4. package/dist/cjs/core/i18n.js +13 -14
  5. package/dist/cjs/core/threeCompat.js +29 -42
  6. package/dist/cjs/dance/DanceBridge.js +148 -177
  7. package/dist/cjs/dance/DanceScoreEngine.js +209 -221
  8. package/dist/cjs/dance/PoseDetector.js +302 -275
  9. package/dist/cjs/index.js +110 -113
  10. package/dist/cjs/material/MaterialEditor.module.css.js +242 -5
  11. package/dist/cjs/material/NiceMaterialEditor.js +1213 -757
  12. package/dist/cjs/material/materialEditorTypes.js +35 -49
  13. package/dist/cjs/material/materialEditorUtils.js +684 -747
  14. package/dist/cjs/material/materialNodeDefinitions.js +1411 -1459
  15. package/dist/cjs/model/ModelEditor.js +90 -40
  16. package/dist/cjs/model/ModelEditor.module.css.js +257 -5
  17. package/dist/cjs/model/ModelEditorLeftPanel.js +126 -47
  18. package/dist/cjs/model/ModelEditorMenuBar.js +359 -32
  19. package/dist/cjs/model/ModelEditorRightPanel.js +647 -131
  20. package/dist/cjs/model/ModelEditorSubComponents.js +277 -112
  21. package/dist/cjs/model/ModelEditorTimeline.js +232 -34
  22. package/dist/cjs/model/ModelEditorToolbar.js +170 -14
  23. package/dist/cjs/model/ModelEditorViewport.js +231 -39
  24. package/dist/cjs/model/ModelViewer.js +388 -63
  25. package/dist/cjs/model/ModelViewer.module.css.js +77 -5
  26. package/dist/cjs/model/NiceArmatureEditor.js +473 -274
  27. package/dist/cjs/model/NiceMorphTargetEditor.js +333 -227
  28. package/dist/cjs/model/NiceOctree.js +372 -320
  29. package/dist/cjs/model/NicePhysicsSimulation.js +528 -298
  30. package/dist/cjs/model/NiceProceduralGeometry.js +673 -300
  31. package/dist/cjs/model/NiceTerrainEditor.js +595 -385
  32. package/dist/cjs/model/NiceWeightPainter.js +384 -261
  33. package/dist/cjs/model/NiceXRPreview.js +385 -308
  34. package/dist/cjs/model/cadModeUtils.js +103 -106
  35. package/dist/cjs/model/editorShortcuts.js +171 -194
  36. package/dist/cjs/model/modelEditorTypes.js +100 -118
  37. package/dist/cjs/model/modelEditorUtils.js +1061 -1146
  38. package/dist/cjs/model/simsModeUtils.js +1023 -1053
  39. package/dist/cjs/model/useModelEditor.js +1977 -1933
  40. package/dist/cjs/model/useModelViewer.js +633 -665
  41. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  42. package/dist/cjs/particle/NiceParticleEditor.js +1186 -571
  43. package/dist/cjs/particle/ParticleEditor.module.css.js +122 -5
  44. package/dist/cjs/particle/particleEditorTypes.js +62 -70
  45. package/dist/cjs/particle/particleEditorUtils.js +998 -1013
  46. package/dist/cjs/rendering/NiceCascadedShadows.js +381 -259
  47. package/dist/cjs/rendering/NiceRenderExport.js +424 -337
  48. package/dist/cjs/rendering/NiceSSAO.js +454 -337
  49. package/dist/cjs/rendering/NiceSSR.js +387 -261
  50. package/dist/cjs/rendering/NiceWebGPURenderer.js +359 -230
  51. package/dist/cjs/uv/NiceUVEditor.js +787 -550
  52. package/dist/cjs/uv/UVEditor.module.css.js +53 -5
  53. package/dist/cjs/uv/uvEditorTypes.js +59 -76
  54. package/dist/cjs/uv/uvEditorUtils.js +501 -575
  55. package/dist/esm/collaborative/collaborativeScene.js +163 -205
  56. package/dist/esm/core/editorAsk.js +53 -40
  57. package/dist/esm/core/i18n.js +15 -13
  58. package/dist/esm/core/threeCompat.js +18 -26
  59. package/dist/esm/dance/DanceBridge.js +151 -176
  60. package/dist/esm/dance/DanceScoreEngine.js +211 -220
  61. package/dist/esm/dance/PoseDetector.js +304 -274
  62. package/dist/esm/index.js +258 -36
  63. package/dist/esm/material/MaterialEditor.module.css.js +242 -3
  64. package/dist/esm/material/NiceMaterialEditor.js +1215 -756
  65. package/dist/esm/material/materialEditorTypes.js +45 -48
  66. package/dist/esm/material/materialEditorUtils.js +713 -746
  67. package/dist/esm/material/materialNodeDefinitions.js +1415 -1458
  68. package/dist/esm/model/ModelEditor.js +91 -39
  69. package/dist/esm/model/ModelEditor.module.css.js +257 -3
  70. package/dist/esm/model/ModelEditorLeftPanel.js +126 -45
  71. package/dist/esm/model/ModelEditorMenuBar.js +359 -30
  72. package/dist/esm/model/ModelEditorRightPanel.js +634 -114
  73. package/dist/esm/model/ModelEditorSubComponents.js +265 -94
  74. package/dist/esm/model/ModelEditorTimeline.js +232 -32
  75. package/dist/esm/model/ModelEditorToolbar.js +170 -12
  76. package/dist/esm/model/ModelEditorViewport.js +231 -37
  77. package/dist/esm/model/ModelViewer.js +389 -62
  78. package/dist/esm/model/ModelViewer.module.css.js +77 -3
  79. package/dist/esm/model/NiceArmatureEditor.js +462 -258
  80. package/dist/esm/model/NiceMorphTargetEditor.js +322 -211
  81. package/dist/esm/model/NiceOctree.js +361 -304
  82. package/dist/esm/model/NicePhysicsSimulation.js +517 -282
  83. package/dist/esm/model/NiceProceduralGeometry.js +667 -284
  84. package/dist/esm/model/NiceTerrainEditor.js +584 -369
  85. package/dist/esm/model/NiceWeightPainter.js +373 -245
  86. package/dist/esm/model/NiceXRPreview.js +374 -292
  87. package/dist/esm/model/cadModeUtils.js +95 -88
  88. package/dist/esm/model/editorShortcuts.js +172 -193
  89. package/dist/esm/model/modelEditorTypes.js +90 -100
  90. package/dist/esm/model/modelEditorUtils.js +1056 -1128
  91. package/dist/esm/model/simsModeUtils.js +1021 -1035
  92. package/dist/esm/model/useModelEditor.js +1946 -1920
  93. package/dist/esm/model/useModelViewer.js +619 -647
  94. package/dist/esm/nice2dev-ui-3d.css +1 -1
  95. package/dist/esm/particle/NiceParticleEditor.js +1188 -570
  96. package/dist/esm/particle/ParticleEditor.module.css.js +122 -3
  97. package/dist/esm/particle/particleEditorTypes.js +69 -69
  98. package/dist/esm/particle/particleEditorUtils.js +1027 -1012
  99. package/dist/esm/rendering/NiceCascadedShadows.js +370 -243
  100. package/dist/esm/rendering/NiceRenderExport.js +413 -321
  101. package/dist/esm/rendering/NiceSSAO.js +450 -328
  102. package/dist/esm/rendering/NiceSSR.js +383 -252
  103. package/dist/esm/rendering/NiceWebGPURenderer.js +326 -214
  104. package/dist/esm/uv/NiceUVEditor.js +788 -549
  105. package/dist/esm/uv/UVEditor.module.css.js +53 -3
  106. package/dist/esm/uv/uvEditorTypes.js +68 -75
  107. package/dist/esm/uv/uvEditorUtils.js +515 -557
  108. package/package.json +7 -12
@@ -1,851 +1,788 @@
1
- 'use strict';
2
-
3
- var materialNodeDefinitions = require('./materialNodeDefinitions.js');
4
-
5
- /* ════════════════════════════════════════════════════════════════════════════
6
- Material Editor Utilities
7
-
8
- Graph operations, shader compilation, and helper functions:
9
- - Graph manipulation (add/remove/connect nodes)
10
- - Topological sorting for compilation order
11
- - GLSL shader generation
12
- - Import/export functionality
13
- - Validation
14
- ════════════════════════════════════════════════════════════════════════════ */
15
- /* ═══════════════════════════════════════════════════════════════════════════
16
- ID Generation
17
- ═══════════════════════════════════════════════════════════════════════════ */
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const materialNodeDefinitions = require("./materialNodeDefinitions.js");
18
4
  let nodeIdCounter = 0;
19
5
  let connectionIdCounter = 0;
20
6
  function generateNodeId() {
21
- return `node_${Date.now()}_${++nodeIdCounter}`;
7
+ return `node_${Date.now()}_${++nodeIdCounter}`;
22
8
  }
23
9
  function generateConnectionId() {
24
- return `conn_${Date.now()}_${++connectionIdCounter}`;
10
+ return `conn_${Date.now()}_${++connectionIdCounter}`;
25
11
  }
26
12
  function resetIdCounters() {
27
- nodeIdCounter = 0;
28
- connectionIdCounter = 0;
29
- }
30
- /* ═══════════════════════════════════════════════════════════════════════════
31
- Graph Creation
32
- ═══════════════════════════════════════════════════════════════════════════ */
33
- function createEmptyGraph(name = 'New Material') {
34
- const outputNode = {
35
- id: generateNodeId(),
36
- type: 'output_pbr',
37
- position: { x: 400, y: 200 },
38
- properties: {},
39
- inputValues: {},
40
- };
41
- return {
42
- id: `graph_${Date.now()}`,
43
- name,
44
- nodes: [outputNode],
45
- connections: [],
46
- outputNodeId: outputNode.id,
47
- metadata: {
48
- createdAt: new Date(),
49
- modifiedAt: new Date(),
50
- blendMode: 'opaque',
51
- cullMode: 'back',
52
- depthTest: true,
53
- depthWrite: true,
54
- },
55
- };
13
+ nodeIdCounter = 0;
14
+ connectionIdCounter = 0;
15
+ }
16
+ function createEmptyGraph(name = "New Material") {
17
+ const outputNode = {
18
+ id: generateNodeId(),
19
+ type: "output_pbr",
20
+ position: { x: 400, y: 200 },
21
+ properties: {},
22
+ inputValues: {}
23
+ };
24
+ return {
25
+ id: `graph_${Date.now()}`,
26
+ name,
27
+ nodes: [outputNode],
28
+ connections: [],
29
+ outputNodeId: outputNode.id,
30
+ metadata: {
31
+ createdAt: /* @__PURE__ */ new Date(),
32
+ modifiedAt: /* @__PURE__ */ new Date(),
33
+ blendMode: "opaque",
34
+ cullMode: "back",
35
+ depthTest: true,
36
+ depthWrite: true
37
+ }
38
+ };
56
39
  }
57
- /* ═══════════════════════════════════════════════════════════════════════════
58
- Node Operations
59
- ═══════════════════════════════════════════════════════════════════════════ */
60
40
  function createNode(type, position, definitions = materialNodeDefinitions.MATERIAL_NODE_MAP) {
61
- var _a;
62
- const definition = definitions.get(type);
63
- if (!definition) {
64
- return null;
65
- }
66
- const properties = {};
67
- const inputValues = {};
68
- // Initialize properties with defaults
69
- (_a = definition.properties) === null || _a === void 0 ? void 0 : _a.forEach((prop) => {
70
- properties[prop.id] = prop.default;
71
- });
72
- // Initialize input values with defaults
73
- definition.inputs.forEach((input) => {
74
- if (input.defaultValue !== undefined) {
75
- inputValues[input.id] = input.defaultValue;
76
- }
77
- });
78
- return {
79
- id: generateNodeId(),
80
- type,
81
- position: { ...position },
82
- properties,
83
- inputValues,
84
- };
41
+ const definition = definitions.get(type);
42
+ if (!definition) {
43
+ return null;
44
+ }
45
+ const properties = {};
46
+ const inputValues = {};
47
+ definition.properties?.forEach((prop) => {
48
+ properties[prop.id] = prop.default;
49
+ });
50
+ definition.inputs.forEach((input) => {
51
+ if (input.defaultValue !== void 0) {
52
+ inputValues[input.id] = input.defaultValue;
53
+ }
54
+ });
55
+ return {
56
+ id: generateNodeId(),
57
+ type,
58
+ position: { ...position },
59
+ properties,
60
+ inputValues
61
+ };
85
62
  }
86
63
  function cloneNode(node) {
87
- return {
88
- ...node,
89
- id: generateNodeId(),
90
- position: { x: node.position.x + 20, y: node.position.y + 20 },
91
- properties: { ...node.properties },
92
- inputValues: { ...node.inputValues },
93
- selected: false,
94
- };
64
+ return {
65
+ ...node,
66
+ id: generateNodeId(),
67
+ position: { x: node.position.x + 20, y: node.position.y + 20 },
68
+ properties: { ...node.properties },
69
+ inputValues: { ...node.inputValues },
70
+ selected: false
71
+ };
95
72
  }
96
73
  function getNodeDefinition(node, definitions = materialNodeDefinitions.MATERIAL_NODE_MAP) {
97
- return definitions.get(node.type);
74
+ return definitions.get(node.type);
98
75
  }
99
76
  function getNodeBounds(node, definition) {
100
- const nodeWidth = 180;
101
- const headerHeight = 32;
102
- const portHeight = 24;
103
- const maxPorts = Math.max(definition.inputs.length, definition.outputs.length);
104
- const nodeHeight = headerHeight + maxPorts * portHeight + 8;
105
- return {
106
- x: node.position.x,
107
- y: node.position.y,
108
- width: nodeWidth,
109
- height: nodeHeight,
110
- };
77
+ const nodeWidth = 180;
78
+ const headerHeight = 32;
79
+ const portHeight = 24;
80
+ const maxPorts = Math.max(definition.inputs.length, definition.outputs.length);
81
+ const nodeHeight = headerHeight + maxPorts * portHeight + 8;
82
+ return {
83
+ x: node.position.x,
84
+ y: node.position.y,
85
+ width: nodeWidth,
86
+ height: nodeHeight
87
+ };
111
88
  }
112
- /* ═══════════════════════════════════════════════════════════════════════════
113
- Connection Operations
114
- ═══════════════════════════════════════════════════════════════════════════ */
115
89
  function createConnection(sourceNodeId, sourcePortId, targetNodeId, targetPortId) {
116
- return {
117
- id: generateConnectionId(),
118
- sourceNodeId,
119
- sourcePortId,
120
- targetNodeId,
121
- targetPortId,
122
- };
90
+ return {
91
+ id: generateConnectionId(),
92
+ sourceNodeId,
93
+ sourcePortId,
94
+ targetNodeId,
95
+ targetPortId
96
+ };
123
97
  }
124
98
  function canConnect(sourceNode, sourcePort, targetNode, targetPort, existingConnections) {
125
- // Can't connect to self
126
- if (sourceNode.id === targetNode.id) {
127
- return { valid: false, reason: 'Cannot connect node to itself' };
128
- }
129
- // Check port directions
130
- if (sourcePort.direction !== 'output' || targetPort.direction !== 'input') {
131
- return { valid: false, reason: 'Invalid port direction' };
132
- }
133
- // Check if target port already has a connection
134
- const existingConnection = existingConnections.find((conn) => conn.targetNodeId === targetNode.id && conn.targetPortId === targetPort.id);
135
- if (existingConnection) {
136
- return { valid: false, reason: 'Port already connected' };
137
- }
138
- // Check type compatibility
139
- if (!areTypesCompatible(sourcePort.dataType, targetPort.dataType)) {
140
- return {
141
- valid: false,
142
- reason: `Incompatible types: ${sourcePort.dataType} → ${targetPort.dataType}`,
143
- };
144
- }
145
- return { valid: true };
99
+ if (sourceNode.id === targetNode.id) {
100
+ return { valid: false, reason: "Cannot connect node to itself" };
101
+ }
102
+ if (sourcePort.direction !== "output" || targetPort.direction !== "input") {
103
+ return { valid: false, reason: "Invalid port direction" };
104
+ }
105
+ const existingConnection = existingConnections.find(
106
+ (conn) => conn.targetNodeId === targetNode.id && conn.targetPortId === targetPort.id
107
+ );
108
+ if (existingConnection) {
109
+ return { valid: false, reason: "Port already connected" };
110
+ }
111
+ if (!areTypesCompatible(sourcePort.dataType, targetPort.dataType)) {
112
+ return {
113
+ valid: false,
114
+ reason: `Incompatible types: ${sourcePort.dataType} → ${targetPort.dataType}`
115
+ };
116
+ }
117
+ return { valid: true };
146
118
  }
147
119
  function areTypesCompatible(sourceType, targetType) {
148
- // 'any' type is compatible with everything
149
- if (sourceType === 'any' || targetType === 'any') {
150
- return true;
151
- }
152
- // Same types are compatible
153
- if (sourceType === targetType) {
154
- return true;
155
- }
156
- // Float can be used where vec2/vec3/vec4 is expected (broadcast)
157
- if (sourceType === 'float' && ['vec2', 'vec3', 'vec4'].includes(targetType)) {
158
- return true;
159
- }
160
- // Color is compatible with vec3/vec4
161
- if (sourceType === 'color' && ['vec3', 'vec4'].includes(targetType)) {
162
- return true;
163
- }
164
- if (sourceType === 'vec3' && targetType === 'color') {
165
- return true;
166
- }
167
- return false;
120
+ if (sourceType === "any" || targetType === "any") {
121
+ return true;
122
+ }
123
+ if (sourceType === targetType) {
124
+ return true;
125
+ }
126
+ if (sourceType === "float" && ["vec2", "vec3", "vec4"].includes(targetType)) {
127
+ return true;
128
+ }
129
+ if (sourceType === "color" && ["vec3", "vec4"].includes(targetType)) {
130
+ return true;
131
+ }
132
+ if (sourceType === "vec3" && targetType === "color") {
133
+ return true;
134
+ }
135
+ return false;
168
136
  }
169
137
  function getPortConnections(nodeId, portId, direction, connections) {
170
- if (direction === 'input') {
171
- return connections.filter((conn) => conn.targetNodeId === nodeId && conn.targetPortId === portId);
172
- }
173
- return connections.filter((conn) => conn.sourceNodeId === nodeId && conn.sourcePortId === portId);
138
+ if (direction === "input") {
139
+ return connections.filter(
140
+ (conn) => conn.targetNodeId === nodeId && conn.targetPortId === portId
141
+ );
142
+ }
143
+ return connections.filter((conn) => conn.sourceNodeId === nodeId && conn.sourcePortId === portId);
174
144
  }
175
145
  function getNodeConnections(nodeId, connections) {
176
- return connections.filter((conn) => conn.sourceNodeId === nodeId || conn.targetNodeId === nodeId);
146
+ return connections.filter((conn) => conn.sourceNodeId === nodeId || conn.targetNodeId === nodeId);
177
147
  }
178
- /* ═══════════════════════════════════════════════════════════════════════════
179
- Graph Operations
180
- ═══════════════════════════════════════════════════════════════════════════ */
181
148
  function addNodeToGraph(graph, node) {
182
- return {
183
- ...graph,
184
- nodes: [...graph.nodes, node],
185
- metadata: { ...graph.metadata, modifiedAt: new Date() },
186
- };
149
+ return {
150
+ ...graph,
151
+ nodes: [...graph.nodes, node],
152
+ metadata: { ...graph.metadata, modifiedAt: /* @__PURE__ */ new Date() }
153
+ };
187
154
  }
188
155
  function removeNodesFromGraph(graph, nodeIds) {
189
- const nodeIdSet = new Set(nodeIds);
190
- // Don't remove the output node
191
- if (nodeIdSet.has(graph.outputNodeId)) {
192
- nodeIdSet.delete(graph.outputNodeId);
193
- }
194
- return {
195
- ...graph,
196
- nodes: graph.nodes.filter((node) => !nodeIdSet.has(node.id)),
197
- connections: graph.connections.filter((conn) => !nodeIdSet.has(conn.sourceNodeId) && !nodeIdSet.has(conn.targetNodeId)),
198
- metadata: { ...graph.metadata, modifiedAt: new Date() },
199
- };
156
+ const nodeIdSet = new Set(nodeIds);
157
+ if (nodeIdSet.has(graph.outputNodeId)) {
158
+ nodeIdSet.delete(graph.outputNodeId);
159
+ }
160
+ return {
161
+ ...graph,
162
+ nodes: graph.nodes.filter((node) => !nodeIdSet.has(node.id)),
163
+ connections: graph.connections.filter(
164
+ (conn) => !nodeIdSet.has(conn.sourceNodeId) && !nodeIdSet.has(conn.targetNodeId)
165
+ ),
166
+ metadata: { ...graph.metadata, modifiedAt: /* @__PURE__ */ new Date() }
167
+ };
200
168
  }
201
169
  function addConnectionToGraph(graph, connection) {
202
- // Remove existing connection to target port
203
- const filteredConnections = graph.connections.filter((conn) => !(conn.targetNodeId === connection.targetNodeId &&
204
- conn.targetPortId === connection.targetPortId));
205
- return {
206
- ...graph,
207
- connections: [...filteredConnections, connection],
208
- metadata: { ...graph.metadata, modifiedAt: new Date() },
209
- };
170
+ const filteredConnections = graph.connections.filter(
171
+ (conn) => !(conn.targetNodeId === connection.targetNodeId && conn.targetPortId === connection.targetPortId)
172
+ );
173
+ return {
174
+ ...graph,
175
+ connections: [...filteredConnections, connection],
176
+ metadata: { ...graph.metadata, modifiedAt: /* @__PURE__ */ new Date() }
177
+ };
210
178
  }
211
179
  function removeConnectionFromGraph(graph, connectionId) {
212
- return {
213
- ...graph,
214
- connections: graph.connections.filter((conn) => conn.id !== connectionId),
215
- metadata: { ...graph.metadata, modifiedAt: new Date() },
216
- };
180
+ return {
181
+ ...graph,
182
+ connections: graph.connections.filter((conn) => conn.id !== connectionId),
183
+ metadata: { ...graph.metadata, modifiedAt: /* @__PURE__ */ new Date() }
184
+ };
217
185
  }
218
186
  function updateNodeInGraph(graph, nodeId, updates) {
219
- return {
220
- ...graph,
221
- nodes: graph.nodes.map((node) => (node.id === nodeId ? { ...node, ...updates } : node)),
222
- metadata: { ...graph.metadata, modifiedAt: new Date() },
223
- };
187
+ return {
188
+ ...graph,
189
+ nodes: graph.nodes.map((node) => node.id === nodeId ? { ...node, ...updates } : node),
190
+ metadata: { ...graph.metadata, modifiedAt: /* @__PURE__ */ new Date() }
191
+ };
224
192
  }
225
193
  function moveNodesInGraph(graph, nodeIds, delta) {
226
- const nodeIdSet = new Set(nodeIds);
227
- return {
228
- ...graph,
229
- nodes: graph.nodes.map((node) => nodeIdSet.has(node.id)
230
- ? { ...node, position: { x: node.position.x + delta.x, y: node.position.y + delta.y } }
231
- : node),
232
- };
194
+ const nodeIdSet = new Set(nodeIds);
195
+ return {
196
+ ...graph,
197
+ nodes: graph.nodes.map(
198
+ (node) => nodeIdSet.has(node.id) ? { ...node, position: { x: node.position.x + delta.x, y: node.position.y + delta.y } } : node
199
+ )
200
+ };
233
201
  }
234
- /* ═══════════════════════════════════════════════════════════════════════════
235
- Topological Sort
236
- ═══════════════════════════════════════════════════════════════════════════ */
237
202
  function topologicalSort(graph) {
238
- const sorted = [];
239
- const visited = new Set();
240
- const visiting = new Set();
241
- const nodeMap = new Map(graph.nodes.map((n) => [n.id, n]));
242
- function visit(nodeId) {
243
- if (visited.has(nodeId)) {
244
- return true;
245
- }
246
- if (visiting.has(nodeId)) {
247
- return false;
248
- } // Cycle detected
249
- visiting.add(nodeId);
250
- // Find all nodes that this node depends on (inputs)
251
- const inputConnections = graph.connections.filter((conn) => conn.targetNodeId === nodeId);
252
- for (const conn of inputConnections) {
253
- if (!visit(conn.sourceNodeId)) {
254
- return false; // Cycle
255
- }
256
- }
257
- visiting.delete(nodeId);
258
- visited.add(nodeId);
259
- const node = nodeMap.get(nodeId);
260
- if (node) {
261
- sorted.push(node);
262
- }
263
- return true;
203
+ const sorted = [];
204
+ const visited = /* @__PURE__ */ new Set();
205
+ const visiting = /* @__PURE__ */ new Set();
206
+ const nodeMap = new Map(graph.nodes.map((n) => [n.id, n]));
207
+ function visit(nodeId) {
208
+ if (visited.has(nodeId)) {
209
+ return true;
210
+ }
211
+ if (visiting.has(nodeId)) {
212
+ return false;
213
+ }
214
+ visiting.add(nodeId);
215
+ const inputConnections = graph.connections.filter((conn) => conn.targetNodeId === nodeId);
216
+ for (const conn of inputConnections) {
217
+ if (!visit(conn.sourceNodeId)) {
218
+ return false;
219
+ }
220
+ }
221
+ visiting.delete(nodeId);
222
+ visited.add(nodeId);
223
+ const node = nodeMap.get(nodeId);
224
+ if (node) {
225
+ sorted.push(node);
264
226
  }
265
- // Start from output node and work backwards
266
- visit(graph.outputNodeId);
267
- // Visit any unvisited nodes (disconnected)
268
- for (const node of graph.nodes) {
269
- if (!visited.has(node.id)) {
270
- visit(node.id);
271
- }
227
+ return true;
228
+ }
229
+ visit(graph.outputNodeId);
230
+ for (const node of graph.nodes) {
231
+ if (!visited.has(node.id)) {
232
+ visit(node.id);
272
233
  }
273
- return sorted;
234
+ }
235
+ return sorted;
274
236
  }
275
- /* ═══════════════════════════════════════════════════════════════════════════
276
- Shader Compilation
277
- ═══════════════════════════════════════════════════════════════════════════ */
278
237
  function compileShader(graph, definitions = materialNodeDefinitions.MATERIAL_NODE_MAP) {
279
- const errors = [];
280
- const warnings = [];
281
- const uniforms = [];
282
- const fragmentLines = [];
283
- // Validate graph
284
- const validationResult = validateGraph(graph, definitions);
285
- errors.push(...validationResult.errors);
286
- warnings.push(...validationResult.warnings);
287
- if (errors.length > 0) {
288
- return {
289
- vertexShader: generateDefaultVertexShader(),
290
- fragmentShader: generateErrorFragmentShader(errors),
291
- uniforms,
292
- attributes: [],
293
- defines: {},
294
- errors,
295
- warnings,
296
- };
297
- }
298
- // Sort nodes topologically
299
- const sortedNodes = topologicalSort(graph);
300
- // Generate code for each node
301
- const nodeOutputs = new Map();
302
- for (const node of sortedNodes) {
303
- const definition = definitions.get(node.type);
304
- if (!definition) {
305
- continue;
306
- }
307
- const nodeCode = generateNodeCode(node, definition, graph, nodeOutputs);
308
- if (nodeCode) {
309
- fragmentLines.push(` // ${definition.name}`);
310
- fragmentLines.push(nodeCode);
311
- fragmentLines.push('');
312
- }
313
- }
314
- // Generate final fragment shader
315
- const fragmentShader = generateFragmentShader(graph, fragmentLines, definitions);
238
+ const errors = [];
239
+ const warnings = [];
240
+ const uniforms = [];
241
+ const fragmentLines = [];
242
+ const validationResult = validateGraph(graph, definitions);
243
+ errors.push(...validationResult.errors);
244
+ warnings.push(...validationResult.warnings);
245
+ if (errors.length > 0) {
316
246
  return {
317
- vertexShader: generateDefaultVertexShader(),
318
- fragmentShader,
319
- uniforms,
320
- attributes: [
321
- { name: 'position', type: 'vec3', location: 0 },
322
- { name: 'normal', type: 'vec3', location: 1 },
323
- { name: 'uv', type: 'vec2', location: 2 },
324
- ],
325
- defines: {},
326
- errors,
327
- warnings,
247
+ vertexShader: generateDefaultVertexShader(),
248
+ fragmentShader: generateErrorFragmentShader(errors),
249
+ uniforms,
250
+ attributes: [],
251
+ defines: {},
252
+ errors,
253
+ warnings
328
254
  };
255
+ }
256
+ const sortedNodes = topologicalSort(graph);
257
+ const nodeOutputs = /* @__PURE__ */ new Map();
258
+ for (const node of sortedNodes) {
259
+ const definition = definitions.get(node.type);
260
+ if (!definition) {
261
+ continue;
262
+ }
263
+ const nodeCode = generateNodeCode(node, definition, graph, nodeOutputs);
264
+ if (nodeCode) {
265
+ fragmentLines.push(` // ${definition.name}`);
266
+ fragmentLines.push(nodeCode);
267
+ fragmentLines.push("");
268
+ }
269
+ }
270
+ const fragmentShader = generateFragmentShader(graph, fragmentLines, definitions);
271
+ return {
272
+ vertexShader: generateDefaultVertexShader(),
273
+ fragmentShader,
274
+ uniforms,
275
+ attributes: [
276
+ { name: "position", type: "vec3", location: 0 },
277
+ { name: "normal", type: "vec3", location: 1 },
278
+ { name: "uv", type: "vec2", location: 2 }
279
+ ],
280
+ defines: {},
281
+ errors,
282
+ warnings
283
+ };
329
284
  }
330
285
  function generateNodeCode(node, definition, graph, nodeOutputs, uniforms) {
331
- var _a;
332
- const outputs = new Map();
333
- const lines = [];
334
- // Get input values (either from connections or defaults)
335
- const inputValues = {};
336
- for (const input of definition.inputs) {
337
- const connection = graph.connections.find((conn) => conn.targetNodeId === node.id && conn.targetPortId === input.id);
338
- if (connection) {
339
- const sourceOutputs = nodeOutputs.get(connection.sourceNodeId);
340
- inputValues[input.id] = (sourceOutputs === null || sourceOutputs === void 0 ? void 0 : sourceOutputs.get(connection.sourcePortId)) || '0.0';
341
- }
342
- else {
343
- inputValues[input.id] = formatDefaultValue((_a = node.inputValues[input.id]) !== null && _a !== void 0 ? _a : input.defaultValue, input.dataType);
344
- }
345
- }
346
- // Generate output variable names
347
- for (const output of definition.outputs) {
348
- const varName = `${node.id}_${output.id}`;
349
- outputs.set(output.id, varName);
350
- }
351
- // Store outputs for downstream nodes
352
- nodeOutputs.set(node.id, outputs);
353
- // Generate GLSL code based on node type
354
- const code = generateGLSLForNode(node, definition, inputValues);
355
- if (code) {
356
- lines.push(code);
357
- }
358
- return lines.join('\n');
286
+ const outputs = /* @__PURE__ */ new Map();
287
+ const lines = [];
288
+ const inputValues = {};
289
+ for (const input of definition.inputs) {
290
+ const connection = graph.connections.find(
291
+ (conn) => conn.targetNodeId === node.id && conn.targetPortId === input.id
292
+ );
293
+ if (connection) {
294
+ const sourceOutputs = nodeOutputs.get(connection.sourceNodeId);
295
+ inputValues[input.id] = sourceOutputs?.get(connection.sourcePortId) || "0.0";
296
+ } else {
297
+ inputValues[input.id] = formatDefaultValue(
298
+ node.inputValues[input.id] ?? input.defaultValue,
299
+ input.dataType
300
+ );
301
+ }
302
+ }
303
+ for (const output of definition.outputs) {
304
+ const varName = `${node.id}_${output.id}`;
305
+ outputs.set(output.id, varName);
306
+ }
307
+ nodeOutputs.set(node.id, outputs);
308
+ const code = generateGLSLForNode(node, definition, inputValues);
309
+ if (code) {
310
+ lines.push(code);
311
+ }
312
+ return lines.join("\n");
359
313
  }
360
314
  function generateGLSLForNode(node, definition, inputs, _outputs, _uniforms) {
361
- const nodeId = node.id.replace(/-/g, '_');
362
- switch (definition.type) {
363
- // Input nodes
364
- case 'constant_float': {
365
- // `properties` is Record<string, unknown> — narrow before use rather than
366
- // assuming the shape a given node type happens to carry.
367
- const value = node.properties.value;
368
- return ` float ${nodeId}_value = ${typeof value === 'number' ? value.toFixed(4) : '0.0'};`;
369
- }
370
- case 'constant_color': {
371
- const rawColor = node.properties.color;
372
- const color = parseColor(typeof rawColor === 'string' ? rawColor : 'var(--nice-bg, #fff)');
373
- return ` vec3 ${nodeId}_rgb = vec3(${color.r.toFixed(4)}, ${color.g.toFixed(4)}, ${color.b.toFixed(4)});
374
- vec4 ${nodeId}_rgba = vec4(${nodeId}_rgb, 1.0);
375
- float ${nodeId}_r = ${nodeId}_rgb.r;
376
- float ${nodeId}_g = ${nodeId}_rgb.g;
377
- float ${nodeId}_b = ${nodeId}_rgb.b;
315
+ const nodeId = node.id.replace(/-/g, "_");
316
+ switch (definition.type) {
317
+ // Input nodes
318
+ case "constant_float": {
319
+ const value = node.properties.value;
320
+ return ` float ${nodeId}_value = ${typeof value === "number" ? value.toFixed(4) : "0.0"};`;
321
+ }
322
+ case "constant_color": {
323
+ const rawColor = node.properties.color;
324
+ const color = parseColor(typeof rawColor === "string" ? rawColor : "var(--nice-bg, #fff)");
325
+ return ` vec3 ${nodeId}_rgb = vec3(${color.r.toFixed(4)}, ${color.g.toFixed(4)}, ${color.b.toFixed(4)});
326
+ vec4 ${nodeId}_rgba = vec4(${nodeId}_rgb, 1.0);
327
+ float ${nodeId}_r = ${nodeId}_rgb.r;
328
+ float ${nodeId}_g = ${nodeId}_rgb.g;
329
+ float ${nodeId}_b = ${nodeId}_rgb.b;
378
330
  float ${nodeId}_a = 1.0;`;
379
- }
380
- case 'uv':
381
- return ` vec2 ${nodeId}_uv = vUv;
382
- float ${nodeId}_u = vUv.x;
331
+ }
332
+ case "uv":
333
+ return ` vec2 ${nodeId}_uv = vUv;
334
+ float ${nodeId}_u = vUv.x;
383
335
  float ${nodeId}_v = vUv.y;`;
384
- case 'time':
385
- return ` float ${nodeId}_time = uTime;
386
- float ${nodeId}_sin = sin(uTime);
387
- float ${nodeId}_cos = cos(uTime);
336
+ case "time":
337
+ return ` float ${nodeId}_time = uTime;
338
+ float ${nodeId}_sin = sin(uTime);
339
+ float ${nodeId}_cos = cos(uTime);
388
340
  float ${nodeId}_delta = uDeltaTime;`;
389
- case 'position':
390
- return ` vec3 ${nodeId}_world = vWorldPosition;
391
- vec3 ${nodeId}_object = vPosition;
341
+ case "position":
342
+ return ` vec3 ${nodeId}_world = vWorldPosition;
343
+ vec3 ${nodeId}_object = vPosition;
392
344
  vec3 ${nodeId}_view = vViewPosition;`;
393
- case 'normal':
394
- return ` vec3 ${nodeId}_world = normalize(vWorldNormal);
395
- vec3 ${nodeId}_object = normalize(vNormal);
396
- vec3 ${nodeId}_view = normalize(vViewNormal);
397
- vec3 ${nodeId}_tangent = normalize(vTangent);
345
+ case "normal":
346
+ return ` vec3 ${nodeId}_world = normalize(vWorldNormal);
347
+ vec3 ${nodeId}_object = normalize(vNormal);
348
+ vec3 ${nodeId}_view = normalize(vViewNormal);
349
+ vec3 ${nodeId}_tangent = normalize(vTangent);
398
350
  vec3 ${nodeId}_bitangent = normalize(vBitangent);`;
399
- // Math nodes
400
- case 'add':
401
- return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = ${inputs.a} + ${inputs.b};`;
402
- case 'subtract':
403
- return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = ${inputs.a} - ${inputs.b};`;
404
- case 'multiply':
405
- return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = ${inputs.a} * ${inputs.b};`;
406
- case 'divide':
407
- return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = ${inputs.a} / max(${inputs.b}, 0.0001);`;
408
- case 'sin':
409
- return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = sin(${inputs.value});`;
410
- case 'cos':
411
- return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = cos(${inputs.value});`;
412
- case 'abs':
413
- return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = abs(${inputs.value});`;
414
- case 'floor':
415
- return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = floor(${inputs.value});`;
416
- case 'fract':
417
- return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = fract(${inputs.value});`;
418
- case 'min':
419
- return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = min(${inputs.a}, ${inputs.b});`;
420
- case 'max':
421
- return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = max(${inputs.a}, ${inputs.b});`;
422
- // Vector nodes
423
- case 'combine_vec2':
424
- return ` vec2 ${nodeId}_vector = vec2(${inputs.x}, ${inputs.y});`;
425
- case 'combine_vec3':
426
- return ` vec3 ${nodeId}_vector = vec3(${inputs.x}, ${inputs.y}, ${inputs.z});`;
427
- case 'combine_vec4':
428
- return ` vec4 ${nodeId}_vector = vec4(${inputs.x}, ${inputs.y}, ${inputs.z}, ${inputs.w});`;
429
- case 'split_vec3':
430
- return ` float ${nodeId}_x = ${inputs.vector}.x;
431
- float ${nodeId}_y = ${inputs.vector}.y;
351
+ // Math nodes
352
+ case "add":
353
+ return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = ${inputs.a} + ${inputs.b};`;
354
+ case "subtract":
355
+ return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = ${inputs.a} - ${inputs.b};`;
356
+ case "multiply":
357
+ return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = ${inputs.a} * ${inputs.b};`;
358
+ case "divide":
359
+ return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = ${inputs.a} / max(${inputs.b}, 0.0001);`;
360
+ case "sin":
361
+ return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = sin(${inputs.value});`;
362
+ case "cos":
363
+ return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = cos(${inputs.value});`;
364
+ case "abs":
365
+ return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = abs(${inputs.value});`;
366
+ case "floor":
367
+ return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = floor(${inputs.value});`;
368
+ case "fract":
369
+ return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = fract(${inputs.value});`;
370
+ case "min":
371
+ return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = min(${inputs.a}, ${inputs.b});`;
372
+ case "max":
373
+ return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = max(${inputs.a}, ${inputs.b});`;
374
+ // Vector nodes
375
+ case "combine_vec2":
376
+ return ` vec2 ${nodeId}_vector = vec2(${inputs.x}, ${inputs.y});`;
377
+ case "combine_vec3":
378
+ return ` vec3 ${nodeId}_vector = vec3(${inputs.x}, ${inputs.y}, ${inputs.z});`;
379
+ case "combine_vec4":
380
+ return ` vec4 ${nodeId}_vector = vec4(${inputs.x}, ${inputs.y}, ${inputs.z}, ${inputs.w});`;
381
+ case "split_vec3":
382
+ return ` float ${nodeId}_x = ${inputs.vector}.x;
383
+ float ${nodeId}_y = ${inputs.vector}.y;
432
384
  float ${nodeId}_z = ${inputs.vector}.z;`;
433
- case 'normalize':
434
- return ` vec3 ${nodeId}_result = normalize(${inputs.vector});`;
435
- case 'dot':
436
- return ` float ${nodeId}_result = dot(${inputs.a}, ${inputs.b});`;
437
- case 'cross':
438
- return ` vec3 ${nodeId}_result = cross(${inputs.a}, ${inputs.b});`;
439
- // Utility nodes
440
- case 'lerp':
441
- return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = mix(${inputs.a}, ${inputs.b}, ${inputs.t});`;
442
- case 'clamp':
443
- return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = clamp(${inputs.value}, ${inputs.min}, ${inputs.max});`;
444
- case 'saturate':
445
- return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = clamp(${inputs.value}, 0.0, 1.0);`;
446
- case 'smoothstep':
447
- return ` float ${nodeId}_result = smoothstep(${inputs.edge0}, ${inputs.edge1}, ${inputs.x});`;
448
- case 'one_minus':
449
- return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = 1.0 - ${inputs.value};`;
450
- case 'fresnel':
451
- return ` float ${nodeId}_result = pow(1.0 - max(dot(${inputs.normal}, ${inputs.viewDir}), 0.0), ${inputs.power});`;
452
- // Procedural nodes
453
- case 'noise_perlin':
454
- return ` float ${nodeId}_value = perlinNoise(${inputs.uv} * ${inputs.scale});`;
455
- case 'checker':
456
- return ` float ${nodeId}_value = mod(floor(${inputs.uv}.x * ${inputs.scale}) + floor(${inputs.uv}.y * ${inputs.scale}), 2.0);
385
+ case "normalize":
386
+ return ` vec3 ${nodeId}_result = normalize(${inputs.vector});`;
387
+ case "dot":
388
+ return ` float ${nodeId}_result = dot(${inputs.a}, ${inputs.b});`;
389
+ case "cross":
390
+ return ` vec3 ${nodeId}_result = cross(${inputs.a}, ${inputs.b});`;
391
+ // Utility nodes
392
+ case "lerp":
393
+ return ` ${getTypeForAny(inputs.a)} ${nodeId}_result = mix(${inputs.a}, ${inputs.b}, ${inputs.t});`;
394
+ case "clamp":
395
+ return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = clamp(${inputs.value}, ${inputs.min}, ${inputs.max});`;
396
+ case "saturate":
397
+ return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = clamp(${inputs.value}, 0.0, 1.0);`;
398
+ case "smoothstep":
399
+ return ` float ${nodeId}_result = smoothstep(${inputs.edge0}, ${inputs.edge1}, ${inputs.x});`;
400
+ case "one_minus":
401
+ return ` ${getTypeForAny(inputs.value)} ${nodeId}_result = 1.0 - ${inputs.value};`;
402
+ case "fresnel":
403
+ return ` float ${nodeId}_result = pow(1.0 - max(dot(${inputs.normal}, ${inputs.viewDir}), 0.0), ${inputs.power});`;
404
+ // Procedural nodes
405
+ case "noise_perlin":
406
+ return ` float ${nodeId}_value = perlinNoise(${inputs.uv} * ${inputs.scale});`;
407
+ case "checker":
408
+ return ` float ${nodeId}_value = mod(floor(${inputs.uv}.x * ${inputs.scale}) + floor(${inputs.uv}.y * ${inputs.scale}), 2.0);
457
409
  vec3 ${nodeId}_color = mix(${inputs.colorA}, ${inputs.colorB}, ${nodeId}_value);`;
458
- case 'gradient_linear': {
459
- const dir = node.properties.direction || 'horizontal';
460
- const gradientCoord = dir === 'horizontal' ? 'x' : dir === 'vertical' ? 'y' : '(x + y) * 0.5';
461
- return ` float ${nodeId}_value = ${inputs.uv}.${gradientCoord === '(x + y) * 0.5' ? gradientCoord : gradientCoord};`;
462
- }
463
- case 'gradient_radial':
464
- return ` float ${nodeId}_value = 1.0 - clamp(distance(${inputs.uv}, ${inputs.center}) / ${inputs.radius}, 0.0, 1.0);`;
465
- // Color nodes
466
- case 'invert':
467
- return ` vec3 ${nodeId}_result = vec3(1.0) - ${inputs.color};`;
468
- case 'grayscale':
469
- return ` float ${nodeId}_gray = dot(${inputs.color}, vec3(0.299, 0.587, 0.114));
410
+ case "gradient_linear": {
411
+ const dir = node.properties.direction || "horizontal";
412
+ const gradientCoord = dir === "horizontal" ? "x" : dir === "vertical" ? "y" : "(x + y) * 0.5";
413
+ return ` float ${nodeId}_value = ${inputs.uv}.${gradientCoord === "(x + y) * 0.5" ? gradientCoord : gradientCoord};`;
414
+ }
415
+ case "gradient_radial":
416
+ return ` float ${nodeId}_value = 1.0 - clamp(distance(${inputs.uv}, ${inputs.center}) / ${inputs.radius}, 0.0, 1.0);`;
417
+ // Color nodes
418
+ case "invert":
419
+ return ` vec3 ${nodeId}_result = vec3(1.0) - ${inputs.color};`;
420
+ case "grayscale":
421
+ return ` float ${nodeId}_gray = dot(${inputs.color}, vec3(0.299, 0.587, 0.114));
470
422
  vec3 ${nodeId}_rgb = vec3(${nodeId}_gray);`;
471
- case 'blend': {
472
- const rawMode = node.properties.mode;
473
- const mode = typeof rawMode === 'string' ? rawMode : 'normal';
474
- const blendCode = generateBlendMode(mode, inputs.base, inputs.blend);
475
- return ` vec3 ${nodeId}_result = mix(${inputs.base}, ${blendCode}, ${inputs.factor});`;
476
- }
477
- default:
478
- return ` // Unsupported node type: ${definition.type}`;
423
+ case "blend": {
424
+ const rawMode = node.properties.mode;
425
+ const mode = typeof rawMode === "string" ? rawMode : "normal";
426
+ const blendCode = generateBlendMode(mode, inputs.base, inputs.blend);
427
+ return ` vec3 ${nodeId}_result = mix(${inputs.base}, ${blendCode}, ${inputs.factor});`;
479
428
  }
429
+ default:
430
+ return ` // Unsupported node type: ${definition.type}`;
431
+ }
480
432
  }
481
433
  function generateBlendMode(mode, base, blend) {
482
- switch (mode) {
483
- case 'multiply':
484
- return `${base} * ${blend}`;
485
- case 'screen':
486
- return `1.0 - (1.0 - ${base}) * (1.0 - ${blend})`;
487
- case 'overlay':
488
- return `mix(2.0 * ${base} * ${blend}, 1.0 - 2.0 * (1.0 - ${base}) * (1.0 - ${blend}), step(0.5, ${base}))`;
489
- case 'add':
490
- return `min(${base} + ${blend}, vec3(1.0))`;
491
- case 'subtract':
492
- return `max(${base} - ${blend}, vec3(0.0))`;
493
- case 'difference':
494
- return `abs(${base} - ${blend})`;
495
- default:
496
- return blend;
497
- }
434
+ switch (mode) {
435
+ case "multiply":
436
+ return `${base} * ${blend}`;
437
+ case "screen":
438
+ return `1.0 - (1.0 - ${base}) * (1.0 - ${blend})`;
439
+ case "overlay":
440
+ return `mix(2.0 * ${base} * ${blend}, 1.0 - 2.0 * (1.0 - ${base}) * (1.0 - ${blend}), step(0.5, ${base}))`;
441
+ case "add":
442
+ return `min(${base} + ${blend}, vec3(1.0))`;
443
+ case "subtract":
444
+ return `max(${base} - ${blend}, vec3(0.0))`;
445
+ case "difference":
446
+ return `abs(${base} - ${blend})`;
447
+ default:
448
+ return blend;
449
+ }
498
450
  }
499
451
  function getTypeForAny(value) {
500
- if (value.startsWith('vec4')) {
501
- return 'vec4';
502
- }
503
- if (value.startsWith('vec3')) {
504
- return 'vec3';
505
- }
506
- if (value.startsWith('vec2')) {
507
- return 'vec2';
508
- }
509
- return 'float';
452
+ if (value.startsWith("vec4")) {
453
+ return "vec4";
454
+ }
455
+ if (value.startsWith("vec3")) {
456
+ return "vec3";
457
+ }
458
+ if (value.startsWith("vec2")) {
459
+ return "vec2";
460
+ }
461
+ return "float";
510
462
  }
511
463
  function formatDefaultValue(value, dataType) {
512
- if (value === undefined || value === null) {
513
- switch (dataType) {
514
- case 'float':
515
- return '0.0';
516
- case 'vec2':
517
- return 'vec2(0.0)';
518
- case 'vec3':
519
- return 'vec3(0.0)';
520
- case 'vec4':
521
- return 'vec4(0.0)';
522
- case 'bool':
523
- return 'false';
524
- case 'int':
525
- return '0';
526
- default:
527
- return '0.0';
528
- }
529
- }
530
- if (Array.isArray(value)) {
531
- if (value.length === 2) {
532
- return `vec2(${value[0].toFixed(4)}, ${value[1].toFixed(4)})`;
533
- }
534
- if (value.length === 3) {
535
- return `vec3(${value[0].toFixed(4)}, ${value[1].toFixed(4)}, ${value[2].toFixed(4)})`;
536
- }
537
- if (value.length === 4) {
538
- return `vec4(${value[0].toFixed(4)}, ${value[1].toFixed(4)}, ${value[2].toFixed(4)}, ${value[3].toFixed(4)})`;
539
- }
540
- }
541
- if (typeof value === 'number') {
542
- return value.toFixed(4);
543
- }
544
- if (typeof value === 'boolean') {
545
- return value ? 'true' : 'false';
546
- }
547
- return String(value);
464
+ if (value === void 0 || value === null) {
465
+ switch (dataType) {
466
+ case "float":
467
+ return "0.0";
468
+ case "vec2":
469
+ return "vec2(0.0)";
470
+ case "vec3":
471
+ return "vec3(0.0)";
472
+ case "vec4":
473
+ return "vec4(0.0)";
474
+ case "bool":
475
+ return "false";
476
+ case "int":
477
+ return "0";
478
+ default:
479
+ return "0.0";
480
+ }
481
+ }
482
+ if (Array.isArray(value)) {
483
+ if (value.length === 2) {
484
+ return `vec2(${value[0].toFixed(4)}, ${value[1].toFixed(4)})`;
485
+ }
486
+ if (value.length === 3) {
487
+ return `vec3(${value[0].toFixed(4)}, ${value[1].toFixed(4)}, ${value[2].toFixed(4)})`;
488
+ }
489
+ if (value.length === 4) {
490
+ return `vec4(${value[0].toFixed(4)}, ${value[1].toFixed(4)}, ${value[2].toFixed(4)}, ${value[3].toFixed(4)})`;
491
+ }
492
+ }
493
+ if (typeof value === "number") {
494
+ return value.toFixed(4);
495
+ }
496
+ if (typeof value === "boolean") {
497
+ return value ? "true" : "false";
498
+ }
499
+ return String(value);
548
500
  }
549
501
  function parseColor(hex) {
550
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
551
- return result
552
- ? {
553
- r: parseInt(result[1], 16) / 255,
554
- g: parseInt(result[2], 16) / 255,
555
- b: parseInt(result[3], 16) / 255,
556
- }
557
- : { r: 1, g: 1, b: 1 };
502
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
503
+ return result ? {
504
+ r: parseInt(result[1], 16) / 255,
505
+ g: parseInt(result[2], 16) / 255,
506
+ b: parseInt(result[3], 16) / 255
507
+ } : { r: 1, g: 1, b: 1 };
558
508
  }
559
509
  function generateDefaultVertexShader() {
560
- return `#version 300 es
561
- precision highp float;
562
-
563
- in vec3 position;
564
- in vec3 normal;
565
- in vec2 uv;
566
- in vec3 tangent;
567
-
568
- uniform mat4 modelMatrix;
569
- uniform mat4 viewMatrix;
570
- uniform mat4 projectionMatrix;
571
- uniform mat3 normalMatrix;
572
-
573
- out vec3 vPosition;
574
- out vec3 vNormal;
575
- out vec2 vUv;
576
- out vec3 vWorldPosition;
577
- out vec3 vWorldNormal;
578
- out vec3 vViewPosition;
579
- out vec3 vViewNormal;
580
- out vec3 vTangent;
581
- out vec3 vBitangent;
582
-
583
- void main() {
584
- vPosition = position;
585
- vNormal = normal;
586
- vUv = uv;
587
-
588
- vec4 worldPosition = modelMatrix * vec4(position, 1.0);
589
- vWorldPosition = worldPosition.xyz;
590
- vWorldNormal = normalMatrix * normal;
591
-
592
- vec4 viewPosition = viewMatrix * worldPosition;
593
- vViewPosition = viewPosition.xyz;
594
- vViewNormal = mat3(viewMatrix) * vWorldNormal;
595
-
596
- vTangent = normalMatrix * tangent;
597
- vBitangent = cross(vWorldNormal, vTangent);
598
-
599
- gl_Position = projectionMatrix * viewPosition;
510
+ return `#version 300 es
511
+ precision highp float;
512
+
513
+ in vec3 position;
514
+ in vec3 normal;
515
+ in vec2 uv;
516
+ in vec3 tangent;
517
+
518
+ uniform mat4 modelMatrix;
519
+ uniform mat4 viewMatrix;
520
+ uniform mat4 projectionMatrix;
521
+ uniform mat3 normalMatrix;
522
+
523
+ out vec3 vPosition;
524
+ out vec3 vNormal;
525
+ out vec2 vUv;
526
+ out vec3 vWorldPosition;
527
+ out vec3 vWorldNormal;
528
+ out vec3 vViewPosition;
529
+ out vec3 vViewNormal;
530
+ out vec3 vTangent;
531
+ out vec3 vBitangent;
532
+
533
+ void main() {
534
+ vPosition = position;
535
+ vNormal = normal;
536
+ vUv = uv;
537
+
538
+ vec4 worldPosition = modelMatrix * vec4(position, 1.0);
539
+ vWorldPosition = worldPosition.xyz;
540
+ vWorldNormal = normalMatrix * normal;
541
+
542
+ vec4 viewPosition = viewMatrix * worldPosition;
543
+ vViewPosition = viewPosition.xyz;
544
+ vViewNormal = mat3(viewMatrix) * vWorldNormal;
545
+
546
+ vTangent = normalMatrix * tangent;
547
+ vBitangent = cross(vWorldNormal, vTangent);
548
+
549
+ gl_Position = projectionMatrix * viewPosition;
600
550
  }`;
601
551
  }
602
552
  function generateFragmentShader(graph, nodeCode, definitions) {
603
- const outputNode = graph.nodes.find((n) => n.id === graph.outputNodeId);
604
- outputNode ? definitions.get(outputNode.type) : null;
605
- return `#version 300 es
606
- precision highp float;
607
-
608
- in vec3 vPosition;
609
- in vec3 vNormal;
610
- in vec2 vUv;
611
- in vec3 vWorldPosition;
612
- in vec3 vWorldNormal;
613
- in vec3 vViewPosition;
614
- in vec3 vViewNormal;
615
- in vec3 vTangent;
616
- in vec3 vBitangent;
617
-
618
- uniform float uTime;
619
- uniform float uDeltaTime;
620
- uniform vec3 uCameraPosition;
621
- uniform vec2 uResolution;
622
-
623
- out vec4 fragColor;
624
-
625
- // Noise functions
626
- float hash(vec2 p) {
627
- return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
628
- }
629
-
630
- float perlinNoise(vec2 p) {
631
- vec2 i = floor(p);
632
- vec2 f = fract(p);
633
- f = f * f * (3.0 - 2.0 * f);
634
-
635
- float a = hash(i);
636
- float b = hash(i + vec2(1.0, 0.0));
637
- float c = hash(i + vec2(0.0, 1.0));
638
- float d = hash(i + vec2(1.0, 1.0));
639
-
640
- return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
641
- }
642
-
643
- void main() {
644
- ${nodeCode.join('\n')}
645
-
646
- // Output
647
- ${generateOutputAssignment(graph)}
553
+ const outputNode = graph.nodes.find((n) => n.id === graph.outputNodeId);
554
+ outputNode ? definitions.get(outputNode.type) : null;
555
+ return `#version 300 es
556
+ precision highp float;
557
+
558
+ in vec3 vPosition;
559
+ in vec3 vNormal;
560
+ in vec2 vUv;
561
+ in vec3 vWorldPosition;
562
+ in vec3 vWorldNormal;
563
+ in vec3 vViewPosition;
564
+ in vec3 vViewNormal;
565
+ in vec3 vTangent;
566
+ in vec3 vBitangent;
567
+
568
+ uniform float uTime;
569
+ uniform float uDeltaTime;
570
+ uniform vec3 uCameraPosition;
571
+ uniform vec2 uResolution;
572
+
573
+ out vec4 fragColor;
574
+
575
+ // Noise functions
576
+ float hash(vec2 p) {
577
+ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
578
+ }
579
+
580
+ float perlinNoise(vec2 p) {
581
+ vec2 i = floor(p);
582
+ vec2 f = fract(p);
583
+ f = f * f * (3.0 - 2.0 * f);
584
+
585
+ float a = hash(i);
586
+ float b = hash(i + vec2(1.0, 0.0));
587
+ float c = hash(i + vec2(0.0, 1.0));
588
+ float d = hash(i + vec2(1.0, 1.0));
589
+
590
+ return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
591
+ }
592
+
593
+ void main() {
594
+ ${nodeCode.join("\n")}
595
+
596
+ // Output
597
+ ${generateOutputAssignment(graph)}
648
598
  }`;
649
599
  }
650
600
  function generateOutputAssignment(graph, _definitions) {
651
- const outputNode = graph.nodes.find((n) => n.id === graph.outputNodeId);
652
- if (!outputNode) {
653
- return 'fragColor = vec4(1.0, 0.0, 1.0, 1.0); // Error: no output node';
654
- }
655
- outputNode.id.replace(/-/g, '_');
656
- // Find what's connected to the output
657
- const albedoConn = graph.connections.find((c) => c.targetNodeId === outputNode.id && c.targetPortId === 'albedo');
658
- const colorConn = graph.connections.find((c) => c.targetNodeId === outputNode.id && c.targetPortId === 'color');
659
- if (outputNode.type === 'output_pbr') {
660
- const albedo = albedoConn
661
- ? `${albedoConn.sourceNodeId.replace(/-/g, '_')}_${albedoConn.sourcePortId}`
662
- : 'vec3(1.0)';
663
- return `fragColor = vec4(${albedo}, 1.0);`;
664
- }
665
- if (outputNode.type === 'output_unlit') {
666
- const color = colorConn
667
- ? `${colorConn.sourceNodeId.replace(/-/g, '_')}_${colorConn.sourcePortId}`
668
- : 'vec3(1.0)';
669
- return `fragColor = vec4(${color}, 1.0);`;
670
- }
671
- return 'fragColor = vec4(1.0);';
601
+ const outputNode = graph.nodes.find((n) => n.id === graph.outputNodeId);
602
+ if (!outputNode) {
603
+ return "fragColor = vec4(1.0, 0.0, 1.0, 1.0); // Error: no output node";
604
+ }
605
+ outputNode.id.replace(/-/g, "_");
606
+ const albedoConn = graph.connections.find(
607
+ (c) => c.targetNodeId === outputNode.id && c.targetPortId === "albedo"
608
+ );
609
+ const colorConn = graph.connections.find(
610
+ (c) => c.targetNodeId === outputNode.id && c.targetPortId === "color"
611
+ );
612
+ if (outputNode.type === "output_pbr") {
613
+ const albedo = albedoConn ? `${albedoConn.sourceNodeId.replace(/-/g, "_")}_${albedoConn.sourcePortId}` : "vec3(1.0)";
614
+ return `fragColor = vec4(${albedo}, 1.0);`;
615
+ }
616
+ if (outputNode.type === "output_unlit") {
617
+ const color = colorConn ? `${colorConn.sourceNodeId.replace(/-/g, "_")}_${colorConn.sourcePortId}` : "vec3(1.0)";
618
+ return `fragColor = vec4(${color}, 1.0);`;
619
+ }
620
+ return "fragColor = vec4(1.0);";
672
621
  }
673
622
  function generateErrorFragmentShader(errors) {
674
- return `#version 300 es
675
- precision highp float;
676
-
677
- out vec4 fragColor;
678
-
679
- void main() {
680
- // Compilation errors: ${errors.length}
681
- // ${errors.map((e) => e.message).join('\n // ')}
682
- fragColor = vec4(1.0, 0.0, 1.0, 1.0);
623
+ return `#version 300 es
624
+ precision highp float;
625
+
626
+ out vec4 fragColor;
627
+
628
+ void main() {
629
+ // Compilation errors: ${errors.length}
630
+ // ${errors.map((e) => e.message).join("\n // ")}
631
+ fragColor = vec4(1.0, 0.0, 1.0, 1.0);
683
632
  }`;
684
633
  }
685
634
  function validateGraph(graph, definitions = materialNodeDefinitions.MATERIAL_NODE_MAP) {
686
- const errors = [];
687
- const warnings = [];
688
- // Check for output node
689
- if (!graph.outputNodeId) {
690
- errors.push({ nodeId: '', message: 'No output node defined' });
691
- }
692
- const outputNode = graph.nodes.find((n) => n.id === graph.outputNodeId);
693
- if (!outputNode) {
694
- errors.push({ nodeId: graph.outputNodeId, message: 'Output node not found in graph' });
695
- }
696
- // Validate each node
697
- for (const node of graph.nodes) {
698
- const definition = definitions.get(node.type);
699
- if (!definition) {
700
- errors.push({ nodeId: node.id, message: `Unknown node type: ${node.type}` });
701
- continue;
702
- }
703
- // Check required inputs
704
- for (const input of definition.inputs) {
705
- const hasConnection = graph.connections.some((conn) => conn.targetNodeId === node.id && conn.targetPortId === input.id);
706
- const hasDefault = input.defaultValue !== undefined || node.inputValues[input.id] !== undefined;
707
- if (!hasConnection && !hasDefault) {
708
- warnings.push({ nodeId: node.id, message: `Input '${input.name}' is not connected` });
709
- }
710
- }
711
- }
712
- // Check for cycles
713
- const hasCycle = detectCycle(graph);
714
- if (hasCycle) {
715
- errors.push({ nodeId: '', message: 'Graph contains a cycle' });
716
- }
717
- // Check connections
718
- for (const conn of graph.connections) {
719
- const sourceNode = graph.nodes.find((n) => n.id === conn.sourceNodeId);
720
- const targetNode = graph.nodes.find((n) => n.id === conn.targetNodeId);
721
- if (!sourceNode) {
722
- errors.push({ nodeId: conn.sourceNodeId, message: 'Source node not found' });
723
- }
724
- if (!targetNode) {
725
- errors.push({ nodeId: conn.targetNodeId, message: 'Target node not found' });
726
- }
635
+ const errors = [];
636
+ const warnings = [];
637
+ if (!graph.outputNodeId) {
638
+ errors.push({ nodeId: "", message: "No output node defined" });
639
+ }
640
+ const outputNode = graph.nodes.find((n) => n.id === graph.outputNodeId);
641
+ if (!outputNode) {
642
+ errors.push({ nodeId: graph.outputNodeId, message: "Output node not found in graph" });
643
+ }
644
+ for (const node of graph.nodes) {
645
+ const definition = definitions.get(node.type);
646
+ if (!definition) {
647
+ errors.push({ nodeId: node.id, message: `Unknown node type: ${node.type}` });
648
+ continue;
727
649
  }
728
- return {
729
- valid: errors.length === 0,
730
- errors,
731
- warnings,
732
- };
650
+ for (const input of definition.inputs) {
651
+ const hasConnection = graph.connections.some(
652
+ (conn) => conn.targetNodeId === node.id && conn.targetPortId === input.id
653
+ );
654
+ const hasDefault = input.defaultValue !== void 0 || node.inputValues[input.id] !== void 0;
655
+ if (!hasConnection && !hasDefault) {
656
+ warnings.push({ nodeId: node.id, message: `Input '${input.name}' is not connected` });
657
+ }
658
+ }
659
+ }
660
+ const hasCycle = detectCycle(graph);
661
+ if (hasCycle) {
662
+ errors.push({ nodeId: "", message: "Graph contains a cycle" });
663
+ }
664
+ for (const conn of graph.connections) {
665
+ const sourceNode = graph.nodes.find((n) => n.id === conn.sourceNodeId);
666
+ const targetNode = graph.nodes.find((n) => n.id === conn.targetNodeId);
667
+ if (!sourceNode) {
668
+ errors.push({ nodeId: conn.sourceNodeId, message: "Source node not found" });
669
+ }
670
+ if (!targetNode) {
671
+ errors.push({ nodeId: conn.targetNodeId, message: "Target node not found" });
672
+ }
673
+ }
674
+ return {
675
+ valid: errors.length === 0,
676
+ errors,
677
+ warnings
678
+ };
733
679
  }
734
680
  function detectCycle(graph) {
735
- const visited = new Set();
736
- const visiting = new Set();
737
- function dfs(nodeId) {
738
- if (visiting.has(nodeId)) {
739
- return true;
740
- }
741
- if (visited.has(nodeId)) {
742
- return false;
743
- }
744
- visiting.add(nodeId);
745
- const outgoingConnections = graph.connections.filter((conn) => conn.sourceNodeId === nodeId);
746
- for (const conn of outgoingConnections) {
747
- if (dfs(conn.targetNodeId)) {
748
- return true;
749
- }
750
- }
751
- visiting.delete(nodeId);
752
- visited.add(nodeId);
753
- return false;
754
- }
755
- for (const node of graph.nodes) {
756
- if (dfs(node.id)) {
757
- return true;
758
- }
681
+ const visited = /* @__PURE__ */ new Set();
682
+ const visiting = /* @__PURE__ */ new Set();
683
+ function dfs(nodeId) {
684
+ if (visiting.has(nodeId)) {
685
+ return true;
686
+ }
687
+ if (visited.has(nodeId)) {
688
+ return false;
689
+ }
690
+ visiting.add(nodeId);
691
+ const outgoingConnections = graph.connections.filter((conn) => conn.sourceNodeId === nodeId);
692
+ for (const conn of outgoingConnections) {
693
+ if (dfs(conn.targetNodeId)) {
694
+ return true;
695
+ }
759
696
  }
697
+ visiting.delete(nodeId);
698
+ visited.add(nodeId);
760
699
  return false;
700
+ }
701
+ for (const node of graph.nodes) {
702
+ if (dfs(node.id)) {
703
+ return true;
704
+ }
705
+ }
706
+ return false;
761
707
  }
762
- /* ═══════════════════════════════════════════════════════════════════════════
763
- View Utilities
764
- ═══════════════════════════════════════════════════════════════════════════ */
765
708
  function screenToGraph(screenPoint, viewTransform) {
766
- return {
767
- x: (screenPoint.x - viewTransform.x) / viewTransform.zoom,
768
- y: (screenPoint.y - viewTransform.y) / viewTransform.zoom,
769
- };
709
+ return {
710
+ x: (screenPoint.x - viewTransform.x) / viewTransform.zoom,
711
+ y: (screenPoint.y - viewTransform.y) / viewTransform.zoom
712
+ };
770
713
  }
771
714
  function graphToScreen(graphPoint, viewTransform) {
772
- return {
773
- x: graphPoint.x * viewTransform.zoom + viewTransform.x,
774
- y: graphPoint.y * viewTransform.zoom + viewTransform.y,
775
- };
715
+ return {
716
+ x: graphPoint.x * viewTransform.zoom + viewTransform.x,
717
+ y: graphPoint.y * viewTransform.zoom + viewTransform.y
718
+ };
776
719
  }
777
720
  function calculateBoundingBox(nodes) {
778
- if (nodes.length === 0) {
779
- return null;
780
- }
781
- let minX = Infinity, minY = Infinity;
782
- let maxX = -Infinity, maxY = -Infinity;
783
- for (const node of nodes) {
784
- minX = Math.min(minX, node.position.x);
785
- minY = Math.min(minY, node.position.y);
786
- maxX = Math.max(maxX, node.position.x + 180);
787
- maxY = Math.max(maxY, node.position.y + 120);
788
- }
789
- return {
790
- x: minX,
791
- y: minY,
792
- width: maxX - minX,
793
- height: maxY - minY,
794
- };
721
+ if (nodes.length === 0) {
722
+ return null;
723
+ }
724
+ let minX = Infinity, minY = Infinity;
725
+ let maxX = -Infinity, maxY = -Infinity;
726
+ for (const node of nodes) {
727
+ minX = Math.min(minX, node.position.x);
728
+ minY = Math.min(minY, node.position.y);
729
+ maxX = Math.max(maxX, node.position.x + 180);
730
+ maxY = Math.max(maxY, node.position.y + 120);
731
+ }
732
+ return {
733
+ x: minX,
734
+ y: minY,
735
+ width: maxX - minX,
736
+ height: maxY - minY
737
+ };
795
738
  }
796
739
  function calculateZoomToFit(nodes, viewportWidth, viewportHeight, padding = 50) {
797
- const bbox = calculateBoundingBox(nodes);
798
- if (!bbox) {
799
- return { x: 0, y: 0, zoom: 1 };
800
- }
801
- const scaleX = (viewportWidth - padding * 2) / bbox.width;
802
- const scaleY = (viewportHeight - padding * 2) / bbox.height;
803
- const zoom = Math.min(scaleX, scaleY, 1);
804
- const centerX = bbox.x + bbox.width / 2;
805
- const centerY = bbox.y + bbox.height / 2;
806
- return {
807
- x: viewportWidth / 2 - centerX * zoom,
808
- y: viewportHeight / 2 - centerY * zoom,
809
- zoom,
810
- };
740
+ const bbox = calculateBoundingBox(nodes);
741
+ if (!bbox) {
742
+ return { x: 0, y: 0, zoom: 1 };
743
+ }
744
+ const scaleX = (viewportWidth - padding * 2) / bbox.width;
745
+ const scaleY = (viewportHeight - padding * 2) / bbox.height;
746
+ const zoom = Math.min(scaleX, scaleY, 1);
747
+ const centerX = bbox.x + bbox.width / 2;
748
+ const centerY = bbox.y + bbox.height / 2;
749
+ return {
750
+ x: viewportWidth / 2 - centerX * zoom,
751
+ y: viewportHeight / 2 - centerY * zoom,
752
+ zoom
753
+ };
811
754
  }
812
- /* ═══════════════════════════════════════════════════════════════════════════
813
- Import/Export
814
- ═══════════════════════════════════════════════════════════════════════════ */
815
755
  function exportGraphToJSON(graph) {
816
- return JSON.stringify(graph, null, 2);
756
+ return JSON.stringify(graph, null, 2);
817
757
  }
818
758
  function importGraphFromJSON(json) {
819
- try {
820
- const parsed = JSON.parse(json);
821
- // Basic validation
822
- if (!parsed.id || !parsed.nodes || !parsed.connections) {
823
- return null;
824
- }
825
- return parsed;
826
- }
827
- catch (_a) {
828
- return null;
759
+ try {
760
+ const parsed = JSON.parse(json);
761
+ if (!parsed.id || !parsed.nodes || !parsed.connections) {
762
+ return null;
829
763
  }
764
+ return parsed;
765
+ } catch {
766
+ return null;
767
+ }
830
768
  }
831
769
  function exportToThreeJS(shader) {
832
- return `import * as THREE from 'three';
833
-
834
- const material = new THREE.ShaderMaterial({
835
- uniforms: {
836
- uTime: { value: 0 },
837
- uDeltaTime: { value: 0 },
838
- uCameraPosition: { value: new THREE.Vector3() },
839
- uResolution: { value: new THREE.Vector2() },
840
- ${shader.uniforms.map((u) => ` ${u.name}: { value: ${JSON.stringify(u.value)} },`).join('\n')}
841
- },
842
- vertexShader: \`${shader.vertexShader}\`,
843
- fragmentShader: \`${shader.fragmentShader}\`,
844
- });
845
-
770
+ return `import * as THREE from 'three';
771
+
772
+ const material = new THREE.ShaderMaterial({
773
+ uniforms: {
774
+ uTime: { value: 0 },
775
+ uDeltaTime: { value: 0 },
776
+ uCameraPosition: { value: new THREE.Vector3() },
777
+ uResolution: { value: new THREE.Vector2() },
778
+ ${shader.uniforms.map((u) => ` ${u.name}: { value: ${JSON.stringify(u.value)} },`).join("\n")}
779
+ },
780
+ vertexShader: \`${shader.vertexShader}\`,
781
+ fragmentShader: \`${shader.fragmentShader}\`,
782
+ });
783
+
846
784
  export default material;`;
847
785
  }
848
-
849
786
  exports.addConnectionToGraph = addConnectionToGraph;
850
787
  exports.addNodeToGraph = addNodeToGraph;
851
788
  exports.areTypesCompatible = areTypesCompatible;