@navaramap/three 0.1.0 → 0.1.1
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/README.md +4 -4
- package/dist/index.d.ts +91 -26
- package/dist/index.js +261 -213
- package/dist/index.js.map +1 -1
- package/dist/index.umd.cjs +31 -31
- package/dist/index.umd.cjs.map +1 -1
- package/package.json +12 -12
- package/src/core/BaseDesc.ts +25 -1
- package/src/core/MeshDesc.ts +2 -1
- package/src/index.ts +174 -77
- package/src/material/enhancer/model/modelBaseEnhancer/shader.ts +2 -2
- package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.ts +2 -2
- package/src/pick/pickHelper.test.ts +182 -55
- package/src/pick/pickHelper.ts +144 -56
- package/src/plugins/AttributionPlugin/index.ts +1 -1
|
@@ -6,7 +6,7 @@ import type { MeshCache } from "../type";
|
|
|
6
6
|
import { isClickGesture, PickHelper } from "./pickHelper";
|
|
7
7
|
|
|
8
8
|
describe("isClickGesture", () => {
|
|
9
|
-
it("accepts a
|
|
9
|
+
it("accepts a pointerup at the exact pointerdown position", () => {
|
|
10
10
|
expect(isClickGesture(new Vector2(100, 100), new Vector2(100, 100))).toBe(
|
|
11
11
|
true,
|
|
12
12
|
);
|
|
@@ -43,6 +43,15 @@ describe("isClickGesture", () => {
|
|
|
43
43
|
// Each axis moved less than 5px but the combined travel exceeds it.
|
|
44
44
|
expect(isClickGesture(new Vector2(0, 0), new Vector2(4, 4))).toBe(false);
|
|
45
45
|
});
|
|
46
|
+
|
|
47
|
+
it("accepts a wider travel when given an explicit tolerance (touch taps)", () => {
|
|
48
|
+
expect(isClickGesture(new Vector2(0, 0), new Vector2(20, 0), 30)).toBe(
|
|
49
|
+
true,
|
|
50
|
+
);
|
|
51
|
+
expect(isClickGesture(new Vector2(0, 0), new Vector2(30, 0), 30)).toBe(
|
|
52
|
+
false,
|
|
53
|
+
);
|
|
54
|
+
});
|
|
46
55
|
});
|
|
47
56
|
|
|
48
57
|
/**
|
|
@@ -53,6 +62,9 @@ describe("isClickGesture", () => {
|
|
|
53
62
|
const createRendererStub = () => {
|
|
54
63
|
const state = {
|
|
55
64
|
pixel: [0, 0, 0] as [number, number, number],
|
|
65
|
+
/** Per-pixel override for the readback, keyed by device coords. */
|
|
66
|
+
pixelAt: undefined as
|
|
67
|
+
((x: number, y: number) => [number, number, number]) | undefined,
|
|
56
68
|
renderCount: 0,
|
|
57
69
|
scissors: [] as { x: number; y: number; w: number; h: number }[],
|
|
58
70
|
};
|
|
@@ -75,14 +87,22 @@ const createRendererStub = () => {
|
|
|
75
87
|
setScissorTest: () => undefined,
|
|
76
88
|
readRenderTargetPixels: (
|
|
77
89
|
_target: unknown,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
x: number,
|
|
91
|
+
y: number,
|
|
92
|
+
w: number,
|
|
93
|
+
h: number,
|
|
82
94
|
buffer: Uint8Array,
|
|
83
95
|
) => {
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
for (let row = 0; row < h; row++) {
|
|
97
|
+
for (let col = 0; col < w; col++) {
|
|
98
|
+
const i = (row * w + col) * 4;
|
|
99
|
+
const [r, g, b] = state.pixelAt?.(x + col, y + row) ?? state.pixel;
|
|
100
|
+
buffer[i] = r;
|
|
101
|
+
buffer[i + 1] = g;
|
|
102
|
+
buffer[i + 2] = b;
|
|
103
|
+
buffer[i + 3] = 255;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
86
106
|
},
|
|
87
107
|
} as unknown as WebGLRenderer;
|
|
88
108
|
return { renderer, state };
|
|
@@ -137,59 +157,165 @@ describe("PickHelper", () => {
|
|
|
137
157
|
);
|
|
138
158
|
helper.enablePick(true);
|
|
139
159
|
|
|
140
|
-
const
|
|
160
|
+
const pointer = (
|
|
161
|
+
type: string,
|
|
162
|
+
x: number,
|
|
163
|
+
y: number,
|
|
164
|
+
init?: PointerEventInit,
|
|
165
|
+
) =>
|
|
141
166
|
element.dispatchEvent(
|
|
142
|
-
new
|
|
167
|
+
new PointerEvent(type, {
|
|
168
|
+
clientX: x,
|
|
169
|
+
clientY: y,
|
|
170
|
+
isPrimary: true,
|
|
171
|
+
pointerType: "mouse",
|
|
172
|
+
...init,
|
|
173
|
+
}),
|
|
143
174
|
);
|
|
144
|
-
return { helper, element, state, picks, hovers, gates,
|
|
175
|
+
return { helper, element, state, picks, hovers, gates, pointer };
|
|
145
176
|
};
|
|
146
177
|
|
|
147
178
|
describe("click picking", () => {
|
|
148
179
|
it("decodes the batch id from the pick render", () => {
|
|
149
|
-
const { state, picks,
|
|
180
|
+
const { state, picks, pointer } = setup();
|
|
150
181
|
state.pixel = [0, 1, 2]; // (0 << 16) + (1 << 8) + 2
|
|
151
182
|
|
|
152
|
-
|
|
153
|
-
|
|
183
|
+
pointer("pointerdown", 100, 100);
|
|
184
|
+
pointer("pointerup", 100, 100);
|
|
154
185
|
|
|
155
186
|
expect(picks).toEqual([[258]]);
|
|
156
187
|
expect(state.renderCount).toBe(1);
|
|
157
188
|
});
|
|
158
189
|
|
|
159
190
|
it("reports an empty pick for a miss (batch id 0)", () => {
|
|
160
|
-
const { picks,
|
|
191
|
+
const { picks, pointer } = setup();
|
|
161
192
|
|
|
162
|
-
|
|
163
|
-
|
|
193
|
+
pointer("pointerdown", 100, 100);
|
|
194
|
+
pointer("pointerup", 100, 100);
|
|
164
195
|
|
|
165
196
|
expect(picks).toEqual([[]]);
|
|
166
197
|
});
|
|
167
198
|
|
|
168
|
-
it("limits GPU work to the
|
|
169
|
-
const { state,
|
|
199
|
+
it("limits GPU work to the search window via scissor", () => {
|
|
200
|
+
const { state, pointer } = setup();
|
|
170
201
|
|
|
171
|
-
|
|
172
|
-
|
|
202
|
+
pointer("pointerdown", 30, 40);
|
|
203
|
+
pointer("pointerup", 30, 40);
|
|
173
204
|
|
|
174
|
-
//
|
|
175
|
-
|
|
205
|
+
// A (2 * PICK_RADIUS + 1)² window centered on the click; y is flipped
|
|
206
|
+
// into WebGL space: 600 - 1 - 40.
|
|
207
|
+
expect(state.scissors).toEqual([{ x: 27, y: 556, w: 7, h: 7 }]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("picks the non-zero id closest to the pointer within the window", () => {
|
|
211
|
+
const { state, picks, pointer } = setup();
|
|
212
|
+
// Two hits inside the window around (100, 100) (device y = 499):
|
|
213
|
+
// id 5 at 3px away, id 9 at 1px away. The closer one wins.
|
|
214
|
+
state.pixelAt = (x, y) => {
|
|
215
|
+
if (x === 103 && y === 499) return [0, 0, 5];
|
|
216
|
+
if (x === 101 && y === 499) return [0, 0, 9];
|
|
217
|
+
return [0, 0, 0];
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
pointer("pointerdown", 100, 100);
|
|
221
|
+
pointer("pointerup", 100, 100);
|
|
222
|
+
|
|
223
|
+
expect(picks).toEqual([[9]]);
|
|
176
224
|
});
|
|
177
225
|
|
|
178
226
|
it("does not pick when the gesture is a drag", () => {
|
|
179
|
-
const { state, picks,
|
|
227
|
+
const { state, picks, pointer } = setup();
|
|
180
228
|
|
|
181
|
-
|
|
182
|
-
|
|
229
|
+
pointer("pointerdown", 100, 100);
|
|
230
|
+
pointer("pointerup", 150, 150);
|
|
183
231
|
|
|
184
232
|
expect(picks).toEqual([]);
|
|
185
233
|
expect(state.renderCount).toBe(0);
|
|
186
234
|
});
|
|
187
235
|
|
|
188
236
|
it("skips the pick render entirely when nobody listens", () => {
|
|
189
|
-
const { state, picks,
|
|
237
|
+
const { state, picks, pointer } = setup({ clickable: false });
|
|
238
|
+
|
|
239
|
+
pointer("pointerdown", 100, 100);
|
|
240
|
+
pointer("pointerup", 100, 100);
|
|
241
|
+
|
|
242
|
+
expect(picks).toEqual([]);
|
|
243
|
+
expect(state.renderCount).toBe(0);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("touch tap picking", () => {
|
|
248
|
+
const touch = { pointerType: "touch" } as const;
|
|
249
|
+
|
|
250
|
+
it("picks on a single-finger tap", () => {
|
|
251
|
+
const { state, picks, pointer } = setup();
|
|
252
|
+
state.pixel = [0, 0, 9];
|
|
253
|
+
|
|
254
|
+
pointer("pointerdown", 100, 100, touch);
|
|
255
|
+
pointer("pointerup", 100, 100, touch);
|
|
256
|
+
|
|
257
|
+
expect(picks).toEqual([[9]]);
|
|
258
|
+
expect(state.renderCount).toBe(1);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("tolerates finger jitter beyond the mouse click tolerance", () => {
|
|
262
|
+
const { picks, pointer } = setup();
|
|
263
|
+
|
|
264
|
+
// 20px of travel: a drag for a mouse, still a tap for a finger.
|
|
265
|
+
pointer("pointerdown", 100, 100, touch);
|
|
266
|
+
pointer("pointerup", 120, 100, touch);
|
|
267
|
+
|
|
268
|
+
expect(picks).toEqual([[]]);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it("rejects travel beyond the tap tolerance", () => {
|
|
272
|
+
const { state, picks, pointer } = setup();
|
|
273
|
+
|
|
274
|
+
pointer("pointerdown", 100, 100, touch);
|
|
275
|
+
pointer("pointerup", 140, 100, touch);
|
|
276
|
+
|
|
277
|
+
expect(picks).toEqual([]);
|
|
278
|
+
expect(state.renderCount).toBe(0);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("keeps the stricter tolerance for the mouse", () => {
|
|
282
|
+
const { state, picks, pointer } = setup();
|
|
283
|
+
|
|
284
|
+
pointer("pointerdown", 100, 100);
|
|
285
|
+
pointer("pointerup", 120, 100);
|
|
286
|
+
|
|
287
|
+
expect(picks).toEqual([]);
|
|
288
|
+
expect(state.renderCount).toBe(0);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it("does not pick when a second finger joins (pinch)", () => {
|
|
292
|
+
const { state, picks, pointer } = setup();
|
|
293
|
+
|
|
294
|
+
pointer("pointerdown", 100, 100, touch);
|
|
295
|
+
pointer("pointerdown", 120, 100, { ...touch, isPrimary: false });
|
|
296
|
+
pointer("pointerup", 120, 100, { ...touch, isPrimary: false });
|
|
297
|
+
pointer("pointerup", 100, 100, touch);
|
|
298
|
+
|
|
299
|
+
expect(picks).toEqual([]);
|
|
300
|
+
expect(state.renderCount).toBe(0);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("searches a wider window than the mouse (fingers are less precise)", () => {
|
|
304
|
+
const { state, pointer } = setup();
|
|
305
|
+
|
|
306
|
+
pointer("pointerdown", 100, 100, touch);
|
|
307
|
+
pointer("pointerup", 100, 100, touch);
|
|
308
|
+
|
|
309
|
+
// (2 * TOUCH_PICK_RADIUS + 1)² centered on the tap (device y = 499).
|
|
310
|
+
expect(state.scissors).toEqual([{ x: 90, y: 489, w: 21, h: 21 }]);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("does not pick when the browser cancels the gesture", () => {
|
|
314
|
+
const { state, picks, pointer } = setup();
|
|
190
315
|
|
|
191
|
-
|
|
192
|
-
|
|
316
|
+
pointer("pointerdown", 100, 100, touch);
|
|
317
|
+
pointer("pointercancel", 100, 100, touch);
|
|
318
|
+
pointer("pointerup", 100, 100, touch);
|
|
193
319
|
|
|
194
320
|
expect(picks).toEqual([]);
|
|
195
321
|
expect(state.renderCount).toBe(0);
|
|
@@ -198,34 +324,35 @@ describe("PickHelper", () => {
|
|
|
198
324
|
|
|
199
325
|
describe("hover picking", () => {
|
|
200
326
|
it("picks at most once per animation frame, at the latest position", () => {
|
|
201
|
-
const { state, hovers,
|
|
327
|
+
const { state, hovers, pointer } = setup();
|
|
202
328
|
state.pixel = [0, 0, 7];
|
|
203
329
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
330
|
+
pointer("pointermove", 10, 10);
|
|
331
|
+
pointer("pointermove", 20, 20);
|
|
332
|
+
pointer("pointermove", 30, 30);
|
|
207
333
|
expect(hovers).toEqual([]); // nothing until the frame fires
|
|
208
334
|
|
|
209
335
|
runFrame();
|
|
210
336
|
|
|
211
337
|
expect(hovers).toEqual([[7]]);
|
|
212
338
|
expect(state.renderCount).toBe(1);
|
|
213
|
-
expect(state.scissors).toEqual([{ x:
|
|
339
|
+
expect(state.scissors).toEqual([{ x: 27, y: 566, w: 7, h: 7 }]);
|
|
214
340
|
});
|
|
215
341
|
|
|
216
342
|
it("reports an empty hover for a miss", () => {
|
|
217
|
-
const { hovers,
|
|
343
|
+
const { hovers, pointer } = setup();
|
|
218
344
|
|
|
219
|
-
|
|
345
|
+
pointer("pointermove", 10, 10);
|
|
220
346
|
runFrame();
|
|
221
347
|
|
|
222
348
|
expect(hovers).toEqual([[]]);
|
|
223
349
|
});
|
|
224
350
|
|
|
225
|
-
it("is suppressed while a
|
|
226
|
-
const { state, hovers,
|
|
351
|
+
it("is suppressed while a button is pressed (including touch contact)", () => {
|
|
352
|
+
const { state, hovers, pointer } = setup();
|
|
227
353
|
|
|
228
|
-
|
|
354
|
+
pointer("pointermove", 10, 10, { buttons: 1 });
|
|
355
|
+
pointer("pointermove", 10, 10, { buttons: 1, pointerType: "touch" });
|
|
229
356
|
runFrame();
|
|
230
357
|
|
|
231
358
|
expect(hovers).toEqual([]);
|
|
@@ -233,11 +360,11 @@ describe("PickHelper", () => {
|
|
|
233
360
|
});
|
|
234
361
|
|
|
235
362
|
it("re-checks the listener gate when the frame fires", () => {
|
|
236
|
-
const { state, hovers, gates,
|
|
363
|
+
const { state, hovers, gates, pointer } = setup();
|
|
237
364
|
state.pixel = [0, 0, 7];
|
|
238
365
|
|
|
239
|
-
|
|
240
|
-
// Listeners are removed between the
|
|
366
|
+
pointer("pointermove", 10, 10);
|
|
367
|
+
// Listeners are removed between the pointermove and the frame.
|
|
241
368
|
gates.hoverable = false;
|
|
242
369
|
runFrame();
|
|
243
370
|
|
|
@@ -246,9 +373,9 @@ describe("PickHelper", () => {
|
|
|
246
373
|
});
|
|
247
374
|
|
|
248
375
|
it("schedules nothing when no hover listener is registered", () => {
|
|
249
|
-
const { state, hovers,
|
|
376
|
+
const { state, hovers, pointer } = setup({ hoverable: false });
|
|
250
377
|
|
|
251
|
-
|
|
378
|
+
pointer("pointermove", 10, 10);
|
|
252
379
|
|
|
253
380
|
expect(rafCallbacks.size).toBe(0);
|
|
254
381
|
runFrame();
|
|
@@ -256,12 +383,12 @@ describe("PickHelper", () => {
|
|
|
256
383
|
expect(state.renderCount).toBe(0);
|
|
257
384
|
});
|
|
258
385
|
|
|
259
|
-
it("
|
|
260
|
-
const { state, hovers,
|
|
386
|
+
it("pointerleave reports nothing hovered and cancels the pending pick", () => {
|
|
387
|
+
const { state, hovers, pointer } = setup();
|
|
261
388
|
state.pixel = [0, 0, 7];
|
|
262
389
|
|
|
263
|
-
|
|
264
|
-
|
|
390
|
+
pointer("pointermove", 10, 10);
|
|
391
|
+
pointer("pointerleave", 10, 10);
|
|
265
392
|
|
|
266
393
|
expect(hovers).toEqual([[]]);
|
|
267
394
|
|
|
@@ -271,10 +398,10 @@ describe("PickHelper", () => {
|
|
|
271
398
|
expect(state.renderCount).toBe(0);
|
|
272
399
|
});
|
|
273
400
|
|
|
274
|
-
it("
|
|
275
|
-
const { hovers,
|
|
401
|
+
it("pointerleave stays silent without hover listeners", () => {
|
|
402
|
+
const { hovers, pointer } = setup({ hoverable: false });
|
|
276
403
|
|
|
277
|
-
|
|
404
|
+
pointer("pointerleave", 10, 10);
|
|
278
405
|
|
|
279
406
|
expect(hovers).toEqual([]);
|
|
280
407
|
});
|
|
@@ -282,16 +409,16 @@ describe("PickHelper", () => {
|
|
|
282
409
|
|
|
283
410
|
describe("enablePick(false)", () => {
|
|
284
411
|
it("detaches every handler and cancels pending hover picks", () => {
|
|
285
|
-
const { helper, state, picks, hovers,
|
|
412
|
+
const { helper, state, picks, hovers, pointer } = setup();
|
|
286
413
|
state.pixel = [0, 0, 7];
|
|
287
|
-
|
|
414
|
+
pointer("pointermove", 10, 10);
|
|
288
415
|
|
|
289
416
|
helper.enablePick(false);
|
|
290
417
|
|
|
291
418
|
expect(rafCallbacks.size).toBe(0);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
419
|
+
pointer("pointerdown", 100, 100);
|
|
420
|
+
pointer("pointerup", 100, 100);
|
|
421
|
+
pointer("pointermove", 20, 20);
|
|
295
422
|
runFrame();
|
|
296
423
|
|
|
297
424
|
expect(picks).toEqual([]);
|