Simon пре 4 година
родитељ
комит
8e899b00ba
5 измењених фајлова са 325 додато и 0 уклоњено
  1. 9 0
      base.css
  2. 12 0
      index.html
  3. 101 0
      src/main.js
  4. 38 0
      src/math.js
  5. 165 0
      src/spatial-grid.js

+ 9 - 0
base.css

@@ -0,0 +1,9 @@
+body {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  background: #000000;
+  margin: 0;
+  padding: 0;
+  overscroll-behavior: none;
+}

+ 12 - 0
index.html

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Spatial Hash Grid</title>
+  <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+  <link rel="stylesheet" type="text/css" href="base.css">
+</head>
+<body>
+  <script src="./src/main.js" type="module">
+  </script>
+</body>
+</html>

+ 101 - 0
src/main.js

@@ -0,0 +1,101 @@
+import {math} from './math.js';
+import {spatial_grid} from './spatial-grid.js';
+
+// Testing harness
+
+const _NUM_CLIENTS = 100000;
+const _ITERATIONS = 10000;
+
+const _CLIENT_BOUNDS = [[-1000.0, -1000.0], [1000.0, 1000.0]];
+const _CLIENT_DIMENSIONS = [100, 100];
+
+const _CLIENT_POSITIONS = [];
+for (let i = 0; i < _NUM_CLIENTS; ++i) {
+  _CLIENT_POSITIONS.push(
+      [
+          math.rand_range(_CLIENT_BOUNDS[0][0], _CLIENT_BOUNDS[1][0]),
+          math.rand_range(_CLIENT_BOUNDS[0][1], _CLIENT_BOUNDS[1][1])
+      ]);
+}
+
+const _CLIENT_QUERIES = [];
+for (let i = 0; i < _ITERATIONS; ++i) {
+  const p = [
+      math.rand_range(_CLIENT_BOUNDS[0][0], _CLIENT_BOUNDS[1][0]),
+      math.rand_range(_CLIENT_BOUNDS[0][1], _CLIENT_BOUNDS[1][1])];
+
+  _CLIENT_QUERIES.push(p);
+}
+
+const _CLIENT_MOVES = [];
+for (let i = 0; i < _NUM_CLIENTS; ++i) {
+  const p = [
+      Math.random(),
+      Math.random()];
+
+  _CLIENT_MOVES.push(p);
+}
+
+class GridTester {
+  constructor(gridClass) {
+    this._grid = new gridClass(_CLIENT_BOUNDS, _CLIENT_DIMENSIONS);
+
+    this._clients = [];
+    for (let i = 0; i < _NUM_CLIENTS; ++i) {
+      const client = this._grid.NewClient(
+          _CLIENT_POSITIONS[i], [15, 15]
+      );
+      this._clients.push(client);
+    }
+  }
+
+  Test_FindNearby() {
+    const queryBounds = [15, 15];
+
+    let startTime = performance.now();
+    for (let i = 0; i < _ITERATIONS; ++i) {
+      this._grid.FindNear(_CLIENT_QUERIES[i], queryBounds);
+    }
+    let totalTime = performance.now() - startTime;
+    return totalTime;
+  }
+
+  Test_Update() {
+    for (let i = 0; i < this._clients.length; ++i) {
+      const c = this._clients[i];
+      c.position[0] = _CLIENT_POSITIONS[i][0];
+      c.position[1] = _CLIENT_POSITIONS[i][1];
+      this._grid.UpdateClient(this._clients[i]);
+    }
+  
+    let startTime = performance.now();
+    for (let i = 0; i < this._clients.length; ++i) {
+      const c = this._clients[i];
+      c.position[0] += _CLIENT_MOVES[i][0];
+      c.position[1] += _CLIENT_MOVES[i][1];
+      this._grid.UpdateClient(this._clients[i]);
+    }
+    let totalTime = performance.now() - startTime;
+
+    return totalTime;
+  }
+}
+
+
+const gridSlow = new GridTester(spatial_grid.SpatialHash_Crap);
+const gridFast = new GridTester(spatial_grid.SpatialHash_Slow);
+
+console.log('Spatial Grid (Naive) - FindNearby: ' + gridSlow.Test_FindNearby() + 'ms');
+console.log('Spatial Grid - FindNearby: ' + gridFast.Test_FindNearby() + 'ms');
+console.log('----------------------------------');
+console.log('Spatial Grid (Naive) - FindNearby: ' + gridSlow.Test_FindNearby() + 'ms');
+console.log('Spatial Grid - FindNearby: ' + gridFast.Test_FindNearby() + 'ms');
+
+console.log('----------------------------------');
+console.log('----------------------------------');
+
+console.log('Spatial Grid (Naive) - Update: ' + gridSlow.Test_Update() + 'ms');
+console.log('Spatial Grid - Update: ' + gridFast.Test_Update() + 'ms');
+console.log('----------------------------------');
+console.log('Spatial Grid (Naive) - Update: ' + gridSlow.Test_Update() + 'ms');
+console.log('Spatial Grid - Update: ' + gridFast.Test_Update() + 'ms');

+ 38 - 0
src/math.js

