zrender-nightly 5.7.0-dev.20250620 → 5.7.0-dev.20250621

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.
Files changed (61) hide show
  1. package/README.md +1 -1
  2. package/build/prepublish.js +20 -0
  3. package/dist/zrender.js +563 -277
  4. package/dist/zrender.js.map +1 -1
  5. package/dist/zrender.min.js +1 -1
  6. package/lib/Element.d.ts +4 -0
  7. package/lib/Element.js +34 -16
  8. package/lib/Handler.js +1 -1
  9. package/lib/Storage.js +20 -20
  10. package/lib/canvas/graphic.js +1 -1
  11. package/lib/contain/text.d.ts +14 -2
  12. package/lib/contain/text.js +65 -15
  13. package/lib/core/BoundingRect.d.ts +25 -3
  14. package/lib/core/BoundingRect.js +182 -76
  15. package/lib/core/OrientedBoundingRect.d.ts +2 -2
  16. package/lib/core/OrientedBoundingRect.js +50 -34
  17. package/lib/core/PathProxy.d.ts +1 -0
  18. package/lib/core/PathProxy.js +16 -1
  19. package/lib/core/dom.d.ts +1 -0
  20. package/lib/core/dom.js +17 -0
  21. package/lib/core/env.js +15 -10
  22. package/lib/core/types.d.ts +1 -0
  23. package/lib/core/util.d.ts +1 -0
  24. package/lib/core/util.js +2 -1
  25. package/lib/graphic/Displayable.js +1 -1
  26. package/lib/graphic/Text.d.ts +4 -2
  27. package/lib/graphic/Text.js +23 -14
  28. package/lib/graphic/helper/parseText.d.ts +13 -4
  29. package/lib/graphic/helper/parseText.js +110 -54
  30. package/lib/svg-legacy/helper/ClippathManager.js +6 -6
  31. package/lib/tool/color.d.ts +3 -1
  32. package/lib/tool/color.js +6 -6
  33. package/lib/tool/parseSVG.js +11 -0
  34. package/lib/tool/path.js +7 -4
  35. package/lib/zrender.d.ts +1 -1
  36. package/lib/zrender.js +1 -1
  37. package/package.json +3 -2
  38. package/src/Element.ts +69 -16
  39. package/src/Handler.ts +1 -1
  40. package/src/Storage.ts +25 -24
  41. package/src/canvas/graphic.ts +1 -1
  42. package/src/canvas/helper.ts +1 -1
  43. package/src/contain/text.ts +103 -19
  44. package/src/core/BoundingRect.ts +308 -87
  45. package/src/core/OrientedBoundingRect.ts +86 -46
  46. package/src/core/PathProxy.ts +17 -1
  47. package/src/core/Transformable.ts +2 -0
  48. package/src/core/dom.ts +24 -0
  49. package/src/core/env.ts +31 -24
  50. package/src/core/matrix.ts +2 -1
  51. package/src/core/types.ts +2 -0
  52. package/src/core/util.ts +4 -2
  53. package/src/graphic/Displayable.ts +1 -3
  54. package/src/graphic/Group.ts +2 -0
  55. package/src/graphic/Text.ts +68 -21
  56. package/src/graphic/helper/parseText.ts +211 -83
  57. package/src/svg-legacy/helper/ClippathManager.ts +5 -5
  58. package/src/tool/color.ts +15 -11
  59. package/src/tool/parseSVG.ts +12 -1
  60. package/src/tool/path.ts +9 -4
  61. package/src/zrender.ts +1 -1
@@ -1,20 +1,24 @@
1
- /**
2
- * @module echarts/core/BoundingRect
3
- */
4
-
5
1
  import * as matrix from './matrix';
6
2
  import Point, { PointLike } from './Point';
3
+ import { NullUndefined } from './types';
7
4
 
8
5
  const mathMin = Math.min;
9
6
  const mathMax = Math.max;
7
+ const mathAbs = Math.abs;
8
+
9
+ const XY = ['x', 'y'] as const;
10
+ const WH = ['width', 'height'] as const;
10
11
 
11
12
  const lt = new Point();
12
13
  const rb = new Point();
