Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: interpolation #214

Merged
merged 3 commits into from
Dec 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 77 additions & 18 deletions src/components/Simulation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class SimulationParams {
// we can pass the parameter object directly
interface Renderable {
params: SimulationParams;
outputSubs: Array<(density: Float32Array) => void>;
outputSubs: Array<(density: Float32Array[]) => void>;
worker: Worker;
disableInteraction: boolean;
}
Expand Down Expand Up @@ -97,8 +97,6 @@ function DiffusionPlane(
shaderMat.fragmentShader = applyConfigToShader(fragmentShader as string);
shaderMat.side = t.DoubleSide;

// provide a dummy density field first

// TODO: until we standardise parameters a bit more we'll hardcode
// an advection size of 32*32
const initDensity = new Float32Array(new Array(64 * 64).fill(0));
Expand Down Expand Up @@ -138,34 +136,95 @@ function DiffusionPlane(
const { outputSubs, worker } = props;

useEffect(() => {
outputSubs.push((density: Float32Array) => {
console.log('[renderer] [event] Creating worker');
outputSubs.push((density: Float32Array[]) => {
output(density);
});

// SUBSCRIPTIONS
// update the density uniforms every time
// output is received
function output(data: Float32Array): void {
function output(data: Float32Array[]): void {
// create a copy to prevent modifying original data
data = data.slice(0);
const param: Record<string, number> = {
densityRangeHigh: parseFloat(renderConfig.densityRangeHigh),
densityRangeLow: parseFloat(renderConfig.densityRangeLow),
densityRangeSize: parseFloat(renderConfig.densityRangeSize),
};
// texture float value are required to be in range [0.0, 1.0],
// so we have to convert this in js
for (let i = 0; i < data.length; i++) {
let density = Math.min(data[i], param.densityRangeHigh);
density = Math.max(density, param.densityRangeLow);
density = density / param.densityRangeSize;
data[i] = density;

function updateTexture(data: Float32Array): void {
// texture float value is required to be in range [0.0, 1.0],
// so we have to convert this in js
for (let i = 0; i < data.length; i++) {
let density = Math.min(data[i], param.densityRangeHigh);
density = Math.max(density, param.densityRangeLow);
density = density / param.densityRangeSize;
data[i] = density;
}
const tex = new t.DataTexture(data, 64, 64, t.RedFormat, t.FloatType);
tex.needsUpdate = true;
shaderMat.uniforms.density.value = tex;
}
// calculate the fps
console.log(`[renderer] [event] Received output, fps: ${data.length}`);
if (data.length < 30) {
console.log(
`[renderer] [event] FPS is low: ${data.length}, interpolation in progress`,
);
// interpolate based on current frame rate
// calc the interplot multiplier
const interpMul = Math.ceil((30 - 1) / data.length - 1);
console.log(
`[renderer] [event] Interpolation multiplier: ${interpMul}`,
);
// create the interpolated data
const interpData: Float32Array[] = [];
// interpolate
for (let i = 0; i < data.length; i++) {
// start with the first original frame, then interpolate interpMul times with linear interpolation,
// then add the next original frame
console.log(
`[renderer] [event] Interpolating frame ${i + 1}/${data.length}`,
);
interpData.push(data[i]);
if (i + 1 < data.length) {
const start = data[i];
const end = data[i + 1];
for (let j = 0; j < interpMul; j++) {
const interp = new Float32Array(start.length);
for (let k = 0; k < start.length; k++) {
interp[k] =
start[k] + ((end[k] - start[k]) * (j + 1)) / (interpMul + 1);
}
interpData.push(interp);
}
}
}

console.log(
`[renderer] [event] Interpolation complete, fps: ${interpData.length}`,
);
let i = 0;
// start the interpolation
setInterval(
() => {
if (i >= interpData.length) return;
updateTexture(interpData[i]);
i++;
},
1000 / (data.length * interpMul),
);
} else {
let i = 0;
setInterval(() => {
if (i >= data.length) return;
updateTexture(data[i]);
i++;
}, 1000 / data.length);
}
const tex = new t.DataTexture(data, 64, 64, t.RedFormat, t.FloatType);
tex.needsUpdate = true;
shaderMat.uniforms.density.value = tex;
}
}, [shaderMat, outputSubs]);
}, [outputSubs, shaderMat.uniforms.density]);

const { disableInteraction } = props;
let pointMoved = false;
Expand Down Expand Up @@ -209,8 +268,8 @@ function DiffusionPlane(
worker.postMessage({
func: RunnerFunc.UPDATE_FORCE,
args: {
force: forceDelta,
position: loc,
forceDelta,
loc,
} satisfies UpdateForceArgs,
});
}, forceInterval);
Expand Down
2 changes: 1 addition & 1 deletion src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default function Home(props: IndexProp): React.ReactElement {
// to distribute the worker messages across different components
// we utilise an observer pattern where components can subscribe
// their functions to different message types
const outputSubs: Array<(density: Float32Array) => void> = useMemo(
const outputSubs: Array<(density: Float32Array[]) => void> = useMemo(
() => [],
[],
);
Expand Down
11 changes: 10 additions & 1 deletion src/workers/modelWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,21 @@ function bindCallback(
event: DedicatedWorkerGlobalScope,
modelService: ModelService,
): void {
const cache: Float32Array[] = [];
const outputCallback = (output: Float32Array): void => {
console.log('outputCallback', output);
const density = new Float32Array(output.length / 3);
for (let i = 0; i < density.length; i++) {
density[i] = output[i * 3];
}
event.postMessage({ type: 'output', density });
cache.push(density);
};
setInterval(() => {
console.log('cache', cache);
if (cache.length > 0) {
event.postMessage({ type: 'output', density: cache });
cache.splice(0, cache.length);
}
}, 1000);
modelService.bindOutput(outputCallback);
}
Loading