@woosh/meep-engine 2.87.5 → 2.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/meep.cjs +9 -3
- package/build/meep.min.js +1 -1
- package/build/meep.module.js +9 -3
- package/editor/view/ecs/HierarchicalEntityListView.js +1 -1
- package/editor/view/v2/prototypeEditor.js +1 -1
- package/package.json +1 -1
- package/src/core/bvh2/bvh3/BVH.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/BVH.js +4 -2
- package/src/core/bvh2/bvh3/BVH.spec.js +15 -3
- package/src/core/geom/2d/aabb/aabb2_clip_line_cohen_sutherland.d.ts +16 -0
- package/src/core/geom/2d/aabb/aabb2_clip_line_cohen_sutherland.d.ts.map +1 -0
- package/src/core/geom/2d/aabb/aabb2_clip_line_cohen_sutherland.js +149 -0
- package/src/core/geom/2d/aabb/aabb2_intersects_circle.d.ts +13 -0
- package/src/core/geom/2d/aabb/aabb2_intersects_circle.d.ts.map +1 -0
- package/src/core/geom/2d/aabb/aabb2_intersects_circle.js +27 -0
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.d.ts +41 -6
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.d.ts.map +1 -1
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.js +92 -22
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.spec.js +98 -2
- package/src/core/geom/2d/hash-grid/prototypeGridQueries.d.ts +2 -0
- package/src/core/geom/2d/hash-grid/prototypeGridQueries.d.ts.map +1 -0
- package/src/core/geom/2d/hash-grid/prototypeGridQueries.js +387 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_circle.d.ts +12 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_circle.d.ts.map +1 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_circle.js +160 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_circle.spec.d.ts +2 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_circle.spec.d.ts.map +1 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_circle.spec.js +85 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_line.d.ts +14 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_line.d.ts.map +1 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_line.js +132 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_line.spec.d.ts +2 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_line.spec.d.ts.map +1 -0
- package/src/core/geom/2d/hash-grid/shg_query_elements_line.spec.js +102 -0
- package/src/core/geom/2d/line/line2_distance_to_point_sqr.d.ts +11 -0
- package/src/core/geom/2d/line/line2_distance_to_point_sqr.d.ts.map +1 -0
- package/src/core/geom/2d/line/line2_distance_to_point_sqr.js +39 -0
- package/src/core/geom/2d/quad-tree/QuadTreeNode.spec.js +53 -0
- package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.spec.d.ts +2 -0
- package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.spec.d.ts.map +1 -0
- package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.spec.js +86 -0
- package/src/core/geom/2d/quad-tree/qt_query_data_raycast.js +1 -1
- package/src/core/model/ObservedString.d.ts.map +1 -1
- package/src/core/model/ObservedString.js +1 -1
- package/src/engine/animation/curve/ecd_bind_animation_curve.js +1 -1
- package/src/engine/ecs/name/Name.d.ts +25 -0
- package/src/engine/ecs/name/Name.d.ts.map +1 -0
- package/src/engine/ecs/name/Name.js +42 -0
- package/src/engine/ecs/name/NameSerializationAdapter.d.ts +18 -0
- package/src/engine/ecs/name/NameSerializationAdapter.d.ts.map +1 -0
- package/src/engine/ecs/name/NameSerializationAdapter.js +29 -0
- package/src/engine/ecs/transform/Transform.d.ts +2 -2
- package/src/engine/graphics/canvas/canvas2d_draw_grid.js +1 -1
- package/src/engine/graphics/ecs/mesh-v2/three_object_to_entity_composition.js +1 -1
- package/src/core/geom/2d/hash-grid/shg_query_raycast.d.ts +0 -14
- package/src/core/geom/2d/hash-grid/shg_query_raycast.d.ts.map +0 -1
- package/src/core/geom/2d/hash-grid/shg_query_raycast.js +0 -21
|
@@ -115,7 +115,7 @@ test("add 3 element and remove in same order, expect grid to be empty", () => {
|
|
|
115
115
|
expect(grid.is_cell_empty(1, 1)).toBe(true);
|
|
116
116
|
});
|
|
117
117
|
|
|
118
|
-
test.skip("performance, insertion 1m random", () => {
|
|
118
|
+
test.skip("query performance, line insertion 1m random", () => {
|
|
119
119
|
const grid = new SpatialHashGrid(100, 100, 1);
|
|
120
120
|
|
|
121
121
|
const random = seededRandom(42);
|
|
@@ -154,5 +154,101 @@ test.skip("performance, insertion 1m random", () => {
|
|
|
154
154
|
|
|
155
155
|
const duration = performance.now() - t0;
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
const duration_seconds = duration * 1e-3;
|
|
158
|
+
|
|
159
|
+
console.log(`Duration ${duration_seconds}s, ${(N / duration_seconds).toFixed(2)} records per second`);
|
|
160
|
+
});
|
|
161
|
+
test("remove element from grid", () => {
|
|
162
|
+
|
|
163
|
+
const grid = new SpatialHashGrid(2, 2, 4);
|
|
164
|
+
|
|
165
|
+
const el = grid.element_allocate();
|
|
166
|
+
|
|
167
|
+
grid.element_set_user_data(el, 7);
|
|
168
|
+
grid.element_set_bounds_primitive(el, 0.5, 0.5, 1, 1);
|
|
169
|
+
|
|
170
|
+
grid.element_insert(el);
|
|
171
|
+
grid.element_remove(el);
|
|
172
|
+
|
|
173
|
+
expect(grid.is_cell_empty(0, 0)).toBe(true);
|
|
174
|
+
expect(grid.is_cell_empty(0, 1)).toBe(true);
|
|
175
|
+
expect(grid.is_cell_empty(1, 0)).toBe(true);
|
|
176
|
+
expect(grid.is_cell_empty(1, 1)).toBe(true);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("add 3 element and remove in same order, expect grid to be empty", () => {
|
|
180
|
+
|
|
181
|
+
const grid = new SpatialHashGrid(2, 2, 4);
|
|
182
|
+
|
|
183
|
+
const a = grid.element_allocate();
|
|
184
|
+
|
|
185
|
+
grid.element_set_user_data(a, 3);
|
|
186
|
+
grid.element_set_bounds_primitive(a, 0.5, 0.5, 1, 1);
|
|
187
|
+
|
|
188
|
+
const b = grid.element_allocate();
|
|
189
|
+
|
|
190
|
+
grid.element_set_user_data(b, 7);
|
|
191
|
+
grid.element_set_bounds_primitive(b, 0.5, 0.5, 1, 1);
|
|
192
|
+
|
|
193
|
+
const c = grid.element_allocate();
|
|
194
|
+
|
|
195
|
+
grid.element_set_user_data(c, 11);
|
|
196
|
+
grid.element_set_bounds_primitive(c, 0.5, 0.5, 1, 1);
|
|
197
|
+
|
|
198
|
+
grid.element_insert(a);
|
|
199
|
+
grid.element_insert(b);
|
|
200
|
+
grid.element_insert(c);
|
|
201
|
+
|
|
202
|
+
grid.element_remove(a);
|
|
203
|
+
grid.element_remove(b);
|
|
204
|
+
grid.element_remove(c);
|
|
205
|
+
|
|
206
|
+
expect(grid.is_cell_empty(0, 0)).toBe(true);
|
|
207
|
+
expect(grid.is_cell_empty(0, 1)).toBe(true);
|
|
208
|
+
expect(grid.is_cell_empty(1, 0)).toBe(true);
|
|
209
|
+
expect(grid.is_cell_empty(1, 1)).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("performance, insertion 1m random", () => {
|
|
213
|
+
const grid = new SpatialHashGrid(100, 100, 1);
|
|
214
|
+
|
|
215
|
+
const random = seededRandom(42);
|
|
216
|
+
|
|
217
|
+
const N = 1000000;
|
|
218
|
+
|
|
219
|
+
const elements = new Uint32Array(N);
|
|
220
|
+
|
|
221
|
+
for (let i = 0; i < N; i++) {
|
|
222
|
+
|
|
223
|
+
const position_x = randomFloatBetween(random, 0, 98);
|
|
224
|
+
const position_y = randomFloatBetween(random, 0, 98);
|
|
225
|
+
|
|
226
|
+
const size_x = randomFloatBetween(random, 0.1, 2);
|
|
227
|
+
const size_y = randomFloatBetween(random, 0.1, 2);
|
|
228
|
+
|
|
229
|
+
const element = grid.element_allocate();
|
|
230
|
+
|
|
231
|
+
grid.element_set_user_data(element, i);
|
|
232
|
+
grid.element_set_bounds_primitive(element,
|
|
233
|
+
position_x,
|
|
234
|
+
position_y,
|
|
235
|
+
position_x + size_x,
|
|
236
|
+
position_y + size_y
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
elements[i] = element;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// perform insertion
|
|
243
|
+
const t0 = performance.now();
|
|
244
|
+
|
|
245
|
+
for (let i = 0; i < N; i++) {
|
|
246
|
+
grid.element_insert(elements[i]);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const duration = performance.now() - t0;
|
|
250
|
+
|
|
251
|
+
const duration_seconds = duration * 1e-3;
|
|
252
|
+
|
|
253
|
+
console.log(`Duration ${duration_seconds}s, ${(N / duration_seconds).toFixed(2)} records per second`);
|
|
158
254
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prototypeGridQueries.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/2d/hash-grid/prototypeGridQueries.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import requestAnimationFrame from "dat.gui/src/dat/utils/requestAnimationFrame.js";
|
|
2
|
+
import { canvas2d_draw_grid } from "../../../../engine/graphics/canvas/canvas2d_draw_grid.js";
|
|
3
|
+
import { MouseEvents } from "../../../../engine/input/devices/events/MouseEvents.js";
|
|
4
|
+
import { PointerDevice } from "../../../../engine/input/devices/PointerDevice.js";
|
|
5
|
+
import { CanvasView } from "../../../../view/elements/CanvasView.js";
|
|
6
|
+
import { FLT_EPSILON_32 } from "../../../math/FLT_EPSILON_32.js";
|
|
7
|
+
import { randomIntegerBetween } from "../../../math/random/randomIntegerBetween.js";
|
|
8
|
+
import { seededRandom } from "../../../math/random/seededRandom.js";
|
|
9
|
+
import { aabb2_clip_line_cohen_sutherland } from "../aabb/aabb2_clip_line_cohen_sutherland.js";
|
|
10
|
+
import { shg_query_elements_circle } from "./shg_query_elements_circle.js";
|
|
11
|
+
import { shg_query_elements_line } from "./shg_query_elements_line.js";
|
|
12
|
+
import { SpatialHashGrid } from "./SpatialHashGrid.js";
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const canvas = new CanvasView();
|
|
16
|
+
|
|
17
|
+
canvas.size.set(window.innerWidth, window.innerHeight);
|
|
18
|
+
|
|
19
|
+
canvas.link();
|
|
20
|
+
|
|
21
|
+
document.body.style.margin = 0;
|
|
22
|
+
document.body.style.overflow = "hidden";
|
|
23
|
+
document.body.appendChild(canvas.el);
|
|
24
|
+
|
|
25
|
+
const selected_boxes = [1, 3, 9]
|
|
26
|
+
|
|
27
|
+
const grid = new SpatialHashGrid(16, 16, 32);
|
|
28
|
+
|
|
29
|
+
const render_scale = 2;
|
|
30
|
+
|
|
31
|
+
const BOX_COUNT = 50;
|
|
32
|
+
|
|
33
|
+
const boxes = new Float32Array(BOX_COUNT * 4);
|
|
34
|
+
|
|
35
|
+
const random = seededRandom();
|
|
36
|
+
|
|
37
|
+
function populate_boxes(
|
|
38
|
+
random,
|
|
39
|
+
bounds_x0, bounds_y0, bounds_x1, bounds_y1
|
|
40
|
+
) {
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < BOX_COUNT; i++) {
|
|
43
|
+
|
|
44
|
+
const offset = i * 4;
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
const width = randomIntegerBetween(random, 10, 20);
|
|
48
|
+
const height = randomIntegerBetween(random, 10, 20);
|
|
49
|
+
|
|
50
|
+
const x0 = randomIntegerBetween(random, Math.ceil(bounds_x0), Math.floor(bounds_x1 - width));
|
|
51
|
+
const y0 = randomIntegerBetween(random, Math.ceil(bounds_y0), Math.floor(bounds_y1 - height));
|
|
52
|
+
|
|
53
|
+
const x1 = x0 + width;
|
|
54
|
+
const y1 = y0 + height;
|
|
55
|
+
|
|
56
|
+
boxes[offset] = x0;
|
|
57
|
+
boxes[offset + 1] = y0;
|
|
58
|
+
boxes[offset + 2] = x1;
|
|
59
|
+
boxes[offset + 3] = y1;
|
|
60
|
+
|
|
61
|
+
// add box to grid
|
|
62
|
+
const box = grid.element_allocate();
|
|
63
|
+
|
|
64
|
+
grid.element_set_user_data(box, i);
|
|
65
|
+
grid.element_set_bounds_primitive(box, x0, y0, x1, y1);
|
|
66
|
+
|
|
67
|
+
grid.element_insert(box);
|
|
68
|
+
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function init() {
|
|
73
|
+
populate_boxes(
|
|
74
|
+
random,
|
|
75
|
+
0, 0,
|
|
76
|
+
grid.size_x * grid.scale, grid.size_y * grid.scale
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
query.link(canvas);
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function render_boxes() {
|
|
84
|
+
const ctx = canvas.context2d;
|
|
85
|
+
|
|
86
|
+
// draw background boxes
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < BOX_COUNT; i++) {
|
|
89
|
+
|
|
90
|
+
if (selected_boxes.includes(i)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const offset = i * 4;
|
|
95
|
+
const x0 = boxes[offset] * render_scale;
|
|
96
|
+
const y0 = boxes[offset + 1] * render_scale;
|
|
97
|
+
const x1 = boxes[offset + 2] * render_scale;
|
|
98
|
+
const y1 = boxes[offset + 3] * render_scale;
|
|
99
|
+
|
|
100
|
+
const w = x1 - x0;
|
|
101
|
+
const h = y1 - y0;
|
|
102
|
+
|
|
103
|
+
ctx.fillStyle = "rgba(0,184,255,1)";
|
|
104
|
+
ctx.fillRect(x0, y0, w, h);
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// draw selected boxes
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < BOX_COUNT; i++) {
|
|
111
|
+
if (!selected_boxes.includes(i)) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const offset = i * 4;
|
|
116
|
+
const x0 = boxes[offset] * render_scale;
|
|
117
|
+
const y0 = boxes[offset + 1] * render_scale;
|
|
118
|
+
const x1 = boxes[offset + 2] * render_scale;
|
|
119
|
+
const y1 = boxes[offset + 3] * render_scale;
|
|
120
|
+
|
|
121
|
+
const w = x1 - x0;
|
|
122
|
+
const h = y1 - y0;
|
|
123
|
+
|
|
124
|
+
ctx.fillStyle = "rgba(238,0,255,1)";
|
|
125
|
+
// ctx.strokeStyle = "rgb(238,0,255)";
|
|
126
|
+
// ctx.lineWidth = 4;
|
|
127
|
+
|
|
128
|
+
// ctx.strokeRect(x0, y0, w, h);
|
|
129
|
+
ctx.fillRect(x0, y0, w, h);
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function render_grid() {
|
|
135
|
+
const scale = grid.scale * render_scale;
|
|
136
|
+
canvas2d_draw_grid({
|
|
137
|
+
ctx: canvas.context2d,
|
|
138
|
+
width: grid.size_x * scale + 1,
|
|
139
|
+
height: grid.size_y * scale + 1,
|
|
140
|
+
spacing: scale,
|
|
141
|
+
color: "rgba(0,0,0,0.2)"
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
class Query {
|
|
146
|
+
#view = null
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
*
|
|
150
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
151
|
+
* @param {SpatialHashGrid} grid
|
|
152
|
+
* @param {number} render_scale
|
|
153
|
+
*/
|
|
154
|
+
render(ctx, grid, render_scale) {
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
*
|
|
160
|
+
* @param {CanvasRenderingContext2D} ctx
|
|
161
|
+
* @param {SpatialHashGrid} grid
|
|
162
|
+
* @param {number} render_scale
|
|
163
|
+
*/
|
|
164
|
+
render_debug(ctx, grid, render_scale) {
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
*
|
|
170
|
+
* @param {number[]} result
|
|
171
|
+
* @param {number} result_offset
|
|
172
|
+
* @param {SpatialHashGrid} grid
|
|
173
|
+
* @returns {number}
|
|
174
|
+
*/
|
|
175
|
+
run(result, result_offset, grid) {
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
*
|
|
181
|
+
* @param {View} view
|
|
182
|
+
*/
|
|
183
|
+
link(view) {
|
|
184
|
+
if (this.#view !== view) {
|
|
185
|
+
this.unlink();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.#view = view;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
unlink() {
|
|
192
|
+
this.#view = null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
class LineQuery extends Query {
|
|
197
|
+
#line = [0, 0, 0, 0];
|
|
198
|
+
|
|
199
|
+
render_debug(ctx, grid, render_scale) {
|
|
200
|
+
|
|
201
|
+
const visited = shg_query_elements_line.visited;
|
|
202
|
+
if (visited === undefined) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const n = visited.length;
|
|
206
|
+
|
|
207
|
+
const scale = grid.scale * render_scale;
|
|
208
|
+
|
|
209
|
+
for (let i = 0; i < n; i += 2) {
|
|
210
|
+
const x = visited[i];
|
|
211
|
+
const y = visited[i + 1];
|
|
212
|
+
|
|
213
|
+
ctx.fillStyle = "rgba(0,0,255,0.3)";
|
|
214
|
+
|
|
215
|
+
ctx.fillRect(x * scale, y * scale, scale, scale);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
render(ctx, grid, render_scale) {
|
|
221
|
+
const line = this.#line;
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
ctx.lineWidth = 2;
|
|
225
|
+
ctx.strokeStyle = "rgb(255,47,0)";
|
|
226
|
+
|
|
227
|
+
ctx.beginPath();
|
|
228
|
+
ctx.moveTo(line[0] * render_scale, line[1] * render_scale);
|
|
229
|
+
ctx.lineTo(line[2] * render_scale, line[3] * render_scale);
|
|
230
|
+
ctx.closePath();
|
|
231
|
+
ctx.stroke();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
run(result, result_offset, grid) {
|
|
235
|
+
const line = this.#line;
|
|
236
|
+
const clipped_line = [];
|
|
237
|
+
|
|
238
|
+
if (!aabb2_clip_line_cohen_sutherland(
|
|
239
|
+
clipped_line, 0,
|
|
240
|
+
0, 0, grid.size_x * grid.scale - FLT_EPSILON_32, grid.size_y * grid.scale - FLT_EPSILON_32,
|
|
241
|
+
line[0], line[1], line[2], line[3]
|
|
242
|
+
)) {
|
|
243
|
+
// line is outsize of the grid
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const els = [];
|
|
248
|
+
const hits = shg_query_elements_line(
|
|
249
|
+
els, 0,
|
|
250
|
+
grid,
|
|
251
|
+
clipped_line[0], clipped_line[1],
|
|
252
|
+
clipped_line[2], clipped_line[3]
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
for (let i = 0; i < hits; i++) {
|
|
257
|
+
const id = grid.element_get_user_data(els[i]);
|
|
258
|
+
|
|
259
|
+
result[i + result_offset] = id;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return hits;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
link(view) {
|
|
266
|
+
super.link(view);
|
|
267
|
+
|
|
268
|
+
const line = this.#line;
|
|
269
|
+
let anchor = 0;
|
|
270
|
+
|
|
271
|
+
view.el.addEventListener(MouseEvents.Click, (evt) => {
|
|
272
|
+
const index = anchor * 2;
|
|
273
|
+
|
|
274
|
+
line[index + 0] = evt.clientX / render_scale;
|
|
275
|
+
line[index + 1] = evt.clientY / render_scale;
|
|
276
|
+
|
|
277
|
+
anchor = (anchor + 1) % 2;
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
class CircleQuery extends Query {
|
|
284
|
+
#circle = [0, 0, 0];
|
|
285
|
+
|
|
286
|
+
link(view) {
|
|
287
|
+
super.link(view);
|
|
288
|
+
|
|
289
|
+
const pointer = new PointerDevice(view.el);
|
|
290
|
+
|
|
291
|
+
pointer.start();
|
|
292
|
+
|
|
293
|
+
pointer.on.drag.add((position, origin) => {
|
|
294
|
+
this.#circle[0] = origin.x / render_scale;
|
|
295
|
+
this.#circle[1] = origin.y / render_scale;
|
|
296
|
+
|
|
297
|
+
this.#circle[2] = position.distanceTo(origin) / render_scale;
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
render_debug(ctx, grid, render_scale) {
|
|
302
|
+
|
|
303
|
+
const visited = shg_query_elements_circle.visited;
|
|
304
|
+
if (visited === undefined) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const n = visited.length;
|
|
308
|
+
|
|
309
|
+
const scale = grid.scale * render_scale;
|
|
310
|
+
|
|
311
|
+
for (let i = 0; i < n; i += 2) {
|
|
312
|
+
const x = visited[i];
|
|
313
|
+
const y = visited[i + 1];
|
|
314
|
+
|
|
315
|
+
ctx.fillStyle = "rgba(0,0,255,0.3)";
|
|
316
|
+
|
|
317
|
+
ctx.fillRect(x * scale, y * scale, scale, scale);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
render(ctx, grid, render_scale) {
|
|
323
|
+
|
|
324
|
+
const c = this.#circle;
|
|
325
|
+
ctx.beginPath();
|
|
326
|
+
ctx.arc(c[0] * render_scale, c[1] * render_scale, c[2] * render_scale, 0, Math.PI * 2);
|
|
327
|
+
ctx.closePath();
|
|
328
|
+
|
|
329
|
+
ctx.lineWidth = 3;
|
|
330
|
+
ctx.strokeStyle = "red";
|
|
331
|
+
ctx.stroke();
|
|
332
|
+
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
run(result, result_offset, grid) {
|
|
336
|
+
const els = [];
|
|
337
|
+
const hits = shg_query_elements_circle(
|
|
338
|
+
els, 0,
|
|
339
|
+
grid,
|
|
340
|
+
this.#circle[0], this.#circle[1],
|
|
341
|
+
this.#circle[2]
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
for (let i = 0; i < hits; i++) {
|
|
346
|
+
const id = grid.element_get_user_data(els[i]);
|
|
347
|
+
|
|
348
|
+
result[i + result_offset] = id;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return hits;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const query = new CircleQuery();
|
|
356
|
+
// const query = new LineQuery();
|
|
357
|
+
|
|
358
|
+
function render() {
|
|
359
|
+
|
|
360
|
+
canvas.context2d.clearRect(0, 0, canvas.size.x, canvas.size.y);
|
|
361
|
+
|
|
362
|
+
render_grid();
|
|
363
|
+
query.render_debug(canvas.context2d, grid, render_scale);
|
|
364
|
+
render_boxes();
|
|
365
|
+
query.render(canvas.context2d, grid, render_scale);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
function update() {
|
|
370
|
+
|
|
371
|
+
requestAnimationFrame(update);
|
|
372
|
+
|
|
373
|
+
selected_boxes.splice(0, selected_boxes.length)
|
|
374
|
+
|
|
375
|
+
query.run(selected_boxes, 0, grid);
|
|
376
|
+
|
|
377
|
+
render();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
init();
|
|
381
|
+
update();
|
|
382
|
+
|
|
383
|
+
window.addEventListener("resize", () => {
|
|
384
|
+
|
|
385
|
+
canvas.size.set(window.innerWidth, window.innerHeight);
|
|
386
|
+
|
|
387
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {number[]} result
|
|
4
|
+
* @param {number} result_offset
|
|
5
|
+
* @param {SpatialHashGrid} grid
|
|
6
|
+
* @param {number} center_x
|
|
7
|
+
* @param {number} center_y
|
|
8
|
+
* @param {number} radius
|
|
9
|
+
* @returns {number}
|
|
10
|
+
*/
|
|
11
|
+
export function shg_query_elements_circle(result: number[], result_offset: number, grid: SpatialHashGrid, center_x: number, center_y: number, radius: number): number;
|
|
12
|
+
//# sourceMappingURL=shg_query_elements_circle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shg_query_elements_circle.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/2d/hash-grid/shg_query_elements_circle.js"],"names":[],"mappings":"AA4CA;;;;;;;;;GASG;AACH,kDARW,MAAM,EAAE,iBACR,MAAM,mCAEN,MAAM,YACN,MAAM,UACN,MAAM,GACJ,MAAM,CAyGlB"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { fabsf } from "../../../math/fabsf.js";
|
|
2
|
+
import { max2 } from "../../../math/max2.js";
|
|
3
|
+
import { min2 } from "../../../math/min2.js";
|
|
4
|
+
import { aabb2_distance_sqr_to_point } from "../aabb/aabb2_distance_sqr_to_point.js";
|
|
5
|
+
import {
|
|
6
|
+
COLUMN_ELEMENT_X0,
|
|
7
|
+
COLUMN_ELEMENT_X1,
|
|
8
|
+
COLUMN_ELEMENT_Y0,
|
|
9
|
+
COLUMN_ELEMENT_Y1,
|
|
10
|
+
NULL_POINTER
|
|
11
|
+
} from "./SpatialHashGrid.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @see https://stackoverflow.com/questions/47639413/algorithm-for-accurate-detection-of-overlap-between-a-square-and-a-circle
|
|
15
|
+
* @param square_center_x
|
|
16
|
+
* @param square_center_y
|
|
17
|
+
* @param circle_center_x
|
|
18
|
+
* @param circle_center_y
|
|
19
|
+
* @param circle_radius
|
|
20
|
+
* @return {boolean}
|
|
21
|
+
*/
|
|
22
|
+
function unit_square_intersects_circle(
|
|
23
|
+
square_center_x,
|
|
24
|
+
square_center_y,
|
|
25
|
+
circle_center_x,
|
|
26
|
+
circle_center_y,
|
|
27
|
+
circle_radius
|
|
28
|
+
) {
|
|
29
|
+
|
|
30
|
+
const x = fabsf(circle_center_x - square_center_x) - 0.5;
|
|
31
|
+
const y = fabsf(circle_center_y - square_center_y) - 0.5;
|
|
32
|
+
|
|
33
|
+
if (x > 0) {
|
|
34
|
+
if (y > 0) {
|
|
35
|
+
return x * x + y * y < circle_radius * circle_radius;
|
|
36
|
+
} else {
|
|
37
|
+
return x < circle_radius;
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
return y < circle_radius;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @param {number[]} result
|
|
48
|
+
* @param {number} result_offset
|
|
49
|
+
* @param {SpatialHashGrid} grid
|
|
50
|
+
* @param {number} center_x
|
|
51
|
+
* @param {number} center_y
|
|
52
|
+
* @param {number} radius
|
|
53
|
+
* @returns {number}
|
|
54
|
+
*/
|
|
55
|
+
export function shg_query_elements_circle(
|
|
56
|
+
result, result_offset,
|
|
57
|
+
grid,
|
|
58
|
+
center_x, center_y,
|
|
59
|
+
radius
|
|
60
|
+
) {
|
|
61
|
+
|
|
62
|
+
// shg_query_elements_circle.visited.splice(0, shg_query_elements_circle.visited.length); // DEBUG
|
|
63
|
+
|
|
64
|
+
const scale_inverse = grid.scale_inverse;
|
|
65
|
+
const grid_size_x = grid.size_x;
|
|
66
|
+
const grid_size_y = grid.size_y;
|
|
67
|
+
|
|
68
|
+
// rasterize circle aabb
|
|
69
|
+
|
|
70
|
+
const grid_center_x = center_x * scale_inverse;
|
|
71
|
+
const grid_center_y = center_y * scale_inverse;
|
|
72
|
+
|
|
73
|
+
const grid_radius = radius * scale_inverse;
|
|
74
|
+
|
|
75
|
+
const aabb_x0 = max2(
|
|
76
|
+
Math.floor(grid_center_x - grid_radius),
|
|
77
|
+
0
|
|
78
|
+
);
|
|
79
|
+
const aabb_y0 = max2(
|
|
80
|
+
Math.floor(grid_center_y - grid_radius),
|
|
81
|
+
0
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const aabb_x1 = min2(
|
|
85
|
+
Math.floor(grid_center_x + grid_radius),
|
|
86
|
+
grid_size_x - 1
|
|
87
|
+
);
|
|
88
|
+
const aabb_y1 = min2(
|
|
89
|
+
Math.floor(grid_center_y + grid_radius),
|
|
90
|
+
grid_size_y - 1
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
let result_count = 0;
|
|
94
|
+
|
|
95
|
+
const radius2 = radius * radius;
|
|
96
|
+
|
|
97
|
+
const element_pool = grid.element_pool;
|
|
98
|
+
const element_float32 = element_pool.data_float32;
|
|
99
|
+
|
|
100
|
+
for (let y = aabb_y0; y <= aabb_y1; y++) {
|
|
101
|
+
for (let x = aabb_x0; x <= aabb_x1; x++) {
|
|
102
|
+
|
|
103
|
+
if (unit_square_intersects_circle(
|
|
104
|
+
x + 0.5,
|
|
105
|
+
y + 0.5,
|
|
106
|
+
grid_center_x,
|
|
107
|
+
grid_center_y,
|
|
108
|
+
grid_radius
|
|
109
|
+
)) {
|
|
110
|
+
|
|
111
|
+
// cell is inside the circle
|
|
112
|
+
|
|
113
|
+
// shg_query_elements_circle.visited.push(x, y); // DEBUG
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
const cell_index = grid.cell_position_to_index(x, y);
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
let node = grid.cell_get_first_node(cell_index);
|
|
120
|
+
|
|
121
|
+
while (node !== NULL_POINTER) {
|
|
122
|
+
|
|
123
|
+
const element = grid.node_get_element(node);
|
|
124
|
+
|
|
125
|
+
// check element bounds
|
|
126
|
+
const element_address = element_pool.element_word(element);
|
|
127
|
+
|
|
128
|
+
const element_x0 = element_float32[element_address + COLUMN_ELEMENT_X0];
|
|
129
|
+
const element_y0 = element_float32[element_address + COLUMN_ELEMENT_Y0];
|
|
130
|
+
const element_x1 = element_float32[element_address + COLUMN_ELEMENT_X1];
|
|
131
|
+
const element_y1 = element_float32[element_address + COLUMN_ELEMENT_Y1];
|
|
132
|
+
|
|
133
|
+
const distance_to_center = aabb2_distance_sqr_to_point(
|
|
134
|
+
element_x0, element_y0,
|
|
135
|
+
element_x1, element_y1,
|
|
136
|
+
center_x, center_y
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (distance_to_center <= radius2) {
|
|
140
|
+
// intersection with the element
|
|
141
|
+
result[result_offset + result_count] = element;
|
|
142
|
+
|
|
143
|
+
result_count++;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
// read out next element in the linked list
|
|
148
|
+
node = grid.node_get_same_cell_next_node(node);
|
|
149
|
+
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return result_count;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// shg_query_elements_circle.visited = []; // DEBUG
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shg_query_elements_circle.spec.d.ts","sourceRoot":"","sources":["../../../../../../src/core/geom/2d/hash-grid/shg_query_elements_circle.spec.js"],"names":[],"mappings":""}
|