Skip to content

Commit

Permalink
faster
Browse files Browse the repository at this point in the history
  • Loading branch information
OrionReed committed Dec 1, 2024
1 parent 0f969ab commit fdd7fb9
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 97 deletions.
87 changes: 72 additions & 15 deletions src/distanceField/cpt.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,108 @@
import type { Vector2 } from '../utils/Vector2.ts';
import { findHullParabolas, transpose } from './utils.ts';

/** Adapted from Felzenszwalb, P. F., & Huttenlocher, D. P. (2012). Distance Transforms of Sampled Functions. Theory of Computing, 8(1), 415–428. */
export function computeCPT(
sedt: Float32Array[],
cpt: Vector2[][],
xcoords: Float32Array[],
ycoords: Float32Array[]
): Vector2[][] {
const length = sedt.length;
const tempArray = new Float32Array(length);

// Pre-allocate hull arrays
const hullVertices: Vector2[] = [];
const hullIntersections: Vector2[] = [];

for (let row = 0; row < length; row++) {
horizontalPass(sedt[row], xcoords[row]);
horizontalPass(sedt[row], xcoords[row], hullVertices, hullIntersections);
}

transpose(sedt);
for (let i = 0; i < length; i++) {
for (let j = i + 1; j < length; j++) {
tempArray[0] = sedt[i][j];
sedt[i][j] = sedt[j][i];
sedt[j][i] = tempArray[0];
}
}

for (let row = 0; row < length; row++) {
horizontalPass(sedt[row], ycoords[row]);
horizontalPass(sedt[row], ycoords[row], hullVertices, hullIntersections);
}

for (let col = 0; col < length; col++) {
for (let row = 0; row < length; row++) {
const y = ycoords[col][row];
const x = xcoords[y][col];
cpt[row][col] = { x, y };
}
const len = length * length;
for (let i = 0; i < len; i++) {
const row = i % length;
const col = (i / length) | 0;
const y = ycoords[col][row];
const x = xcoords[y][col];
cpt[row][col].x = x;
cpt[row][col].y = y;
}

return cpt;
}

function horizontalPass(singleRow: Float32Array, indices: Float32Array) {
const hullVertices: Vector2[] = [];
const hullIntersections: Vector2[] = [];
function horizontalPass(
singleRow: Float32Array,
indices: Float32Array,
hullVertices: Vector2[],
hullIntersections: Vector2[]
) {
// Clear hull arrays before use
hullVertices.length = 0;
hullIntersections.length = 0;

findHullParabolas(singleRow, hullVertices, hullIntersections);
marchParabolas(singleRow, hullVertices, hullIntersections, indices);
}

function marchParabolas(row: Float32Array, verts: Vector2[], intersections: Vector2[], indices: Float32Array) {
let k = 0;
const n = row.length;
const numVerts = verts.length;

for (let i = 0; i < row.length; i++) {
while (intersections[k + 1].x < i) {
for (let i = 0; i < n; i++) {
while (k < numVerts - 1 && intersections[k + 1].x < i) {
k++;
}
const dx = i - verts[k].x;
row[i] = dx * dx + verts[k].y;
indices[i] = verts[k].x;
}
}

function findHullParabolas(row: Float32Array, verts: Vector2[], intersections: Vector2[]) {
let k = 0;

verts[k] = { x: 0, y: row[0] };
intersections[k] = { x: -Infinity, y: 0 };
intersections[k + 1] = { x: Infinity, y: 0 };

const n = row.length;

for (let i = 1; i < n; i++) {
const s: Vector2 = { x: 0, y: 0 };
const qx = i;
const qy = row[i];
let p = verts[k];

// Calculate intersection
s.x = (qy + qx * qx - (p.y + p.x * p.x)) / (2 * (qx - p.x));

while (k > 0 && s.x <= intersections[k].x) {
k--;
p = verts[k];
s.x = (qy + qx * qx - (p.y + p.x * p.x)) / (2 * (qx - p.x));
}

k++;
verts[k] = { x: qx, y: qy };
intersections[k] = { x: s.x, y: 0 };
intersections[k + 1] = { x: Infinity, y: 0 };
}

// Adjust the length of verts and intersections arrays
verts.length = k + 1;
intersections.length = k + 2;
}
41 changes: 0 additions & 41 deletions src/distanceField/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,47 +147,6 @@ export class Fields {
}
}

private renderer(
pixelRenderer: (
distance: number,
closestX: number,
closestY: number,
shapeColor: number,
row: number,
col: number
) => { r: number; g: number; b: number }
): ImageData {
const imageData = new ImageData(this.resolution, this.resolution);
const data = imageData.data;
const resolution = this.resolution;

// Pre-cache arrays to avoid repeated property access
const edt = this.edt;
const cpt = this.cpt;
const colorField = this.colorField;

// Process pixels in a single loop
for (let i = 0; i < resolution * resolution; i++) {
const row = i % resolution;
const col = (i / resolution) | 0; // Faster integer division
const index = i * 4;

const distance = edt[row][col];
const { x: closestX, y: closestY } = cpt[row][col];
const shapeColor = colorField[closestX][closestY];

const color = pixelRenderer(distance, closestX, closestY, shapeColor, row, col);

// Direct array access is faster than property access
data[index] = color.r;
data[index + 1] = color.g;
data[index + 2] = color.b;
data[index + 3] = 255;
}

return imageData;
}

public generateImageData(): ImageData {
const imageData = new ImageData(this.resolution, this.resolution);
const data = imageData.data;
Expand Down
41 changes: 0 additions & 41 deletions src/distanceField/utils.ts

This file was deleted.

0 comments on commit fdd7fb9

Please sign in to comment.