@nice2dev/ui-3d 1.3.15 → 1.3.16

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