Skip to content

Commit

Permalink
feat: interpolation (#214)
Browse files Browse the repository at this point in the history
* feat: interpolation

The output data type of the simulation component was changed to enhance
the visual representation of the simulation. Initially, a simple
Float32Array was used. However, a more complicated output data type - an
array of Float32Arrays - was introduced to better represent the density
data. We also added interpolation to handle low frame rates. This
modification will provide a smoother simulation display. Furthermore,
loggers were improved to provide more insights about the worker
creation, frame rate, and the interpolation process. The naming of
temporary variables holding crucial data was also refined for a better
understanding of the codebase. Lastly, the code organization was
improved and clearer comments were added.

BREAKING CHANGE: Output from worker now distributes the data per second
instead of per each run

* fix(simulation): add deps bug in `useEffect`

the missing deps have been re-added to useEffect in Simulation.tsx
according to ESLint Check

* perf(worker): optimize cache handling in modelWorker

Changed the way cache is handled in the modelWorker file. Instead of
duplicating the entire cache with the 'slice' method, the change uses
the cache directly because the copy will automatically handled by
postMessage. This optimization reduces unnecessary resource consumption
and improves efficiency.
  • Loading branch information
Lutra-Fs authored Dec 10, 2023
1 parent a476c14 commit f593892
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 20 deletions.
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);
}

0 comments on commit f593892

Please sign in to comment.