13
14
  const lb = new Point();
14
15
  const rt = new Point();
15
16
 
16
- const minTv = new Point();
17
- const maxTv = new Point();
17
+ const _intersectCtx = createIntersectContext();
18
+ const _minTv = _intersectCtx.minTv;
19
+ const _maxTv = _intersectCtx.maxTv;
20
+ // [min, max]
21
+ const _lenMinMax = [0, 0];
18
22
 
19
23
  class BoundingRect {
20
24
 
@@ -24,6 +28,12 @@ class BoundingRect {
24
28
  height: number
25
29
 
26
30
  constructor(x: number, y: number, width: number, height: number) {
31
+ BoundingRect.set(this, x, y, width, height);
32
+ }
33
+
34
+ static set<TTarget extends RectLike>(
35
+ target: TTarget, x: number, y: number, width: number, height: number
36
+ ): TTarget {
27
37
  if (width < 0) {
28
38
  x = x + width;
29
39
  width = -width;
@@ -33,10 +43,12 @@ class BoundingRect {
33
43
  height = -height;
34
44
  }
35
45
 
36
- this.x = x;
37
- this.y = y;
38
- this.width = width;
39
- this.height = height;
46
+ target.x = x;
47
+ target.y = y;
48
+ target.width = width;
49
+ target.height = height;
50
+
51
+ return target;
40
52
  }
41
53
 
42
54
  union(other: BoundingRect) {
@@ -44,7 +56,7 @@ class BoundingRect {
44
56
  const y = mathMin(other.y, this.y);
45
57
 
46
58
  // If x is -Infinity and width is Infinity (like in the case of
47
- // IncrementalDisplayble), x + width would be NaN
59
+ // IncrementalDisplayable), x + width would be NaN
48
60
  if (isFinite(this.x) && isFinite(this.width)) {
49
61
  this.width = mathMax(
50
62
  other.x + other.width,
@@ -80,7 +92,6 @@ class BoundingRect {
80
92
 
81
93
  const m = matrix.create();
82
94
 
83
- // 矩阵右乘
84
95
  matrix.translate(m, m, [-a.x, -a.y]);
85
96
  matrix.scale(m, m, [sx, sy]);
86
97
  matrix.translate(m, m, [b.x, b.y]);
@@ -88,100 +99,107 @@ class BoundingRect {
88
99
  return m;
89
100
  }
90
101
 
91
- intersect(b: RectLike, mtv?: PointLike): boolean {
92
- if (!b) {
102
+ /**
103
+ * @see `static intersect`
104
+ */
105
+ intersect(
106
+ b: RectLike,
107
+ mtv?: PointLike,
108
+ opt?: BoundingRectIntersectOpt
109
+ ): boolean {
110
+ return BoundingRect.intersect(this, b, mtv, opt);
111
+ }
112
+
113
+ /**
114
+ * [NOTICE]
115
+ * Touching the edge is considered an intersection.
116
+ * zero-width/height can still cause intersection if `touchThreshold` is 0.
117
+ * See more in `BoundingRectIntersectOpt['touchThreshold']`
118
+ *
119
+ * @param mtv
120
+ * If it's not overlapped. it means needs to move `b` rect with Maximum Translation Vector to be overlapped.
121
+ * Else it means needs to move `b` rect with Minimum Translation Vector to be not overlapped.
122
+ */
123
+ static intersect(
124
+ a: RectLike,
125
+ b: RectLike,
126
+ mtv?: PointLike,
127
+ opt?: BoundingRectIntersectOpt
128
+ ): boolean {
129
+ if (mtv) {
130
+ Point.set(mtv, 0, 0);
131
+ }
132
+ const outIntersectRect = opt && opt.outIntersectRect || null;
133
+ const clamp = opt && opt.clamp;
134
+ if (outIntersectRect) {
135
+ outIntersectRect.x = outIntersectRect.y = outIntersectRect.width = outIntersectRect.height = NaN;
136
+ }
137
+
138
+ if (!a || !b) {
93
139
  return false;
94
140
  }
95
141
 
142
+ // Normalize negative width/height.
143
+ if (!(a instanceof BoundingRect)) {
144
+ a = BoundingRect.set(_tmpIntersectA, a.x, a.y, a.width, a.height);
145
+ }
96
146
  if (!(b instanceof BoundingRect)) {
97
- // Normalize negative width/height.
98
- b = BoundingRect.create(b);
147
+ b = BoundingRect.set(_tmpIntersectB, b.x, b.y, b.width, b.height);
99
148
  }
100
149
 
101
- const a = this;
102
- const ax0 = a.x;
103
- const ax1 = a.x + a.width;
104
- const ay0 = a.y;
105
- const ay1 = a.y + a.height;
150
+ const useMTV = !!mtv;
106
151
 
107
- const bx0 = b.x;
108
- const bx1 = b.x + b.width;
109
- const by0 = b.y;
110
- const by1 = b.y + b.height;
152
+ _intersectCtx.reset(opt, useMTV);
111
153
 
112
- let overlap = !(ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0);
113
- if (mtv) {
114
- let dMin = Infinity;
115
- let dMax = 0;
116
- const d0 = Math.abs(ax1 - bx0);
117
- const d1 = Math.abs(bx1 - ax0);
118
- const d2 = Math.abs(ay1 - by0);
119
- const d3 = Math.abs(by1 - ay0);
120
- const dx = Math.min(d0, d1);
121
- const dy = Math.min(d2, d3);
122
- // On x axis
123
- if (ax1 < bx0 || bx1 < ax0) {
124
- if (dx > dMax) {
125
- dMax = dx;
126
- if (d0 < d1) {
127
- Point.set(maxTv, -d0, 0); // b is on the right
128
- }
129
- else {
130
- Point.set(maxTv, d1, 0); // b is on the left
131
- }
132
- }
133
- }
134
- else {
135
- if (dx < dMin) {
136
- dMin = dx;
137
- if (d0 < d1) {
138
- Point.set(minTv, d0, 0); // b is on the right
139
- }
140
- else {
141
- Point.set(minTv, -d1, 0); // b is on the left
142
- }
143
- }
144
- }
154
+ const touchThreshold = _intersectCtx.touchThreshold;
145
155
 
146
- // On y axis
147
- if (ay1 < by0 || by1 < ay0) {
148
- if (dy > dMax) {
149
- dMax = dy;
150
- if (d2 < d3) {
151
- Point.set(maxTv, 0, -d2); // b is on the bottom(larger y)
152
- }
153
- else {
154
- Point.set(maxTv, 0, d3); // b is on the top(smaller y)
155
- }
156
- }
157
- }
158
- else {
159
- if (dx < dMin) {
160
- dMin = dx;
161
- if (d2 < d3) {
162
- Point.set(minTv, 0, d2); // b is on the bottom
163
- }
164
- else {
165
- Point.set(minTv, 0, -d3); // b is on the top
166
- }
167
- }
168
- }
156
+ const ax0 = a.x + touchThreshold;
157
+ const ax1 = a.x + a.width - touchThreshold;
158
+ const ay0 = a.y + touchThreshold;
159
+ const ay1 = a.y + a.height - touchThreshold;
160
+
161
+ const bx0 = b.x + touchThreshold;
162
+ const bx1 = b.x + b.width - touchThreshold;
163
+ const by0 = b.y + touchThreshold;
164
+ const by1 = b.y + b.height - touchThreshold;
165
+
166
+ if (ax0 > ax1 || ay0 > ay1 || bx0 > bx1 || by0 > by1) {
167
+ return false;
169
168
  }
170
169
 
171
- if (mtv) {
172
- Point.copy(mtv, overlap ? minTv : maxTv);
170
+ const overlap = !(ax1 < bx0 || bx1 < ax0 || ay1 < by0 || by1 < ay0);
171
+
172
+ if (useMTV || outIntersectRect) {
173
+ _lenMinMax[0] = Infinity;
174
+ _lenMinMax[1] = 0;
175
+
176
+ intersectOneDim(ax0, ax1, bx0, bx1, 0, useMTV, outIntersectRect, clamp);
177
+ intersectOneDim(ay0, ay1, by0, by1, 1, useMTV, outIntersectRect, clamp);
178
+
179
+ if (useMTV) {
180
+ Point.copy(
181
+ mtv,
182
+ overlap
183
+ ? (_intersectCtx.useDir ? _intersectCtx.dirMinTv : _minTv)
184
+ : _maxTv
185
+ );
186
+ }
173
187
  }
188
+
174
189
  return overlap;
175
190
  }
176
191
 
177
- contain(x: number, y: number): boolean {
178
- const rect = this;
192
+ static contain(rect: RectLike, x: number, y: number): boolean {
179
193
  return x >= rect.x
180
194
  && x <= (rect.x + rect.width)
181
195
  && y >= rect.y
182
196
  && y <= (rect.y + rect.height);
183
197
  }
184
198
 
199
+ contain(x: number, y: number): boolean {
200
+ return BoundingRect.contain(this, x, y);
201
+ }
202
+
185
203
  clone() {
186
204
  return new BoundingRect(this.x, this.y, this.width, this.height);
187
205
  }
@@ -220,11 +238,13 @@ class BoundingRect {
220
238
  return new BoundingRect(rect.x, rect.y, rect.width, rect.height);
221
239
  }
222
240
 
223
- static copy(target: RectLike, source: RectLike) {
241
+ static copy<TTarget extends RectLike>(target: TTarget, source: RectLike): TTarget {
224
242
  target.x = source.x;
225
243
  target.y = source.y;
226
244
  target.width = source.width;
227
245
  target.height = source.height;
246
+
247
+ return target;
228
248
  }
229
249
 
230
250
  static applyTransform(target: RectLike, source: RectLike, m: matrix.MatrixArray) {
@@ -278,6 +298,73 @@ class BoundingRect {
278
298
  }
279
299
  }
280
300
 
301
+ const _tmpIntersectA = new BoundingRect(0, 0, 0, 0);
302
+ const _tmpIntersectB = new BoundingRect(0, 0, 0, 0);
303
+
304
+
305
+ function intersectOneDim(
306
+ a0: number, a1: number, b0: number, b1: number,
307
+ updateDimIdx: number,
308
+ useMTV: boolean,
309
+ outIntersectRect: BoundingRectIntersectOpt['outIntersectRect'],
310
+ clamp: BoundingRectIntersectOpt['clamp']
311
+ ): void {
312
+ const d0 = mathAbs(a1 - b0);
313
+ const d1 = mathAbs(b1 - a0);
314
+ const d01min = mathMin(d0, d1);
315
+ const updateDim = XY[updateDimIdx];
316
+ const zeroDim = XY[1 - updateDimIdx];
317
+ const wh = WH[updateDimIdx];
318
+
319
+ if (a1 < b0 || b1 < a0) { // No intersection on this dimension.
320
+ if (d0 < d1) {
321
+ if (useMTV) {
322
+ _maxTv[updateDim] = -d0; // b is on the right/bottom(larger x/y)
323
+ }
324
+ if (clamp) {
325
+ outIntersectRect[updateDim] = a1;
326
+ outIntersectRect[wh] = 0;
327
+ }
328
+ }
329
+ else {
330
+ if (useMTV) {
331
+ _maxTv[updateDim] = d1; // b is on the left/top(smaller x/y)
332
+ }
333
+ if (clamp) {
334
+ outIntersectRect[updateDim] = a0;
335
+ outIntersectRect[wh] = 0;
336
+ }
337
+ }
338
+ }
339
+ else { // Has intersection
340
+ if (outIntersectRect) {
341
+ outIntersectRect[updateDim] = mathMax(a0, b0);
342
+ outIntersectRect[wh] = mathMin(a1, b1) - outIntersectRect[updateDim];
343
+ }
344
+ if (useMTV) {
345
+ if (d01min < _lenMinMax[0] || _intersectCtx.useDir) {
346
+ // If bidirectional, both dist0 dist1 need to check,
347
+ // otherwise only check the smaller one.
348
+ _lenMinMax[0] = mathMin(d01min, _lenMinMax[0]);
349
+ if (d0 < d1 || !_intersectCtx.bidirectional) {
350
+ _minTv[updateDim] = d0; // b is on the right/bottom(larger x/y)
351
+ _minTv[zeroDim] = 0;
352
+ if (_intersectCtx.useDir) {
353
+ _intersectCtx.calcDirMTV();
354
+ }
355
+ }
356
+ if (d0 >= d1 || !_intersectCtx.bidirectional) {
357
+ _minTv[updateDim] = -d1; // b is on the left/top(smaller x/y)
358
+ _minTv[zeroDim] = 0;
359
+ if (_intersectCtx.useDir) {
360
+ _intersectCtx.calcDirMTV();
361
+ }
362
+ }
363
+ }
364
+ }
365
+ }
366
+ }
367
+
281
368
 
282
369
  export type RectLike = {
283
370
  x: number
@@ -286,4 +373,138 @@ export type RectLike = {
286
373
  height: number
287
374
  }
288
375
 
289
- export default BoundingRect;
376
+ export interface BoundingRectIntersectOpt {
377
+ /**
378
+ * If specified, when overlapping, the output `mtv` is still a minimal vector that can resolve the overlap.
379
+ * However it is not Minimum Translation Vector, but a vector follow the direction.
380
+ * Be a radian, representing a vector direction.
381
+ * `direction=atan2(y, x)`, i.e., `direction=0` is vector(1,0), `direction=PI/4` is vector(1,1).
382
+ */
383
+ direction?: number
384
+ /**
385
+ * By default `true`. It means whether `BoundingRectIntersectOpt['direction']` is bidirectional. If `true`,
386
+ * the returned mtv is the minimal among both `opt.direction` and `opt.direction + Math.PI`.
387
+ */
388
+ bidirectional?: boolean
389
+ /**
390
+ * Two rects that touch but are within the threshold do not be considered an intersection.
391
+ * Scenarios:
392
+ * - Without a `touchThreshold`, zero-width/height can still cause intersection.
393
+ * In some scenarios, a rect with border styles still needs to display even if width/height is zero;
394
+ * but in some other scenarios, zero-width/height represents "nothing", such as in HTML
395
+ * BoundingClientRect, or when zrender.Group has all children `ignored: true`. In this case, we can use
396
+ * a non-negative `touchThreshold` to form a "minus width/height" and force it to never cause an
397
+ * intersection. And in this case, mtv will not be calculated.
398
+ * - Without a `touchThreshold`, touching the edge is considered an intersection.
399
+ * - Having a `touchThreshold`, elements can use the same rect instance to achieve compact layout while
400
+ * still passing through the overlap-hiding handler.
401
+ * - a positive near-zero number is commonly used in `touchThreshold` for aggressive overlap handling,
402
+ * such as:
403
+ * - Hide one element if overlapping.
404
+ * - Two elements are vertically touching at top/bottom edges, but are restricted to move along
405
+ * the horizontal direction to resolve overlap.
406
+ */
407
+ touchThreshold?: number
408
+
409
+ /**
410
+ * - If an intersection occur, set the intersection rect to it.
411
+ * - Otherwise,
412
+ * - If `clamp: true`, `outIntersectRect` is set with a clamped rect that is on the edge or corner
413
+ * of the first rect input to `intersect` method.
414
+ * - Otherwise, set to all NaN (it will not pass `contain` and `intersect`).
415
+ */
416
+ outIntersectRect?: RectLike
417
+ clamp?: boolean;
418
+ }
419
+
420
+ /**
421
+ * [CAVEAT] Do not use it other than in `BoundingRect` and `OrientedBoundingRect`.
422
+ */
423
+ export function createIntersectContext() {
424
+
425
+ let _direction: BoundingRectIntersectOpt['direction'] = 0;
426
+ const _dirCheckVec = new Point();
427
+ const _dirTmp = new Point();
428
+
429
+ const _ctx = {
430
+ minTv: new Point(),
431
+ maxTv: new Point(),
432
+ useDir: false as boolean,
433
+ dirMinTv: new Point(),
434
+ touchThreshold: 0 as BoundingRectIntersectOpt['touchThreshold'],
435
+ bidirectional: true as BoundingRectIntersectOpt['bidirectional'],
436
+
437
+ negativeSize: false as boolean,
438
+
439
+ reset(opt: BoundingRectIntersectOpt | NullUndefined, useMTV: boolean): void {
440
+ _ctx.touchThreshold = 0;
441
+ if (opt && opt.touchThreshold != null) {
442
+ _ctx.touchThreshold = mathMax(0, opt.touchThreshold);
443
+ }
444
+ _ctx.negativeSize = false;
445
+
446
+ if (!useMTV) {
447
+ return;
448
+ }
449
+
450
+ _ctx.minTv.set(Infinity, Infinity);
451
+ _ctx.maxTv.set(0, 0);
452
+ _ctx.useDir = false;
453
+
454
+ if (opt && opt.direction != null) {
455
+ _ctx.useDir = true;
456
+ _ctx.dirMinTv.copy(_ctx.minTv);
457
+ _dirTmp.copy(_ctx.minTv);
458
+ _direction = opt.direction;
459
+ _ctx.bidirectional = opt.bidirectional == null || !!opt.bidirectional;
460
+ if (!_ctx.bidirectional) {
461
+ _dirCheckVec.set(Math.cos(_direction), Math.sin(_direction));
462
+ }
463
+ }
464
+ },
465
+
466
+ calcDirMTV(): void {
467
+ const minTv = _ctx.minTv;
468
+ const dirMinTv = _ctx.dirMinTv;
469
+ const squareMag = minTv.y * minTv.y + minTv.x * minTv.x;
470
+ const dirSin = Math.sin(_direction);
471
+ const dirCos = Math.cos(_direction);
472
+ const dotProd = dirSin * minTv.y + dirCos * minTv.x;
473
+
474
+ if (nearZero(dotProd)) {
475
+ if (nearZero(minTv.x) && nearZero(minTv.y)) {
476
+ // The two OBBs touch at the edges.
477
+ dirMinTv.set(0, 0);
478
+ }
479
+ // Otherwise `minTv` is perpendicular to `this.direction`.
480
+ return;
481
+ }
482
+
483
+ _dirTmp.x = squareMag * dirCos / dotProd;
484
+ _dirTmp.y = squareMag * dirSin / dotProd;
485
+ if (nearZero(_dirTmp.x) && nearZero(_dirTmp.y)) {
486
+ // The result includes near-(0,0) regardless of `bidirectional`.
487
+ dirMinTv.set(0, 0);
488
+ return;
489
+ }
490
+
491
+ if ((
492
+ _ctx.bidirectional
493
+ || _dirCheckVec.dot(_dirTmp) > 0
494
+ )
495
+ && _dirTmp.len() < dirMinTv.len()
496
+ ) {
497
+ dirMinTv.copy(_dirTmp);
498
+ }
499
+ }
500
+ };
501
+
502
+ function nearZero(val: number): boolean {
503
+ return mathAbs(val) < 1e-10; // Empirically OK for pixel-scale values.
504
+ }
505
+
506
+ return _ctx;
507
+ }
508
+
509
+
510
+ export default BoundingRect;
@@ -18,14 +18,20 @@
18
18
  */
19
19
 
20
20
  import Point, { PointLike } from './Point';
21
- import BoundingRect from './BoundingRect';
21
+ import BoundingRect, { BoundingRectIntersectOpt, createIntersectContext } from './BoundingRect';
22
22
  import { MatrixArray } from './matrix';
23
23
 
24
- const extent = [0, 0];
25
- const extent2 = [0, 0];
24
+ const mathMin = Math.min;
25
+ const mathMax = Math.max;
26
+ const mathAbs = Math.abs;
27
+
28
+ const _extent = [0, 0];
29
+ const _extent2 = [0, 0];
30
+
31
+ const _intersectCtx = createIntersectContext();
32
+ const _minTv = _intersectCtx.minTv;
33
+ const _maxTv = _intersectCtx.maxTv;
26
34
 
27
- const minTv = new Point();
28
- const maxTv = new Point();
29
35
 
30
36
  class OrientedBoundingRect {
31
37
 
@@ -80,36 +86,57 @@ class OrientedBoundingRect {
80
86
  }
81
87
 
82
88
  /**
83
- * If intersect with another OBB
89
+ * If intersect with another OBB.
90
+ *
91
+ * [NOTICE]
92
+ * Touching the edge is considered an intersection.
93
+ * zero-width/height can still cause intersection if `touchThreshold` is 0.
94
+ * See more in `BoundingRectIntersectOpt['touchThreshold']`
95
+ *
84
96
  * @param other Bounding rect to be intersected with
85
- * @param mtv Calculated .
86
- * If it's not overlapped. it means needs to move given rect with Maximum Translation Vector to be overlapped.
87
- * Else it means needs to move given rect with Minimum Translation Vector to be not overlapped.
97
+ * @param mtv
98
+ * If it's not overlapped. it means needs to move `other` rect with Maximum Translation Vector to be overlapped.
99
+ * FIXME: Maximum Translation Vector is buggy. Fix it before using it. See case in `test/obb-collide.html`.
100
+ * Else it means needs to move `other` rect with Minimum Translation Vector to be not overlapped.
88
101
  */
89
- intersect(other: OrientedBoundingRect, mtv?: PointLike): boolean {
102
+ intersect(
103
+ other: OrientedBoundingRect,
104
+ mtv?: PointLike,
105
+ opt?: BoundingRectIntersectOpt
106
+ ): boolean {
90
107
  // OBB collision with SAT method
91
108
 
92
109
  let overlapped = true;
93
110
  const noMtv = !mtv;
94
- minTv.set(Infinity, Infinity);
95
- maxTv.set(0, 0);
111
+
112
+ if (mtv) {
113
+ Point.set(mtv, 0, 0);
114
+ }
115
+
116
+ _intersectCtx.reset(opt, !noMtv);
117
+
96
118
  // Check two axes for both two obb.
97
- if (!this._intersectCheckOneSide(this, other, minTv, maxTv, noMtv, 1)) {
119
+ if (!this._intersectCheckOneSide(this, other, noMtv, 1)) {
98
120
  overlapped = false;
99
121
  if (noMtv) {
100
122
  // Early return if no need to calculate mtv
101
123
  return overlapped;
102
124
  }
103
125
  }
104
- if (!this._intersectCheckOneSide(other, this, minTv, maxTv, noMtv, -1)) {
126
+ if (!this._intersectCheckOneSide(other, this, noMtv, -1)) {
105
127
  overlapped = false;
106
128
  if (noMtv) {
107
129
  return overlapped;
108
130
  }
109
131
  }
110
132
 
111
- if (!noMtv) {
112
- Point.copy(mtv, overlapped ? minTv : maxTv);
133
+ if (!noMtv && !_intersectCtx.negativeSize) {
134
+ Point.copy(
135
+ mtv,
136
+ overlapped
137
+ ? (_intersectCtx.useDir ? _intersectCtx.dirMinTv : _minTv)
138
+ : _maxTv
139
+ );
113
140
  }
114
141
 
115
142
  return overlapped;
@@ -119,46 +146,57 @@ class OrientedBoundingRect {
119
146
  private _intersectCheckOneSide(
120
147
  self: OrientedBoundingRect,
121
148
  other: OrientedBoundingRect,
122
- minTv: Point,
123
- maxTv: Point,
124
149
  noMtv: boolean,
125
- inverse: 1 | -1
150
+ inverse: 1 | -1,
126
151
  ): boolean {
152
+
153
+ // [CAVEAT] Must not use `this` in this method.
154
+
127
155
  let overlapped = true;
128
156
  for (let i = 0; i < 2; i++) {
129
- const axis = this._axes[i];
130
- this._getProjMinMaxOnAxis(i, self._corners, extent);
131
- this._getProjMinMaxOnAxis(i, other._corners, extent2);
132
-
133
- // Not overlap on the any axis.
134
- if (extent[1] < extent2[0] || extent[0] > extent2[1]) {
157
+ const axis = self._axes[i];
158
+ self._getProjMinMaxOnAxis(i, self._corners, _extent);
159
+ self._getProjMinMaxOnAxis(i, other._corners, _extent2);
160
+
161
+ // Following the behavior in `BoundingRect.ts`, touching the edge is considered
162
+ // an overlap, but get a mtv [0, 0].
163
+ if (_intersectCtx.negativeSize || _extent[1] < _extent2[0] || _extent[0] > _extent2[1]) {
164
+ // Not overlap on the any axis.
135
165
  overlapped = false;
136
- if (noMtv) {
166
+ if (_intersectCtx.negativeSize || noMtv) {
137
167
  return overlapped;
138
168
  }
139
- const dist0 = Math.abs(extent2[0] - extent[1]);
140
- const dist1 = Math.abs(extent[0] - extent2[1]);
169
+ const dist0 = mathAbs(_extent2[0] - _extent[1]);
170
+ const dist1 = mathAbs(_extent[0] - _extent2[1]);
141
171
 
142
172
  // Find longest distance of all axes.
143
- if (Math.min(dist0, dist1) > maxTv.len()) {
173
+ if (mathMin(dist0, dist1) > _maxTv.len()) {
144
174
  if (dist0 < dist1) {
145
- Point.scale(maxTv, axis, -dist0 * inverse);
175
+ Point.scale(_maxTv, axis, -dist0 * inverse);
146
176
  }
147
177
  else {
148
- Point.scale(maxTv, axis, dist1 * inverse);
178
+ Point.scale(_maxTv, axis, dist1 * inverse);
149
179
  }
150
180
  }
151
181
  }
152
- else if (minTv) {
153
- const dist0 = Math.abs(extent2[0] - extent[1]);
154
- const dist1 = Math.abs(extent[0] - extent2[1]);
155
-
156
- if (Math.min(dist0, dist1) < minTv.len()) {
157
- if (dist0 < dist1) {
158
- Point.scale(minTv, axis, dist0 * inverse);
182
+ else if (!noMtv) {
183
+ const dist0 = mathAbs(_extent2[0] - _extent[1]);
184
+ const dist1 = mathAbs(_extent[0] - _extent2[1]);
185
+
186
+ if (_intersectCtx.useDir || mathMin(dist0, dist1) < _minTv.len()) {
187
+ // If bidirectional, both dist0 dist1 need to check,
188
+ // otherwise only check the smaller one.
189
+ if (dist0 < dist1 || !_intersectCtx.bidirectional) {
190
+ Point.scale(_minTv, axis, dist0 * inverse);
191
+ if (_intersectCtx.useDir) {
192
+ _intersectCtx.calcDirMTV();
193
+ }
159
194
  }
160
- else {
161
- Point.scale(minTv, axis, -dist1 * inverse);
195
+ if (dist0 >= dist1 || !_intersectCtx.bidirectional) {
196
+ Point.scale(_minTv, axis, -dist1 * inverse);
197
+ if (_intersectCtx.useDir) {
198
+ _intersectCtx.calcDirMTV();
199
+ }
162
200
  }
163
201
  }
164
202
  }
@@ -166,7 +204,7 @@ class OrientedBoundingRect {
166
204
  return overlapped;
167
205
  }
168
206
 
169
- private _getProjMinMaxOnAxis(dim: number, corners: Point[], out: number[]) {
207
+ private _getProjMinMaxOnAxis(dim: number, corners: Point[], out: number[]): void {
170
208
  const axis = this._axes[dim];
171
209
  const origin = this._origin;
172
210
  const proj = corners[0].dot(axis) + origin[dim];
@@ -175,13 +213,15 @@ class OrientedBoundingRect {
175
213
 
176
214
  for (let i = 1; i < corners.length; i++) {
177
215
  const proj = corners[i].dot(axis) + origin[dim];
178
- min = Math.min(proj, min);
179
- max = Math.max(proj, max);
216
+ min = mathMin(proj, min);
217
+ max = mathMax(proj, max);
180
218
  }
181
219
 
182
- out[0] = min;
183
- out[1] = max;
220
+ out[0] = min + _intersectCtx.touchThreshold;
221
+ out[1] = max - _intersectCtx.touchThreshold;
222
+
223
+ _intersectCtx.negativeSize = out[1] < out[0];
184
224
  }
185
225
  }
186
226
 
187
- export default OrientedBoundingRect;
227
+ export default OrientedBoundingRect;