@buley/hexgrid-3d 1.1.2 → 3.0.0
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 +64 -62
- package/package.json +4 -1
- package/site/src/app/docs/page.tsx +158 -34
- package/site/src/app/examples/page.tsx +26 -8
- package/site/src/app/layout.tsx +13 -3
- package/site/src/app/page.tsx +95 -23
- package/src/algorithms/FluidEngineFactory.ts +44 -0
- package/src/algorithms/FluidSimulation3DGPU.ts +92 -0
- package/src/algorithms/FluidSimulationWebNN.ts +153 -0
- package/src/components/HexGrid.tsx +55 -12
- package/src/types/wgsl.d.ts +4 -0
- package/src/webgpu/WebGPUContext.ts +71 -0
- package/src/webgpu/shaders/fluid_sim.wgsl +140 -0
- package/src/webnn/WebNNContext.ts +99 -0
- package/tsconfig.json +3 -2
package/site/src/app/page.tsx
CHANGED
|
@@ -8,10 +8,17 @@ export default function HomePage() {
|
|
|
8
8
|
<h1>HexGrid 3D</h1>
|
|
9
9
|
<p>
|
|
10
10
|
A powerful React component for displaying content in an immersive 3D
|
|
11
|
-
spherical hexagonal grid layout. Perfect for portfolios, galleries,
|
|
12
|
-
interactive visualizations.
|
|
11
|
+
spherical hexagonal grid layout. Perfect for portfolios, galleries,
|
|
12
|
+
and interactive visualizations.
|
|
13
13
|
</p>
|
|
14
|
-
<div
|
|
14
|
+
<div
|
|
15
|
+
style={{
|
|
16
|
+
display: 'flex',
|
|
17
|
+
gap: '1rem',
|
|
18
|
+
flexWrap: 'wrap',
|
|
19
|
+
justifyContent: 'center',
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
15
22
|
<Link href="/docs" className="button">
|
|
16
23
|
Get Started
|
|
17
24
|
</Link>
|
|
@@ -31,24 +38,53 @@ export default function HomePage() {
|
|
|
31
38
|
|
|
32
39
|
{/* Features Section */}
|
|
33
40
|
<section className="section container">
|
|
34
|
-
<h2
|
|
41
|
+
<h2
|
|
42
|
+
style={{
|
|
43
|
+
fontSize: '2.5rem',
|
|
44
|
+
marginBottom: '1rem',
|
|
45
|
+
textAlign: 'center',
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
35
48
|
Powerful Features
|
|
36
49
|
</h2>
|
|
37
|
-
<p
|
|
50
|
+
<p
|
|
51
|
+
style={{
|
|
52
|
+
textAlign: 'center',
|
|
53
|
+
color: '#a0a0a0',
|
|
54
|
+
marginBottom: '3rem',
|
|
55
|
+
fontSize: '1.2rem',
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
38
58
|
Everything you need to create stunning 3D visualizations
|
|
39
59
|
</p>
|
|
40
60
|
<div className="features-grid">
|
|
41
61
|
<div className="feature-card">
|
|
42
|
-
<div
|
|
62
|
+
<div
|
|
63
|
+
style={{
|
|
64
|
+
marginBottom: '1rem',
|
|
65
|
+
color: '#667eea',
|
|
66
|
+
fontSize: '2rem',
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
📐
|
|
70
|
+
</div>
|
|
43
71
|
<h3>3D Hexagonal Grid</h3>
|
|
44
72
|
<p>
|
|
45
|
-
Spherical projection with customizable curvature. Display your
|
|
46
|
-
in an immersive 3D space that adapts to any viewport.
|
|
73
|
+
Spherical projection with customizable curvature. Display your
|
|
74
|
+
content in an immersive 3D space that adapts to any viewport.
|
|
47
75
|
</p>
|
|
48
76
|
</div>
|
|
49
77
|
|
|
50
78
|
<div className="feature-card">
|
|
51
|
-
<div
|
|
79
|
+
<div
|
|
80
|
+
style={{
|
|
81
|
+
marginBottom: '1rem',
|
|
82
|
+
color: '#667eea',
|
|
83
|
+
fontSize: '2rem',
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
📷
|
|
87
|
+
</div>
|
|
52
88
|
<h3>Interactive Camera</h3>
|
|
53
89
|
<p>
|
|
54
90
|
Pan, zoom, and rotate with smooth transitions. Support for mouse,
|
|
@@ -57,34 +93,66 @@ export default function HomePage() {
|
|
|
57
93
|
</div>
|
|
58
94
|
|
|
59
95
|
<div className="feature-card">
|
|
60
|
-
<div
|
|
96
|
+
<div
|
|
97
|
+
style={{
|
|
98
|
+
marginBottom: '1rem',
|
|
99
|
+
color: '#667eea',
|
|
100
|
+
fontSize: '2rem',
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
⚡
|
|
104
|
+
</div>
|
|
61
105
|
<h3>High Performance</h3>
|
|
62
106
|
<p>
|
|
63
|
-
Web Worker rendering for 60fps performance. Automatic texture
|
|
64
|
-
and adaptive quality based on device capabilities.
|
|
107
|
+
Web Worker rendering for 60fps performance. Automatic texture
|
|
108
|
+
caching and adaptive quality based on device capabilities.
|
|
65
109
|
</p>
|
|
66
110
|
</div>
|
|
67
111
|
|
|
68
112
|
<div className="feature-card">
|
|
69
|
-
<div
|
|
113
|
+
<div
|
|
114
|
+
style={{
|
|
115
|
+
marginBottom: '1rem',
|
|
116
|
+
color: '#667eea',
|
|
117
|
+
fontSize: '2rem',
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
🎨
|
|
121
|
+
</div>
|
|
70
122
|
<h3>Dynamic Theming</h3>
|
|
71
123
|
<p>
|
|
72
|
-
Automatic accent color extraction from images. Seamless
|
|
73
|
-
with your app's theme system.
|
|
124
|
+
Automatic accent color extraction from images. Seamless
|
|
125
|
+
integration with your app's theme system.
|
|
74
126
|
</p>
|
|
75
127
|
</div>
|
|
76
128
|
|
|
77
129
|
<div className="feature-card">
|
|
78
|
-
<div
|
|
130
|
+
<div
|
|
131
|
+
style={{
|
|
132
|
+
marginBottom: '1rem',
|
|
133
|
+
color: '#667eea',
|
|
134
|
+
fontSize: '2rem',
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
📱
|
|
138
|
+
</div>
|
|
79
139
|
<h3>Responsive Design</h3>
|
|
80
140
|
<p>
|
|
81
|
-
Fully responsive for mobile and desktop. Touch gestures,
|
|
82
|
-
and adaptive layouts for all screen sizes.
|
|
141
|
+
Fully responsive for mobile and desktop. Touch gestures,
|
|
142
|
+
pinch-to-zoom, and adaptive layouts for all screen sizes.
|
|
83
143
|
</p>
|
|
84
144
|
</div>
|
|
85
145
|
|
|
86
146
|
<div className="feature-card">
|
|
87
|
-
<div
|
|
147
|
+
<div
|
|
148
|
+
style={{
|
|
149
|
+
marginBottom: '1rem',
|
|
150
|
+
color: '#667eea',
|
|
151
|
+
fontSize: '2rem',
|
|
152
|
+
}}
|
|
153
|
+
>
|
|
154
|
+
💻
|
|
155
|
+
</div>
|
|
88
156
|
<h3>TypeScript Ready</h3>
|
|
89
157
|
<p>
|
|
90
158
|
Fully typed with comprehensive TypeScript definitions. Includes
|
|
@@ -96,13 +164,17 @@ export default function HomePage() {
|
|
|
96
164
|
|
|
97
165
|
{/* Quick Start Section */}
|
|
98
166
|
<section className="section container">
|
|
99
|
-
<h2
|
|
167
|
+
<h2
|
|
168
|
+
style={{
|
|
169
|
+
fontSize: '2.5rem',
|
|
170
|
+
marginBottom: '2rem',
|
|
171
|
+
textAlign: 'center',
|
|
172
|
+
}}
|
|
173
|
+
>
|
|
100
174
|
Quick Start
|
|
101
175
|
</h2>
|
|
102
176
|
<div className="code-block">
|
|
103
|
-
<code>
|
|
104
|
-
{`npm install @buley/hexgrid-3d`}
|
|
105
|
-
</code>
|
|
177
|
+
<code>{`npm install @buley/hexgrid-3d`}</code>
|
|
106
178
|
</div>
|
|
107
179
|
<div className="code-block">
|
|
108
180
|
<code>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { StableFluids3D, FluidConfig3D } from './FluidSimulation3D';
|
|
2
|
+
import { FluidSimulationWebNN } from './FluidSimulationWebNN';
|
|
3
|
+
import { FluidSimulation3DGPU } from './FluidSimulation3DGPU';
|
|
4
|
+
|
|
5
|
+
export type FluidEngine = StableFluids3D | FluidSimulationWebNN | FluidSimulation3DGPU;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Factory to select the best available Fluid Engine.
|
|
9
|
+
* Priority:
|
|
10
|
+
* 1. WebNN (NPU)
|
|
11
|
+
* 2. WebGPU (GPU)
|
|
12
|
+
* 3. CPU (Fallback)
|
|
13
|
+
*/
|
|
14
|
+
export class FluidEngineFactory {
|
|
15
|
+
static async create(config: FluidConfig3D): Promise<FluidEngine> {
|
|
16
|
+
// 1. Try WebNN
|
|
17
|
+
try {
|
|
18
|
+
const webnn = new FluidSimulationWebNN(config);
|
|
19
|
+
const webnnSupported = await webnn.initialize();
|
|
20
|
+
if (webnnSupported) {
|
|
21
|
+
console.log("Fluid Engine: Using WebNN (NPU)");
|
|
22
|
+
return webnn;
|
|
23
|
+
}
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.warn("Fluid Engine: WebNN init failed", e);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 2. Try WebGPU
|
|
29
|
+
try {
|
|
30
|
+
const webgpu = new FluidSimulation3DGPU(config);
|
|
31
|
+
const webgpuSupported = await webgpu.initialize();
|
|
32
|
+
if (webgpuSupported) {
|
|
33
|
+
console.log("Fluid Engine: Using WebGPU");
|
|
34
|
+
return webgpu;
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.warn("Fluid Engine: WebGPU init failed", e);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 3. Fallback to CPU
|
|
41
|
+
console.log("Fluid Engine: using CPU Fallback");
|
|
42
|
+
return new StableFluids3D(config);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebGPU Implementation of Fluid Simulation (Fallback)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Vector3 } from '../math/Vector3';
|
|
6
|
+
import { WebGPUContext } from '../webgpu/WebGPUContext';
|
|
7
|
+
import type { FluidConfig3D } from './FluidSimulation3D';
|
|
8
|
+
// @ts-ignore - Importing text file
|
|
9
|
+
import shaderSource from '../webgpu/shaders/fluid_sim.wgsl';
|
|
10
|
+
|
|
11
|
+
export class FluidSimulation3DGPU {
|
|
12
|
+
private width: number;
|
|
13
|
+
private height: number;
|
|
14
|
+
private depth: number;
|
|
15
|
+
private size: number;
|
|
16
|
+
|
|
17
|
+
private density: Float32Array;
|
|
18
|
+
private velocityX: Float32Array;
|
|
19
|
+
private velocityY: Float32Array;
|
|
20
|
+
private velocityZ: Float32Array;
|
|
21
|
+
|
|
22
|
+
private context: WebGPUContext;
|
|
23
|
+
private device: GPUDevice | null = null;
|
|
24
|
+
|
|
25
|
+
// GPU Buffers
|
|
26
|
+
private densityTexture: GPUTexture | null = null;
|
|
27
|
+
private velocityTexture: GPUTexture | null = null;
|
|
28
|
+
|
|
29
|
+
constructor(config: FluidConfig3D) {
|
|
30
|
+
this.width = Math.round(config.width);
|
|
31
|
+
this.height = Math.round(config.height);
|
|
32
|
+
this.depth = Math.round(config.depth);
|
|
33
|
+
this.size = this.width * this.height * this.depth;
|
|
34
|
+
|
|
35
|
+
this.density = new Float32Array(this.size);
|
|
36
|
+
this.velocityX = new Float32Array(this.size);
|
|
37
|
+
this.velocityY = new Float32Array(this.size);
|
|
38
|
+
this.velocityZ = new Float32Array(this.size);
|
|
39
|
+
|
|
40
|
+
this.context = WebGPUContext.getInstance();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async initialize(): Promise<boolean> {
|
|
44
|
+
const success = await this.context.initialize();
|
|
45
|
+
if (!success) return false;
|
|
46
|
+
|
|
47
|
+
this.device = this.context.getDevice();
|
|
48
|
+
if (!this.device) return false;
|
|
49
|
+
|
|
50
|
+
// Create 3D textures
|
|
51
|
+
this.densityTexture = this.device.createTexture({
|
|
52
|
+
size: [this.width, this.height, this.depth],
|
|
53
|
+
format: 'rgba16float',
|
|
54
|
+
usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ... Implement full texture creation and pipeline setup ...
|
|
58
|
+
// This is a placeholder for the verified architecture.
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async step(dt: number) {
|
|
64
|
+
if (!this.device) return;
|
|
65
|
+
|
|
66
|
+
// Dispatch compute passes
|
|
67
|
+
const commandEncoder = this.device.createCommandEncoder();
|
|
68
|
+
// ... commands ...
|
|
69
|
+
this.device.queue.submit([commandEncoder.finish()]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Public API
|
|
73
|
+
addDensity(x: number, y: number, z: number, amount: number, radius: number) {
|
|
74
|
+
// Needs CPU -> GPU copy
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
addForce(pos: Vector3, force: Vector3, radius: number) {
|
|
78
|
+
// Needs CPU -> GPU copy
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getDensityAt(pos: Vector3): number {
|
|
82
|
+
return 0; // Requires readback
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getVelocityAt(pos: Vector3): Vector3 {
|
|
86
|
+
return new Vector3(0,0,0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
clear() {
|
|
90
|
+
// Clear textures
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebNN implementation of Fluid Simulation.
|
|
3
|
+
* Attempts to map the Stable Fluids algorithm to a neural network graph
|
|
4
|
+
* for NPU acceleration.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Vector3 } from '../math/Vector3';
|
|
8
|
+
import { WebNNContext } from '../webnn/WebNNContext';
|
|
9
|
+
import type { FluidConfig3D } from './FluidSimulation3D';
|
|
10
|
+
|
|
11
|
+
// Extended WebNN types for GraphBuilder
|
|
12
|
+
declare global {
|
|
13
|
+
interface MLGraphBuilder {
|
|
14
|
+
input(name: string, descriptor: MLOperandDescriptor): MLOperand;
|
|
15
|
+
constant(descriptor: MLOperandDescriptor, buffer: ArrayBufferView): MLOperand;
|
|
16
|
+
add(a: MLOperand, b: MLOperand): MLOperand;
|
|
17
|
+
sub(a: MLOperand, b: MLOperand): MLOperand;
|
|
18
|
+
mul(a: MLOperand, b: MLOperand): MLOperand;
|
|
19
|
+
div(a: MLOperand, b: MLOperand): MLOperand;
|
|
20
|
+
clamp(x: MLOperand, options?: { minValue?: number; maxValue?: number }): MLOperand;
|
|
21
|
+
build(outputs: Record<string, MLOperand>): Promise<MLGraph>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface MLOperandDescriptor {
|
|
25
|
+
dataType: 'float32' | 'float16' | 'int32' | 'uint32';
|
|
26
|
+
dimensions: number[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface MLOperand {
|
|
30
|
+
// Opaque handle
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface Window {
|
|
34
|
+
MLGraphBuilder: {
|
|
35
|
+
new (context: MLContext): MLGraphBuilder;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class FluidSimulationWebNN {
|
|
41
|
+
private width: number;
|
|
42
|
+
private height: number;
|
|
43
|
+
private depth: number;
|
|
44
|
+
private size: number;
|
|
45
|
+
|
|
46
|
+
private density: Float32Array;
|
|
47
|
+
private velocityX: Float32Array;
|
|
48
|
+
private velocityY: Float32Array;
|
|
49
|
+
private velocityZ: Float32Array;
|
|
50
|
+
|
|
51
|
+
private context: WebNNContext;
|
|
52
|
+
private graph: MLGraph | null = null;
|
|
53
|
+
private builder: MLGraphBuilder | null = null;
|
|
54
|
+
|
|
55
|
+
constructor(config: FluidConfig3D) {
|
|
56
|
+
this.width = Math.round(config.width);
|
|
57
|
+
this.height = Math.round(config.height);
|
|
58
|
+
this.depth = Math.round(config.depth);
|
|
59
|
+
this.size = this.width * this.height * this.depth;
|
|
60
|
+
|
|
61
|
+
this.density = new Float32Array(this.size);
|
|
62
|
+
this.velocityX = new Float32Array(this.size);
|
|
63
|
+
this.velocityY = new Float32Array(this.size);
|
|
64
|
+
this.velocityZ = new Float32Array(this.size);
|
|
65
|
+
|
|
66
|
+
this.context = WebNNContext.getInstance();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async initialize(): Promise<boolean> {
|
|
70
|
+
const success = await this.context.initialize('npu');
|
|
71
|
+
if (!success) return false;
|
|
72
|
+
|
|
73
|
+
const mlContext = this.context.getContext();
|
|
74
|
+
if (mlContext && typeof window !== 'undefined' && window.MLGraphBuilder) {
|
|
75
|
+
this.builder = new window.MLGraphBuilder(mlContext);
|
|
76
|
+
await this.buildGraph();
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private async buildGraph() {
|
|
83
|
+
if (!this.builder) return;
|
|
84
|
+
|
|
85
|
+
// TODO: Implement the full Stable Fluids graph.
|
|
86
|
+
// Fluid simulation involves iterative solvers (Jacobi/Gauss-Seidel) which are hard to express
|
|
87
|
+
// as a single feed-forward graph without loops.
|
|
88
|
+
// Standard WebNN 1.0 does not support loops (Control Flow) easily inside the graph.
|
|
89
|
+
// We might need to unroll the iterations or dispatch the graph multiple times per frame.
|
|
90
|
+
|
|
91
|
+
// Strategy:
|
|
92
|
+
// 1. Create a "Diffusion Step" graph that takes (Field, PreviousField) -> NewField
|
|
93
|
+
// 2. Create an "Advection Step" graph? Advection requires gathering from arbitrary indices (Sampler),
|
|
94
|
+
// which is not a standard NPU operation (they prefer convolution/matmul).
|
|
95
|
+
|
|
96
|
+
// CHALLENGE: Stable Fluids is not a neural network. It's a PDE solver.
|
|
97
|
+
// NPUs are optimized for MatMul and Conv2D.
|
|
98
|
+
// We can map Diffusion to a 3D Convolution kernel (Laplacian approximation).
|
|
99
|
+
// Advection is the hard part (Grid Interpolation at arbitrary coords).
|
|
100
|
+
|
|
101
|
+
// For now, let's implement a simple "Decay/Diffusion" graph as a proof of concept
|
|
102
|
+
// that runs element-wise operations on the NPU.
|
|
103
|
+
|
|
104
|
+
const desc: MLOperandDescriptor = { dataType: 'float32', dimensions: [1, this.depth, this.height, this.width] };
|
|
105
|
+
const densityInput = this.builder.input('density', desc);
|
|
106
|
+
const decayConst = this.builder.constant({dataType: 'float32', dimensions: [1]}, new Float32Array([0.99]));
|
|
107
|
+
|
|
108
|
+
// Simple operation: Density * 0.99
|
|
109
|
+
const output = this.builder.mul(densityInput, decayConst);
|
|
110
|
+
|
|
111
|
+
this.graph = await this.builder.build({ 'densityOut': output });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async step(dt: number) {
|
|
115
|
+
if (!this.graph || !this.context.getContext()) {
|
|
116
|
+
// Fallback or no-op
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Execute the graph
|
|
121
|
+
// Needs to bind inputs/outputs
|
|
122
|
+
// This is highly experimental logic as WebNN API/Browser support is in flux.
|
|
123
|
+
try {
|
|
124
|
+
// Mock execution for now until we have a real environment to test against
|
|
125
|
+
// In a real implementation:
|
|
126
|
+
// this.context.getContext()!.compute(this.graph, inputs, outputs);
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.error("WebNN compute failed", e);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Public API compatibility with StableFluids3D
|
|
133
|
+
addDensity(x: number, y: number, z: number, amount: number, radius: number) {
|
|
134
|
+
// CPU implementation for interaction
|
|
135
|
+
// ... same as CPU ...
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
addForce(pos: Vector3, force: Vector3, radius: number) {
|
|
139
|
+
// ... same as CPU ...
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
getDensityAt(pos: Vector3): number {
|
|
143
|
+
return 0; // Readback from GPU/NPU required
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
getVelocityAt(pos: Vector3): Vector3 {
|
|
147
|
+
return new Vector3(0,0,0);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
clear() {
|
|
151
|
+
this.density.fill(0);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -1,18 +1,61 @@
|
|
|
1
|
-
import type { CSSProperties, RefObject } from 'react';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import type {
|
|
4
|
-
Photo as PhotoType,
|
|
5
|
-
HexGridProps as HexGridPropsType,
|
|
6
|
-
HexGridFeatureFlags,
|
|
7
|
-
} from '../types';
|
|
8
1
|
|
|
9
|
-
|
|
2
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { FluidEngineFactory, FluidEngine } from '../algorithms/FluidEngineFactory';
|
|
4
|
+
import { HexGridProps } from '../types';
|
|
10
5
|
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
export function HexGrid(props: HexGridProps): React.JSX.Element {
|
|
7
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
8
|
+
const [engine, setEngine] = useState<FluidEngine | null>(null);
|
|
9
|
+
const [error, setError] = useState<string | null>(null);
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
// Initialize Fluid Engine
|
|
13
|
+
const initEngine = async () => {
|
|
14
|
+
try {
|
|
15
|
+
const fluidEngine = await FluidEngineFactory.create({
|
|
16
|
+
width: 64,
|
|
17
|
+
height: 64,
|
|
18
|
+
depth: 64,
|
|
19
|
+
viscosity: 0.0001,
|
|
20
|
+
diffusion: 0.00001
|
|
21
|
+
});
|
|
22
|
+
setEngine(fluidEngine);
|
|
23
|
+
} catch (err: unknown) {
|
|
24
|
+
console.error("Failed to init fluid engine", err);
|
|
25
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
initEngine();
|
|
30
|
+
|
|
31
|
+
return () => {
|
|
32
|
+
// Cleanup if engine has cleanup method
|
|
33
|
+
if (engine && 'clear' in engine) {
|
|
34
|
+
engine.clear();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
if (error) {
|
|
40
|
+
return <div className="text-red-500">Error initializing simulation: {error}</div>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="hexgrid-container" style={{ width: '100%', height: '100%', position: 'relative' }}>
|
|
45
|
+
{/* Visualization Canvas */}
|
|
46
|
+
<canvas
|
|
47
|
+
ref={canvasRef}
|
|
48
|
+
width={800}
|
|
49
|
+
height={600}
|
|
50
|
+
style={{ width: '100%', height: '100%' }}
|
|
51
|
+
/>
|
|
52
|
+
|
|
53
|
+
{/* Status Overlay */}
|
|
54
|
+
<div style={{ position: 'absolute', top: 10, left: 10, background: 'rgba(0,0,0,0.5)', color: 'white', padding: '5px', pointerEvents: 'none' }}>
|
|
55
|
+
Engine: {engine ? engine.constructor.name : 'Initializing...'}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
16
59
|
}
|
|
17
60
|
|
|
18
61
|
export default HexGrid;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebGPU Context Manager
|
|
3
|
+
* Handles the creation and management of the WebGPU Device and Adapter.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class WebGPUContext {
|
|
7
|
+
private static instance: WebGPUContext;
|
|
8
|
+
private adapter: GPUAdapter | null = null;
|
|
9
|
+
private device: GPUDevice | null = null;
|
|
10
|
+
private isSupported: boolean = false;
|
|
11
|
+
|
|
12
|
+
private constructor() {}
|
|
13
|
+
|
|
14
|
+
static getInstance(): WebGPUContext {
|
|
15
|
+
if (!WebGPUContext.instance) {
|
|
16
|
+
WebGPUContext.instance = new WebGPUContext();
|
|
17
|
+
}
|
|
18
|
+
return WebGPUContext.instance;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Initialize WebGPU context.
|
|
23
|
+
*/
|
|
24
|
+
async initialize(): Promise<boolean> {
|
|
25
|
+
if (typeof navigator === 'undefined' || !navigator.gpu) {
|
|
26
|
+
console.warn('WebGPU is not supported in this environment.');
|
|
27
|
+
this.isSupported = false;
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
this.adapter = await navigator.gpu.requestAdapter({
|
|
33
|
+
powerPreference: 'high-performance'
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (!this.adapter) {
|
|
37
|
+
console.warn('No WebGPU adapter found.');
|
|
38
|
+
this.isSupported = false;
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.device = await this.adapter.requestDevice();
|
|
43
|
+
|
|
44
|
+
this.device.lost.then((info) => {
|
|
45
|
+
console.error(`WebGPU device lost: ${info.message}`);
|
|
46
|
+
this.device = null;
|
|
47
|
+
this.isSupported = false;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
this.isSupported = true;
|
|
51
|
+
console.log('WebGPU initialized successfully.');
|
|
52
|
+
return true;
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error('Failed to initialize WebGPU:', e);
|
|
55
|
+
this.isSupported = false;
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
getDevice(): GPUDevice | null {
|
|
61
|
+
return this.device;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getAdapter(): GPUAdapter | null {
|
|
65
|
+
return this.adapter;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
isAvailable(): boolean {
|
|
69
|
+
return this.isSupported && this.device !== null;
|
|
70
|
+
}
|
|
71
|
+
}
|