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