Skip to content

Commit 02f890f

Browse files
committed
fix: fix spectrum symmetry
1 parent e37f5bc commit 02f890f

9 files changed

+138
-97
lines changed

gui.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export const defaultParams: GuiParams = {
5353
resolution: 256,
5454
wind: vec2.fromValues(4, 11),
5555
alignment: 1.0e-2,
56-
foamSpreading: 1.2,
56+
foamSpreading: 1.0,
5757
foamContrast: 6.5,
5858
randomSeed: 0,
5959
tileRenderer: {

index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import {
1414
testFft2,
1515
testOceanFieldIfft2,
1616
testFft2Hermitian,
17-
testOceanFieldIfft2HermitianProperty,
1817
testFft2Combined,
18+
testOceanFieldBuilderHermitianSpectrum
1919
} from './test';
2020

2121
// testButterflyTexture();
@@ -26,7 +26,7 @@ import {
2626
// testFft2Hermitian();
2727
// testFft2Combined();
2828
// testOceanFieldIfft2();
29-
// testOceanFieldIfft2HermitianProperty();
29+
// testOceanFieldBuilderHermitianSpectrum();
3030

3131
registerWorkerGlobals();
3232

ocean/ocean-field-builder.test.ts

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { vec2 } from 'gl-matrix';
2+
import { merge } from 'lodash-es';
3+
4+
import { createMockGpu } from '../graphics/gpu.mock';
5+
import { OceanFieldBuilder } from './ocean-field-builder';
6+
import {
7+
float4ToComplex2d,
8+
ifft2,
9+
im,
10+
eix,
11+
add,
12+
mult,
13+
conj,
14+
complex,
15+
} from '../fft';
16+
import {
17+
OceanFieldBuildParams,
18+
defaultBuildParams,
19+
} from './ocean-field-build-params';
20+
21+
export const testOceanFieldBuilderHermitianSpectrum = () => {
22+
// Arrange
23+
const gpu = createMockGpu();
24+
const builder = new OceanFieldBuilder(gpu);
25+
const framebuffer = gpu.createRenderTarget();
26+
const params: OceanFieldBuildParams = merge({}, defaultBuildParams, {
27+
alignment: 0.01,
28+
resolution: 512,
29+
wind: vec2.fromValues(21.0, 24.7),
30+
randomSeed: 0,
31+
cascades: [
32+
{
33+
size: 100.0,
34+
strength: 3e12,
35+
},
36+
{
37+
size: 200.0,
38+
strength: 2e12,
39+
},
40+
{
41+
size: 300.0,
42+
strength: 1e12,
43+
},
44+
],
45+
});
46+
const buffer = new Float32Array(params.resolution ** 2 * 4);
47+
const h0textures = builder['createH0Textures'](params.resolution);
48+
gpu.attachTextures(framebuffer, h0textures);
49+
50+
const bounds = (array: number[]) => {
51+
return array.reduce(
52+
(acc, curr) => {
53+
if (curr < acc[0]) {
54+
acc[0] = curr;
55+
}
56+
if (curr > acc[1]) {
57+
acc[1] = curr;
58+
}
59+
return acc;
60+
},
61+
[Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY]
62+
);
63+
};
64+
65+
// Act
66+
builder['generateInitialSpectrum'](h0textures, params);
67+
68+
// Assert
69+
for (let slot = 0; slot < h0textures.length; slot++) {
70+
gpu.readValues(
71+
framebuffer,
72+
buffer,
73+
params.resolution,
74+
params.resolution,
75+
WebGL2RenderingContext.RGBA,
76+
WebGL2RenderingContext.FLOAT,
77+
slot
78+
);
79+
80+
const t = 36000;
81+
const h0 = float4ToComplex2d(buffer, params.resolution, 0);
82+
const h0MinConj = float4ToComplex2d(buffer, params.resolution, 2);
83+
84+
const h = h0.map((row, i) => {
85+
return row.map((h0, j) => {
86+
const k = vec2.fromValues(
87+
j - params.resolution / 2,
88+
i - params.resolution / 2
89+
);
90+
const kLen = vec2.len(k);
91+
if (i !== 0 && j !== 0) {
92+
const w = Math.sqrt(9.8 * kLen);
93+
const e = eix(t * w);
94+
return add(mult(h0, e), mult(h0MinConj[i][j], conj(e)));
95+
} else {
96+
return complex(0.0, 0.0);
97+
}
98+
});
99+
});
100+
101+
const signal = ifft2(h).flat(1);
102+
const isCloseToReal = signal.every((v) => Math.abs(im(v)) <= 1.0e-10);
103+
104+
if (!isCloseToReal) {
105+
const [min, max] = bounds(signal.map((e) => im(e)));
106+
console.warn(
107+
`testOceanFieldBuilderHermitianSpectrum [slot ${slot}]: Test didn't pass: [min: ${min}, max: ${max}]`
108+
);
109+
return;
110+
}
111+
console.log(
112+
`testOceanFieldBuilderHermitianSpectrum [slot ${slot}]: Test passed!`
113+
);
114+
}
115+
};

ocean/ocean-field-builder.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ export class OceanFieldBuilder {
170170
this.gpu.createFloat4Texture(
171171
size,
172172
size,
173-
TextureFiltering.Linear,
174-
TextureMode.Mirror
173+
TextureFiltering.Nearest,
174+
TextureMode.Repeat
175175
)
176176
);
177177
}

ocean/ocean-field.test.ts

-74
Original file line numberDiff line numberDiff line change
@@ -80,77 +80,3 @@ export const testOceanFieldIfft2 = () => {
8080
}
8181
}
8282
};
83-
84-
/**
85-
* @todo: fix test
86-
*/
87-
export const testOceanFieldIfft2HermitianProperty = () => {
88-
const gpu = createMockGpu();
89-
const builder = new OceanFieldBuilder(gpu);
90-
const oceanField = builder.build({
91-
alignment: 0.0,
92-
resolution: 4,
93-
wind: vec2.fromValues(28.0, 28.0),
94-
randomSeed: 0,
95-
});
96-
97-
// Arrange
98-
const framebuffer = gpu.createRenderTarget();
99-
const buffer = new Float32Array(
100-
oceanField.params.resolution *
101-
oceanField.params.resolution *
102-
4
103-
);
104-
105-
for (let slot of [0, 1]) {
106-
for (let couple of [0, 1]) {
107-
oceanField['generateSpectrum'](performance.now());
108-
109-
// Act
110-
oceanField['ifft2']();
111-
gpu.attachTexture(
112-
framebuffer,
113-
oceanField['ifftTextures'][slot],
114-
0
115-
);
116-
gpu.readValues(
117-
framebuffer,
118-
buffer,
119-
oceanField.params.resolution,
120-
oceanField.params.resolution,
121-
WebGL2RenderingContext.RGBA,
122-
WebGL2RenderingContext.FLOAT,
123-
0
124-
);
125-
126-
const actual = float4ToComplex2d(
127-
buffer,
128-
oceanField.params.resolution,
129-
couple * 2
130-
).flat(1);
131-
132-
// Assert
133-
const _im = actual.map((v) => im(v));
134-
const isCloseToReal = _im.every((v) => Math.abs(v) <= 1.0e-4);
135-
if (!isCloseToReal) {
136-
const max = _im.reduce(
137-
(max, curr) => (curr > max ? curr : max),
138-
Number.NEGATIVE_INFINITY
139-
);
140-
141-
const min = _im.reduce(
142-
(min, curr) => (curr < min ? curr : min),
143-
Number.POSITIVE_INFINITY
144-
);
145-
146-
console.warn(
147-
`testOceanFieldIfft2HermitianProperty [slot ${slot}-${couple}]: Test don't pass: [max: ${max}, min: ${min}]`
148-
);
149-
return;
150-
}
151-
console.log(
152-
`testOceanFieldIfft2HermitianProperty [slot ${slot}-${couple}]: Test passed!`
153-
);
154-
}
155-
}
156-
};