@@ -0,0 +1,38 @@
+export const math = (function() {
+  return {
+    rand_range: function(a, b) {
+      return Math.random() * (b - a) + a;
+    },
+
+    rand_normalish: function() {
+      const r = Math.random() + Math.random() + Math.random() + Math.random();
+      return (r / 4.0) * 2.0 - 1;
+    },
+
+    rand_int: function(a, b) {
+      return Math.round(Math.random() * (b - a) + a);
+    },
+
+    lerp: function(x, a, b) {
+      return x * (b - a) + a;
+    },
+
+    smoothstep: function(x, a, b) {
+      x = x * x * (3.0 - 2.0 * x);
+      return x * (b - a) + a;
+    },
+
+    smootherstep: function(x, a, b) {
+      x = x * x * x * (x * (x * 6 - 15) + 10);
+      return x * (b - a) + a;
+    },
+
+    clamp: function(x, a, b) {
+      return Math.min(Math.max(x, a), b);
+    },
+
+    sat: function(x) {
+      return Math.min(Math.max(x, 0.0), 1.0);
+    },
+  };
+})();

+ 165 - 0
src/spatial-grid.js

@@ -0,0 +1,165 @@
+import {math} from './math.js';
+
+
+export const spatial_grid = (() => {
+  
+  class SpatialHash_Crap {
+    constructor() {
+      this._clients = new Set();
+    }
+  
+    NewClient(position, dimensions) {
+      const client = {
+        position: position,
+        dimensions: dimensions
+      };
+  
+      this._Insert(client);
+  
+      return client;
+    }
+  
+    UpdateClient(client) {
+    }
+  
+    FindNear(position, dimensions) {
+      const [x, y] = position;
+      const [w, h] = dimensions;
+  
+      const searchBox = {
+        position: position,
+        dimensions: dimensions,
+      };
+
+      const _Overlaps = (a, b) => {
+        const [aw, ah] = a.dimensions;
+        const [bw, bh] = b.dimensions;
+        return (Math.abs(a.position[0] - b.position[0]) * 2 < (aw + bw)) &&
+               (Math.abs(a.position[1] - b.position[1]) * 2 < (ah + bh));
+      };
+
+      const results = [];
+
+      for (let c of this._clients) {
+        if (_Overlaps(searchBox, c)) {
+          results.push(c);
+        }
+      }
+
+      return results;
+    }
+  
+    _Insert(client) {
+      this._clients.add(client);
+    }
+  
+    Remove(client) {
+      this._clients.delete(client);
+    }
+  }
+
+  class SpatialHash_Slow {
+    constructor(bounds, dimensions) {
+      const [x, y] = dimensions;
+      this._cells = new Map();
+      this._dimensions = dimensions;
+      this._bounds = bounds;
+    }
+  
+    _GetCellIndex(position) {
+      const x = math.sat((position[0] - this._bounds[0][0]) / (
+          this._bounds[1][0] - this._bounds[0][0]));
+      const y = math.sat((position[1] - this._bounds[0][1]) / (
+          this._bounds[1][1] - this._bounds[0][1]));
+  
+      const xIndex = Math.floor(x * (this._dimensions[0] - 1));
+      const yIndex = Math.floor(y * (this._dimensions[1] - 1));
+  
+      return [xIndex, yIndex];
+    }
+
+    _Key(i1, i2) {
+      return i1 + '.' + i2;
+    }
+  
+    NewClient(position, dimensions) {
+      const client = {
+        position: position,
+        dimensions: dimensions,
+        indices: null,
+      };
+  
+      this._Insert(client);
+  
+      return client;
+    }
+  
+    UpdateClient(client) {
+      this.Remove(client);
+      this._Insert(client);
+    }
+  
+    FindNear(position, bounds) {
+      const [x, y] = position;
+      const [w, h] = bounds;
+  
+      const i1 = this._GetCellIndex([x - w / 2, y - h / 2]);
+      const i2 = this._GetCellIndex([x + w / 2, y + h / 2]);
+  
+      const clients = new Set();
+  
+      for (let x = i1[0], xn = i2[0]; x <= xn; ++x) {
+        for (let y = i1[1], yn = i2[1]; y <= yn; ++y) {
+          const k = this._Key(x, y);
+
+          if (k in this._cells) {
+            for (let v of this._cells[k]) {
+              clients.add(v);
+            }
+          }
+        }
+      }
+      return clients;
+    }
+  
+    _Insert(client) {
+      const [x, y] = client.position;
+      const [w, h] = client.dimensions;
+  
+      const i1 = this._GetCellIndex([x - w / 2, y - h / 2]);
+      const i2 = this._GetCellIndex([x + w / 2, y + h / 2]);
+  
+      client.indices = [i1, i2];
+  
+      for (let x = i1[0], xn = i2[0]; x <= xn; ++x) {
+        for (let y = i1[1], yn = i2[1]; y <= yn; ++y) {
+          const k = this._Key(x, y);
+          if (!(k in this._cells)) {
+            this._cells[k] = new Set();
+          }
+          this._cells[k].add(client);
+        }
+      }
+    }
+  
+    Remove(client) {
+      const [i1, i2] = client.indices;
+  
+      for (let x = i1[0], xn = i2[0]; x <= xn; ++x) {
+        for (let y = i1[1], yn = i2[1]; y <= yn; ++y) {
+          const k = this._Key(x, y);
+
+          this._cells[k].delete(client);
+        }
+      }
+    }
+  }
+
+
+
+  return {
+    SpatialHash_Crap: SpatialHash_Crap,
+    SpatialHash_Slow: SpatialHash_Slow,
+  };
+
+})();