@fusefactory/fuse-three-forcegraph 1.0.1

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.
package/README.md ADDED
@@ -0,0 +1,397 @@
1
+ # Fuse Three Force Graph
2
+
3
+ A high-performance GPU-accelerated force-directed graph visualization library built with Three.js. Features a modular pass-based architecture for flexible and extensible force simulations.
4
+
5
+ ## Features
6
+
7
+ - **GPU-Accelerated**: All force calculations run on the GPU using WebGL compute shaders
8
+ - **Modular Pass Architecture**: Flexible system for composing and customizing force behaviors
9
+ - **Ping-Pong Rendering**: Efficient double-buffering for position and velocity updates
10
+ - **Interactive Controls**: Built-in camera controls, node dragging, and hover interactions
11
+ - **Extensible**: Easy to add custom force passes and visual effects
12
+
13
+ ## Architecture
14
+
15
+ ### Core Components
16
+
17
+ #### Engine (`core/Engine.ts`)
18
+ The main orchestrator that owns and coordinates all components:
19
+ - Manages shared GPU buffers (SimulationBuffers, StaticAssets, PickBuffer)
20
+ - Coordinates GraphStore, GraphScene, and ForceSimulation
21
+ - Handles the render loop and user interactions
22
+
23
+ #### SimulationBuffers (`textures/SimulationBuffers.ts`)
24
+ Manages dynamic render targets updated by force simulation:
25
+ - Position buffers (current, previous, original) for ping-pong rendering
26
+ - Velocity buffers for force accumulation
27
+ - Automatically sizes textures based on node count
28
+
29
+ #### StaticAssets (`textures/StaticAssets.ts`)
30
+ Manages read-only GPU textures:
31
+ - Node radii and colors
32
+ - Link indices and properties
33
+ - Created once at initialization, updated only on mode changes
34
+
35
+ #### GraphScene (`rendering/GraphScene.ts`)
36
+ Manages the 3D scene and visual rendering:
37
+ - Node and link renderers
38
+ - Camera controls
39
+ - Visual mode application
40
+
41
+ ### Force Simulation
42
+
43
+ The simulation uses a **pass-based architecture** where each force type is implemented as an independent pass:
44
+
45
+ #### BasePass (`simulation/BasePass.ts`)
46
+ Abstract base class for all force passes. Provides:
47
+ - Material management
48
+ - Uniform updates
49
+ - Enable/disable control
50
+ - Render execution
51
+
52
+ #### Built-in Force Passes
53
+
54
+ Located in `simulation/passes/`:
55
+
56
+ 1. **VelocityCarryPass** - Applies damping to previous velocity
57
+ 2. **CollisionPass** - Prevents node overlap
58
+ 3. **ManyBodyPass** - Charge repulsion between all nodes
59
+ 4. **GravityPass** - Pulls nodes toward center
60
+ 5. **LinkPass** - Spring forces between connected nodes
61
+ 6. **EmbeddingsPass** - Elastic pull toward original positions
62
+ 7. **DragPass** - Interactive node dragging
63
+ 8. **IntegratePass** - Updates positions from velocities
64
+
65
+ #### ForceSimulation (`simulation/ForceSimulation.ts`)
66
+ Manages and executes force passes:
67
+ ```typescript
68
+ // Add custom force pass
69
+ simulation.addPass('myForce', new MyCustomPass(config))
70
+
71
+ // Remove a pass
72
+ simulation.removePass('collision')
73
+
74
+ // Enable/disable a pass
75
+ simulation.setPassEnabled('gravity', false)
76
+
77
+ // Get a pass for configuration
78
+ const gravityPass = simulation.getPass('gravity')
79
+ ```
80
+
81
+ ### Execution Pipeline
82
+
83
+ Each simulation step follows this sequence:
84
+
85
+ 1. **Velocity Carry** - Initialize velocity buffer with damped previous velocity
86
+ 2. **Force Accumulation** - Each enabled pass accumulates forces into velocity
87
+ - Read current velocity
88
+ - Compute force contribution
89
+ - Write to previous velocity buffer
90
+ - Swap buffers (ping-pong)
91
+ 3. **Integration** - Update positions using accumulated velocities
92
+ 4. **Alpha Decay** - Reduce simulation heat over time
93
+
94
+ ## Usage
95
+
96
+ ### Basic Setup
97
+
98
+ ```typescript
99
+ import { Engine } from './core/Engine'
100
+
101
+ // Create engine with a canvas element
102
+ const engine = new Engine(canvas, {
103
+ width: window.innerWidth,
104
+ height: window.innerHeight,
105
+ backgroundColor: '#000000'
106
+ })
107
+
108
+ // Load graph data
109
+ const graphData = {
110
+ nodes: [
111
+ { id: '1', x: 0, y: 0, z: 0 },
112
+ { id: '2', x: 10, y: 10, z: 0 }
113
+ ],
114
+ links: [
115
+ { source: '1', target: '2' }
116
+ ]
117
+ }
118
+
119
+ engine.setData(graphData)
120
+
121
+ // Start simulation and rendering
122
+ engine.start()
123
+
124
+ GRAPH.styleRegistry.setNodeStyles({
125
+ 'root': { color: 0xE53E3E, size: 55 },
126
+ 'series': { color: 0x38A169, size: 33 },
127
+ 'artwork': { color: 0x3182CE, size: 22 },
128
+ })
129
+
130
+ // Get simulation config - direct access, changes take effect immediately
131
+ const simulation = engine.getSimulation()
132
+ const config = simulation.config
133
+
134
+ // Create Tweakpane
135
+ pane = new Pane()
136
+
137
+ // Bind simulation parameters - changes to config work directly, no sync needed
138
+ const simFolder = pane.addFolder({ title: 'Simulation' })
139
+ simFolder.addBinding(config, 'alpha', { min: 0, max: 1 })
140
+ simFolder.addBinding(config, 'alphaDecay', { min: 0, max: 0.1 })
141
+ simFolder.addBinding(config, 'damping', { min: 0, max: 1 })
142
+
143
+ // Many-body force, check uniforms that can be added in binding...
144
+ const manyBodyFolder = pane.addFolder({ title: 'Many-Body Force' })
145
+ manyBodyFolder.addBinding(config, 'enableManyBody')
146
+ manyBodyFolder.addBinding(config, 'manyBodyStrength', { min: 0, max: 100 })
147
+
148
+
149
+ // Set up attractors - each pulls specific categories
150
+ simulation.setAttractors([
151
+ {
152
+ id: 'center',
153
+ position: { x: 0, y: 0.0, z: 0 },
154
+ categories: ['root'],
155
+ strength: 55.
156
+ },])
157
+
158
+ // Adjust global attractor strength
159
+ simulation.config.attractorStrength = 0.03
160
+
161
+
162
+ ```
163
+
164
+ ### Generating Random Data
165
+
166
+ For testing and prototyping, you can generate random graph data:
167
+
168
+ ```typescript
169
+ // Utility function to create random nodes and links
170
+ const createRandomData = (nodeCount: number = 10, linkCount: number = 15) => {
171
+ const groups = ['root', 'series', 'artwork', 'character', 'location']
172
+
173
+ // Generate random nodes
174
+ const nodes = Array.from({ length: nodeCount }, (_, i) => ({
175
+ id: (i + 1).toString(),
176
+ group: groups[Math.floor(Math.random() * groups.length)],
177
+ x: (Math.random() - 0.5) * 2,
178
+ y: (Math.random() - 0.5) * 2,
179
+ z: (Math.random() - 0.5) * 2
180
+ }))
181
+
182
+ // Generate random links
183
+ const links = Array.from({ length: linkCount }, () => {
184
+ const sourceId = Math.floor(Math.random() * nodeCount) + 1
185
+ let targetId = Math.floor(Math.random() * nodeCount) + 1
186
+
187
+ // Ensure source and target are different
188
+ while (targetId === sourceId) {
189
+ targetId = Math.floor(Math.random() * nodeCount) + 1
190
+ }
191
+
192
+ return {
193
+ source: sourceId.toString(),
194
+ target: targetId.toString()
195
+ }
196
+ })
197
+
198
+ return { nodes, links }
199
+ }
200
+
201
+ // Use random data
202
+ engine.setData(createRandomData(100, 70))
203
+ ```
204
+
205
+ ### Custom Force Pass
206
+
207
+ ```typescript
208
+ import { BasePass, type PassContext } from './simulation/BasePass'
209
+
210
+ class CustomForcePass extends BasePass {
211
+ private strength: number = 1.0
212
+
213
+ getName(): string {
214
+ return 'CustomForce'
215
+ }
216
+
217
+ initMaterial(context: PassContext): void {
218
+ this.material = this.createMaterial(
219
+ vertexShader,
220
+ fragmentShader,
221
+ {
222
+ uPositionsTexture: { value: null },
223
+ uVelocityTexture: { value: null },
224
+ uStrength: { value: this.strength },
225
+ uAlpha: { value: 1.0 }
226
+ }
227
+ )
228
+ }
229
+
230
+ updateUniforms(context: PassContext): void {
231
+ if (!this.material) return
232
+
233
+ this.material.uniforms.uPositionsTexture.value =
234
+ context.simBuffers.getCurrentPositionTexture()
235
+ this.material.uniforms.uVelocityTexture.value =
236
+ context.simBuffers.getCurrentVelocityTexture()
237
+ this.material.uniforms.uAlpha.value = context.alpha
238
+ this.material.uniforms.uStrength.value = this.strength
239
+ }
240
+
241
+ setStrength(strength: number): void {
242
+ this.strength = strength
243
+ }
244
+ }
245
+
246
+ // Add to simulation
247
+ const customPass = new CustomForcePass()
248
+ customPass.initMaterial(context)
249
+ forceSimulation.addPass('custom', customPass, 2) // position 2
250
+ ```
251
+
252
+ ### Configuration
253
+
254
+ ```typescript
255
+ // Update force configuration
256
+ engine.getSimulation().updateConfig({
257
+ manyBodyStrength: 100,
258
+ enableCollision: true,
259
+ collisionRadius: 8.0,
260
+ gravity: 1.2,
261
+ damping: 0.95,
262
+ alpha: 1.0
263
+ })
264
+
265
+ // Interactive node dragging
266
+ engine.getInteractionManager().on('dragStart', ({ nodeId }) => {
267
+ console.log('Dragging node:', nodeId)
268
+ })
269
+
270
+ // Node picking
271
+ const nodeId = engine.pickNode(mouseX, mouseY)
272
+ ```
273
+
274
+ ## Project Structure
275
+
276
+ ```
277
+ fuse-three-forcegraph/
278
+ ├── assets/
279
+ │ └── glsl/ # GLSL shaders for force simulation
280
+ │ ├── force-sim/ # Force compute shaders
281
+ │ ├── lines/ # Link rendering shaders
282
+ │ └── points/ # Node rendering shaders
283
+ ├── audio/ # Audio integration (RNBO)
284
+ ├── controls/ # Input handling and interactions
285
+ │ ├── InteractionManager.ts
286
+ │ ├── InputProcessor.ts
287
+ │ └── handlers/ # Click, drag, hover handlers
288
+ ├── core/ # Core engine components
289
+ │ ├── Engine.ts # Main orchestrator
290
+ │ ├── EventEmitter.ts # Event system
291
+ │ └── GraphStore.ts # Graph data management
292
+ ├── rendering/ # Visual rendering
293
+ │ ├── GraphScene.ts # Scene management
294
+ │ ├── CameraController.ts
295
+ │ ├── nodes/ # Node rendering
296
+ │ └── links/ # Link rendering
297
+ ├── simulation/ # Force simulation
298
+ │ ├── BasePass.ts # Pass base class
299
+ │ ├── ForceSimulation.ts # Pass manager
300
+ │ └── passes/ # Individual force passes
301
+ ├── textures/ # GPU buffer management
302
+ │ ├── SimulationBuffers.ts # Dynamic buffers
303
+ │ ├── StaticAssets.ts # Static textures
304
+ │ └── PickBuffer.ts # GPU picking
305
+ ├── types/ # TypeScript definitions
306
+ └── ui/ # UI components (tooltips, etc.)
307
+ ```
308
+
309
+ ## GPU Texture Layout
310
+
311
+ ### Position/Velocity Buffers
312
+ - Format: `RGBA Float`
313
+ - Layout: Grid where each pixel = one node
314
+ - Channels: `(x, y, z, unused)`
315
+ - Size: Next power-of-2 square ≥ √nodeCount
316
+
317
+ ### Static Assets
318
+ - **Radii**: `Red Float` (1 channel)
319
+ - **Colors**: `RGBA Float` (4 channels)
320
+ - **Link Indices**: `RGBA Float` (source_x, source_y, target_x, target_y)
321
+
322
+ ## Performance Considerations
323
+
324
+ - All position/velocity data stays on GPU
325
+ - Force computations use fragment shaders (parallel)
326
+ - Ping-pong rendering avoids read-after-write hazards
327
+ - Static assets minimize data transfer
328
+ - Geometry uses instancing for efficient rendering
329
+
330
+ ## Interaction Model
331
+
332
+ The library implements a three-tiered interaction system for progressive engagement:
333
+
334
+ ### 1. Hover
335
+ - **Trigger**: Mouse cursor enters node boundary
336
+ - **Purpose**: Lightweight preview and visual feedback
337
+ - **Response**:
338
+ - Node highlight/glow effect
339
+ - Cursor change
340
+ - Optional tooltip display
341
+ - No layout disruption
342
+ - **Use Case**: Quick scanning and exploration
343
+
344
+ ### 2. Pop (Dwell/Long Hover)
345
+ - **Trigger**: Cursor remains over node for defined duration (e.g., 500ms)
346
+ - **Purpose**: Detailed information display without commitment
347
+ - **Response**:
348
+ - Expanded tooltip/info card
349
+ - Highlight connected nodes and edges
350
+ - Subtle camera focus adjustment
351
+ - Audio feedback (optional)
352
+ - **Use Case**: Examining node details and immediate connections
353
+
354
+ ### 3. Click
355
+ - **Trigger**: Primary mouse button click on node
356
+ - **Purpose**: Full interaction and state change
357
+ - **Response**:
358
+ - Node selection/deselection
359
+ - Full graph filtering (show only connected components)
360
+ - Panel/sidebar updates
361
+ - Deep-dive views
362
+ - State persistence
363
+ - **Use Case**: Focused analysis and permanent selection
364
+
365
+ ### Interaction Pipeline
366
+ ```typescript
367
+ // Hover
368
+ interactionManager.on('hover', ({ node, position }) => {
369
+ // Immediate visual feedback
370
+ graphScene.highlightNode(node.id, 'hover')
371
+ })
372
+
373
+ // Pop (triggered after dwell time)
374
+ interactionManager.on('pop', ({ node, dwellTime }) => {
375
+ // Show detailed tooltip
376
+ tooltipManager.showExpanded(node)
377
+ // Highlight neighborhood
378
+ graphScene.highlightNeighborhood(node.id)
379
+ })
380
+
381
+ // Click
382
+ interactionManager.on('click', ({ node }) => {
383
+ // Full selection
384
+ graphStore.selectNode(node.id)
385
+ // Filter graph
386
+ graphScene.filterToConnected(node.id)
387
+ })
388
+ ```
389
+
390
+ This progressive disclosure pattern prevents overwhelming users while enabling deep exploration when needed.
391
+
392
+ ## Dependencies
393
+
394
+ - **three.js** - 3D rendering engine
395
+ - **camera-controls** - Camera manipulation
396
+ - **gsap** - Animation and transitions
397
+