ocean/programs/h0.ts

+15-16
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,19 @@ uniform struct FieldCascade {
2828
float maxK;
2929
} cascades[3];
3030
31-
vec2 gauss() {
32-
vec2 uv = 2.0f * vec2(ivec2(gl_FragCoord.xy)) / float(resolution) - vec2(1.0f);
33-
vec2 noise2 = vec2(0.0f);
31+
vec4 gauss() {
32+
vec2 uv = vec2(ivec2(gl_FragCoord.xy)) / float(resolution);
3433
35-
if((uv.x >= 0.0f && uv.y >= 0.0f) || (uv.x <= 0.0f && uv.y <= 0.0f)) {
36-
noise2 = texture(noise, uv).rg;
37-
} else {
38-
noise2 = texture(noise, uv).ba;
39-
}
34+
vec2 noise0 = texture(noise, uv).rg;
35+
vec2 noise1 = texture(noise, -uv).rg;
36+
37+
float u0 = 2.0f * PI * noise0.x;
38+
float v0 = sqrt(-2.0f * log(noise0.y));
39+
40+
float u1 = 2.0f * PI * noise1.x;
41+
float v1 = sqrt(-2.0f * log(noise1.y));
4042
41-
float u0 = 2.0f * PI * noise2.x;
42-
float v0 = sqrt(-2.0f * log(noise2.y));
43-
return vec2(v0 * cos(u0), v0 * sin(u0));
43+
return vec4(v0 * cos(u0), v0 * sin(u0), v1 * cos(u1), -v1 * sin(u1));
4444
}
4545
4646
vec4 phillips(in vec2 k, float A, float minK, float maxK) {
@@ -65,11 +65,10 @@ vec4 phillips(in vec2 k, float A, float minK, float maxK) {
6565
void main() {
6666
vec2 x = vec2(ivec2(gl_FragCoord.xy) - ivec2(resolution / 2u)); // [-N/2, N/2]
6767
vec2 k = vec2(PI2) * x;
68-
vec2 rnd = gauss();
69-
vec4 mult = vec4(rnd.x, rnd.y, rnd.x, -rnd.y);
68+
vec4 rnd = gauss();
7069
71-
spectrum0 = phillips(k / cascades[0].size, cascades[0].strength, cascades[0].minK, cascades[0].maxK) * mult;
72-
spectrum1 = phillips(k / cascades[1].size, cascades[1].strength, cascades[1].minK, cascades[1].maxK) * mult;
73-
spectrum2 = phillips(k / cascades[2].size, cascades[2].strength, cascades[2].minK, cascades[2].maxK) * mult;
70+
spectrum0 = phillips(k / cascades[0].size, cascades[0].strength, cascades[0].minK, cascades[0].maxK) * rnd;
71+
spectrum1 = phillips(k / cascades[1].size, cascades[1].strength, cascades[1].minK, cascades[1].maxK) * rnd;
72+
spectrum2 = phillips(k / cascades[2].size, cascades[2].strength, cascades[2].minK, cascades[2].maxK) * rnd;
7473
}
7574
`;

ocean/programs/hk.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ void compressSpectrum(in spectrum spec, out vec4 part0, out vec4 part1) {
129129
}
130130
131131
void main() {
132-
vec2 x = vec2(ivec2(gl_FragCoord.xy)) - float(resolution) * 0.5; // [-N/2, N/2)
132+
vec2 x = vec2(ivec2(gl_FragCoord.xy) - ivec2(resolution / 2u)); // [-N/2, N/2)
133133
134134
spectrum spec0 = getSpectrum(h0Texture0, x, sizes[0]);
135135
spectrum spec1 = getSpectrum(h0Texture1, x, sizes[1]);

test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from './fft/fft.test';
22
export * from './fft/fft2.test';
33
export * from './ocean/butterfly.test';
44
export * from './ocean/ocean-field.test';
5+
export * from './ocean/ocean-field-builder.test';

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"compilerOptions": {
33
"removeComments": false,
4-
"lib": ["es2020", "es2017", "dom"],
4+
"lib": ["ES2020", "ES2017", "DOM"],
55
"module": "amd",
66
"target": "ES2018"
77
}

0 commit comments

Comments
 (0)