@nice2dev/ui-3d 1.0.0 → 1.0.2

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