distance.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // Signed distance fields over a window of a solid/empty grid: the terrain is drawn from them,
  2. // so its edges are smooth curves instead of the staircase of the material bitmap.
  3. const INF = 1e20;
  4. /** Scratch buffers for the 1D transform, grown on demand. */
  5. let f = new Float64Array(0);
  6. let d = new Float64Array(0);
  7. let z = new Float64Array(0);
  8. let v = new Int32Array(0);
  9. function ensure(n: number): void {
  10. if (f.length >= n) return;
  11. f = new Float64Array(n);
  12. d = new Float64Array(n);
  13. z = new Float64Array(n + 1);
  14. v = new Int32Array(n);
  15. }
  16. /** Squared Euclidean distance transform of `grid` along one line (Felzenszwalb & Huttenlocher). */
  17. function edt1d(grid: Float64Array, offset: number, stride: number, n: number): void {
  18. for (let q = 0; q < n; q++) f[q] = grid[offset + q * stride];
  19. let k = 0;
  20. v[0] = 0;
  21. z[0] = -INF;
  22. z[1] = INF;
  23. for (let q = 1; q < n; q++) {
  24. let s = (f[q] + q * q - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]);
  25. while (s <= z[k]) {
  26. k--;
  27. s = (f[q] + q * q - (f[v[k]] + v[k] * v[k])) / (2 * q - 2 * v[k]);
  28. }
  29. k++;
  30. v[k] = q;
  31. z[k] = s;
  32. z[k + 1] = INF;
  33. }
  34. k = 0;
  35. for (let q = 0; q < n; q++) {
  36. while (z[k + 1] < q) k++;
  37. const dq = q - v[k];
  38. d[q] = dq * dq + f[v[k]];
  39. }
  40. for (let q = 0; q < n; q++) grid[offset + q * stride] = d[q];
  41. }
  42. function edt2d(grid: Float64Array, w: number, h: number): void {
  43. ensure(Math.max(w, h));
  44. for (let x = 0; x < w; x++) edt1d(grid, x, w, h);
  45. for (let y = 0; y < h; y++) edt1d(grid, y * w, 1, w);
  46. }
  47. let inside = new Float64Array(0);
  48. let outside = new Float64Array(0);
  49. /**
  50. * Signed distance (in pixels, positive inside solid) from each pixel center of a w×h window to
  51. * the solid/empty boundary, which runs halfway between a solid pixel and an empty one.
  52. * Values are clamped to ±`clamp`.
  53. */
  54. export function signedDistance(solid: (i: number) => boolean, w: number, h: number, out: Float32Array, clamp: number): void {
  55. const n = w * h;
  56. if (inside.length < n) {
  57. inside = new Float64Array(n);
  58. outside = new Float64Array(n);
  59. }
  60. for (let i = 0; i < n; i++) {
  61. const s = solid(i);
  62. inside[i] = s ? INF : 0;
  63. outside[i] = s ? 0 : INF;
  64. }
  65. edt2d(inside, w, h);
  66. edt2d(outside, w, h);
  67. for (let i = 0; i < n; i++) {
  68. const sd = inside[i] > 0 ? Math.sqrt(inside[i]) - 0.5 : 0.5 - Math.sqrt(outside[i]);
  69. out[i] = sd > clamp ? clamp : sd < -clamp ? -clamp : sd;
  70. }
  71. }
  72. /** Bilinear sample of a w×h grid of pixel-center values at window coordinates (u, v) (pixel units). */
  73. export function sampleBilinear(grid: Float32Array, w: number, h: number, u: number, v: number): number {
  74. const x = u - 0.5;
  75. const y = v - 0.5;
  76. let ix = Math.floor(x);
  77. let iy = Math.floor(y);
  78. let fx = x - ix;
  79. let fy = y - iy;
  80. if (ix < 0) {
  81. ix = 0;
  82. fx = 0;
  83. } else if (ix >= w - 1) {
  84. ix = w - 2;
  85. fx = 1;
  86. }
  87. if (iy < 0) {
  88. iy = 0;
  89. fy = 0;
  90. } else if (iy >= h - 1) {
  91. iy = h - 2;
  92. fy = 1;
  93. }
  94. const i = iy * w + ix;
  95. const a = grid[i];
  96. const b = grid[i + 1];
  97. const c = grid[i + w];
  98. const e = grid[i + w + 1];
  99. return a + (b - a) * fx + (c - a) * fy + (a - b - c + e) * fx * fy;
  100. }