@stemy/ngx-utils 19.7.1 → 19.7.4
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/fesm2022/stemy-ngx-utils.mjs +472 -262
- package/fesm2022/stemy-ngx-utils.mjs.map +1 -1
- package/ngx-utils/common-types.d.ts +41 -4
- package/ngx-utils/components/interactive-canvas/interactive-canvas.component.d.ts +27 -14
- package/ngx-utils/components/interactive-canvas/interactive-item.component.d.ts +4 -0
- package/ngx-utils/ngx-utils.imports.d.ts +2 -2
- package/ngx-utils/utils/canvas.d.ts +1 -0
- package/ngx-utils/utils/geometry/functions.d.ts +12 -5
- package/ngx-utils/utils/geometry/gjk.d.ts +3 -0
- package/ngx-utils/utils/geometry/index.d.ts +2 -1
- package/ngx-utils/utils/geometry/shapes.d.ts +15 -8
- package/ngx-utils/utils/math.utils.d.ts +34 -0
- package/package.json +1 -1
- package/public_api.d.ts +3 -3
- package/ngx-utils/utils/geometry/distance.d.ts +0 -25
|
@@ -1146,6 +1146,9 @@ function drawOval(ctx, w, h) {
|
|
|
1146
1146
|
ctx.ellipse(0, 0, w / 2, h / 2, 0, 0, Math.PI * 2);
|
|
1147
1147
|
ctx.closePath();
|
|
1148
1148
|
}
|
|
1149
|
+
function drawPoint(ctx) {
|
|
1150
|
+
drawOval(ctx, 4, 4);
|
|
1151
|
+
}
|
|
1149
1152
|
class CanvasUtils {
|
|
1150
1153
|
static manipulatePixels(canvas, ctx, colorTransformer) {
|
|
1151
1154
|
const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
@@ -1732,28 +1735,54 @@ class FileSystemEntry {
|
|
|
1732
1735
|
function dotProduct(a, b) {
|
|
1733
1736
|
return a.x * b.x + a.y * b.y;
|
|
1734
1737
|
}
|
|
1738
|
+
function tripleProduct(a, b, c) {
|
|
1739
|
+
const ac = a.x * c.x + a.y * c.y;
|
|
1740
|
+
const bc = b.x * c.x + b.y * c.y;
|
|
1741
|
+
return { x: b.x * ac - a.x * bc, y: b.y * ac - a.y * bc };
|
|
1742
|
+
}
|
|
1735
1743
|
function isPoint(v) {
|
|
1736
|
-
return typeof v === "object" &&
|
|
1744
|
+
return typeof v === "object" && Number.isFinite(v.x) && Number.isFinite(v.y);
|
|
1745
|
+
}
|
|
1746
|
+
function ensurePoint(p, fallback = { x: 0, y: 0 }) {
|
|
1747
|
+
return isPoint(p) ? { x: +p.x, y: +p.y } : fallback;
|
|
1737
1748
|
}
|
|
1738
1749
|
function perpendicular(p) {
|
|
1739
1750
|
return { x: -p.y, y: +p.x };
|
|
1740
1751
|
}
|
|
1741
|
-
function
|
|
1752
|
+
function negatePt(p) {
|
|
1753
|
+
return { x: -p.x, y: -p.y };
|
|
1754
|
+
}
|
|
1755
|
+
function normalizePt(p) {
|
|
1756
|
+
const length = lengthOfPt(p);
|
|
1757
|
+
return dividePts(p, length);
|
|
1758
|
+
}
|
|
1759
|
+
function addPts(a, b) {
|
|
1742
1760
|
return { x: a.x + b.x, y: a.y + b.y };
|
|
1743
1761
|
}
|
|
1744
|
-
function
|
|
1762
|
+
function distanceSq(a, b) {
|
|
1745
1763
|
const x = b.x - a.x;
|
|
1746
1764
|
const y = b.y - a.y;
|
|
1747
|
-
return
|
|
1765
|
+
return x * x + y * y;
|
|
1766
|
+
}
|
|
1767
|
+
function distance(a, b) {
|
|
1768
|
+
return Math.sqrt(distanceSq(a, b));
|
|
1748
1769
|
}
|
|
1749
|
-
function
|
|
1770
|
+
function lerpPts(a, b, t) {
|
|
1771
|
+
const diff = subPts(b, a);
|
|
1772
|
+
return addPts(a, multiplyPts(diff, t));
|
|
1773
|
+
}
|
|
1774
|
+
function lengthOfPt(p) {
|
|
1750
1775
|
return Math.hypot(p.x, p.y);
|
|
1751
1776
|
}
|
|
1752
|
-
function
|
|
1777
|
+
function multiplyPts(a, b) {
|
|
1753
1778
|
const s = isPoint(b) ? b : { x: b, y: b };
|
|
1754
1779
|
return { x: a.x * s.x, y: a.y * s.y };
|
|
1755
1780
|
}
|
|
1756
|
-
function
|
|
1781
|
+
function dividePts(a, b) {
|
|
1782
|
+
const s = isPoint(b) ? b : { x: b, y: b };
|
|
1783
|
+
return { x: a.x / s.x, y: a.y / s.y };
|
|
1784
|
+
}
|
|
1785
|
+
function subPts(a, b) {
|
|
1757
1786
|
return { x: a.x - b.x, y: a.y - b.y };
|
|
1758
1787
|
}
|
|
1759
1788
|
function rotateDeg(p, ang) {
|
|
@@ -1770,125 +1799,289 @@ function toRadians(deg) {
|
|
|
1770
1799
|
return deg * Math.PI / 180;
|
|
1771
1800
|
}
|
|
1772
1801
|
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1802
|
+
const EPSILON = 1e-9;
|
|
1803
|
+
/**
|
|
1804
|
+
* Normalize a range
|
|
1805
|
+
* @param minOrRange
|
|
1806
|
+
* @param max
|
|
1807
|
+
*/
|
|
1808
|
+
function normalizeRange(minOrRange, max) {
|
|
1809
|
+
const min = (Array.isArray(minOrRange) ? minOrRange[0] : minOrRange) ?? 0;
|
|
1810
|
+
max = (Array.isArray(minOrRange) ? minOrRange[1] : max) ?? 1;
|
|
1811
|
+
return (max < min) ? [max, min] : [min, max];
|
|
1778
1812
|
}
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1813
|
+
/**
|
|
1814
|
+
* Clamps a value to a range
|
|
1815
|
+
* @param value
|
|
1816
|
+
* @param min
|
|
1817
|
+
* @param max
|
|
1818
|
+
*/
|
|
1819
|
+
function clamp(value, min, max) {
|
|
1820
|
+
const range = normalizeRange(min, max);
|
|
1821
|
+
return Math.max(Math.min(value, range[1]), range[0]);
|
|
1784
1822
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
return { simplex: [B], closest: B.p, bary: [1] };
|
|
1796
|
-
}
|
|
1797
|
-
const t = Math.max(0, Math.min(1, -dotProduct(A.p, ab) / ab2));
|
|
1798
|
-
const closest = ptAdd(A.p, ptMultiply(ab, t));
|
|
1799
|
-
if (t <= 1e-9) {
|
|
1800
|
-
return { simplex: [A], closest: A.p, bary: [1] };
|
|
1801
|
-
}
|
|
1802
|
-
if (t >= 1 - 1e-9) {
|
|
1803
|
-
return { simplex: [B], closest: B.p, bary: [1] };
|
|
1804
|
-
}
|
|
1805
|
-
return { simplex: [A, B], closest, bary: [1 - t, t] };
|
|
1806
|
-
}
|
|
1807
|
-
// Triangle case (A,B,C) – use Ericson's closest-point to triangle (p=origin)
|
|
1808
|
-
const A = simplex[0], B = simplex[1], C = simplex[2];
|
|
1809
|
-
const a = A.p, b = B.p, c = C.p;
|
|
1810
|
-
const ab = ptSubtract(b, a), ac = ptSubtract(c, a), ap = { x: -a.x, y: -a.y };
|
|
1811
|
-
const d1 = dotProduct(ab, ap), d2 = dotProduct(ac, ap);
|
|
1812
|
-
if (d1 <= 0 && d2 <= 0)
|
|
1813
|
-
return { simplex: [A], closest: a, bary: [1] };
|
|
1814
|
-
const bp = { x: -b.x, y: -b.y };
|
|
1815
|
-
const d3 = dotProduct(ab, bp), d4 = dotProduct(ac, bp);
|
|
1816
|
-
if (d3 >= 0 && d4 <= d3)
|
|
1817
|
-
return { simplex: [B], closest: b, bary: [1] };
|
|
1818
|
-
const vc = d1 * d4 - d3 * d2;
|
|
1819
|
-
if (vc <= 0 && d1 >= 0 && d3 <= 0) {
|
|
1820
|
-
const v = d1 / (d1 - d3);
|
|
1821
|
-
const closest = ptAdd(a, ptMultiply(ab, v));
|
|
1822
|
-
return { simplex: [A, B], closest, bary: [1 - v, v] };
|
|
1823
|
-
}
|
|
1824
|
-
const cp = { x: -c.x, y: -c.y };
|
|
1825
|
-
const bc = ptSubtract(c, b);
|
|
1826
|
-
const d5 = dotProduct(bc, cp), d6 = dotProduct(ac, cp);
|
|
1827
|
-
if (d6 >= 0 && d5 <= d6)
|
|
1828
|
-
return { simplex: [C], closest: c, bary: [1] };
|
|
1829
|
-
const vb = d5 * d2 - d1 * d6;
|
|
1830
|
-
if (vb <= 0 && d2 >= 0 && d6 <= 0) {
|
|
1831
|
-
const w = d2 / (d2 - d6);
|
|
1832
|
-
const closest = ptAdd(a, ptMultiply(ac, w));
|
|
1833
|
-
return { simplex: [A, C], closest, bary: [1 - w, 0, w] };
|
|
1834
|
-
}
|
|
1835
|
-
const va = d3 * d6 - d5 * d4;
|
|
1836
|
-
if (va <= 0) {
|
|
1837
|
-
const denom = (d4 - d3) + (d5 - d6);
|
|
1838
|
-
const w = denom !== 0 ? (d4 - d3) / denom : 0.5;
|
|
1839
|
-
const closest = ptAdd(b, ptMultiply(bc, w));
|
|
1840
|
-
return { simplex: [B, C], closest, bary: [0, 1 - w, w] };
|
|
1841
|
-
}
|
|
1842
|
-
// Origin inside triangle – distance is zero
|
|
1843
|
-
return { simplex: [A, B, C], closest: { x: 0, y: 0 }, bary: [0, 0, 0] };
|
|
1823
|
+
/**
|
|
1824
|
+
* Clamps a value to a range in a way, that when it is over in one end, then it appears from the other end
|
|
1825
|
+
* @param value
|
|
1826
|
+
* @param min
|
|
1827
|
+
* @param max
|
|
1828
|
+
*/
|
|
1829
|
+
function overflow(value, min, max) {
|
|
1830
|
+
const range = normalizeRange(min, max);
|
|
1831
|
+
const length = range[1] - range[0];
|
|
1832
|
+
return ((((value - range[0]) % length) + length) % length) + range[0];
|
|
1844
1833
|
}
|
|
1845
1834
|
/**
|
|
1846
|
-
*
|
|
1847
|
-
*
|
|
1848
|
-
* @param
|
|
1849
|
-
* @param
|
|
1835
|
+
* Checks if a number is equal to b number with epsilon tolerance
|
|
1836
|
+
* @param a
|
|
1837
|
+
* @param b
|
|
1838
|
+
* @param epsilon
|
|
1850
1839
|
*/
|
|
1840
|
+
function isEqual(a, b, epsilon = null) {
|
|
1841
|
+
epsilon = ObjectUtils.isNumber(epsilon) ? epsilon : EPSILON;
|
|
1842
|
+
return Math.abs(a - b) <= epsilon;
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Checks if a number is equal to zero with epsilon tolerance
|
|
1846
|
+
* @param a
|
|
1847
|
+
* @param epsilon
|
|
1848
|
+
*/
|
|
1849
|
+
function isZero(a, epsilon = null) {
|
|
1850
|
+
return isEqual(a, 0, epsilon);
|
|
1851
|
+
}
|
|
1852
|
+
class MathUtils {
|
|
1853
|
+
static equal(a, b, epsilon = null) {
|
|
1854
|
+
return isEqual(a, b, epsilon);
|
|
1855
|
+
}
|
|
1856
|
+
static clamp(value, min, max) {
|
|
1857
|
+
return clamp(value, min, max);
|
|
1858
|
+
}
|
|
1859
|
+
static round(value, precision = 2, divider = 1) {
|
|
1860
|
+
precision = Math.pow(10, precision);
|
|
1861
|
+
return Math.round(value * precision / divider) / precision;
|
|
1862
|
+
}
|
|
1863
|
+
static approxIndex(x, values, epsilon = null) {
|
|
1864
|
+
if (!Array.isArray(values) || values.length == 0) {
|
|
1865
|
+
return -1;
|
|
1866
|
+
}
|
|
1867
|
+
let s = 0;
|
|
1868
|
+
let e = values.length - 1;
|
|
1869
|
+
while (s <= e) {
|
|
1870
|
+
const i = Math.floor((s + e) / 2);
|
|
1871
|
+
const v = values[i];
|
|
1872
|
+
if (MathUtils.equal(v, x, epsilon)) {
|
|
1873
|
+
return i;
|
|
1874
|
+
}
|
|
1875
|
+
if (v < x) {
|
|
1876
|
+
s = i + 1;
|
|
1877
|
+
}
|
|
1878
|
+
else {
|
|
1879
|
+
e = i - 1;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
const m = Math.max(e, 0);
|
|
1883
|
+
const a = values[s];
|
|
1884
|
+
const b = values[m];
|
|
1885
|
+
return Math.abs(a - x) < Math.abs(b - x) ? s : m;
|
|
1886
|
+
}
|
|
1887
|
+
static approximate(x, values, epsilon = null) {
|
|
1888
|
+
const index = MathUtils.approxIndex(x, values, epsilon);
|
|
1889
|
+
return values[index] ?? null;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
const MAX_ITERS = 40;
|
|
1894
|
+
// =========================
|
|
1895
|
+
// GJK distance (robust)
|
|
1896
|
+
// =========================
|
|
1851
1897
|
function gjkDistance(A, B) {
|
|
1852
|
-
|
|
1853
|
-
const
|
|
1854
|
-
|
|
1898
|
+
// 1) Quick overlap
|
|
1899
|
+
const inter = gjkIntersection(A, B);
|
|
1900
|
+
if (inter.hit) {
|
|
1901
|
+
// Pass through pa/pb
|
|
1902
|
+
return { distance: 0, pa: inter.pa ?? null, pb: inter.pb ?? null };
|
|
1903
|
+
}
|
|
1904
|
+
const ca = A.center;
|
|
1905
|
+
const cb = B.center;
|
|
1906
|
+
// 2) Bisection along the center-line to find the first hit pose
|
|
1907
|
+
let s = 0;
|
|
1908
|
+
let e = 1;
|
|
1909
|
+
let iters = 0;
|
|
1910
|
+
// Keep the best "hit" snapshot and its center so we can map witnesses back
|
|
1911
|
+
let hitSnap = null;
|
|
1912
|
+
let hitCenter = ca;
|
|
1913
|
+
while ((e - s) > EPSILON && iters < MAX_ITERS) {
|
|
1914
|
+
iters++;
|
|
1915
|
+
const t = (e + s) * 0.5;
|
|
1916
|
+
// Assumes A.move(newCenter) returns a NEW shape whose center is exactly this point
|
|
1917
|
+
const aMoved = A.move(lerpPts(ca, cb, t));
|
|
1918
|
+
const test = gjkIntersection(aMoved, B);
|
|
1919
|
+
if (test.hit) {
|
|
1920
|
+
hitSnap = test;
|
|
1921
|
+
hitCenter = aMoved.center;
|
|
1922
|
+
e = t; // shrink toward contact
|
|
1923
|
+
}
|
|
1924
|
+
else {
|
|
1925
|
+
s = t; // still separated
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
// 3) Make sure we end with a hit snapshot (in case we stopped on iteration cap)
|
|
1929
|
+
if (!hitSnap) {
|
|
1930
|
+
const aMoved = A.move(lerpPts(ca, cb, e));
|
|
1931
|
+
const test = gjkIntersection(aMoved, B);
|
|
1932
|
+
if (test.hit) {
|
|
1933
|
+
hitSnap = test;
|
|
1934
|
+
hitCenter = aMoved.center;
|
|
1935
|
+
}
|
|
1936
|
+
else {
|
|
1937
|
+
// Extremely degenerate: no hit even at e ~ 1 (shouldn't happen for non-degenerate shapes).
|
|
1938
|
+
// Fall back to center-line direction as a last resort.
|
|
1939
|
+
const dir = normalizePt(subPts(cb, ca));
|
|
1940
|
+
const pa0 = A.support(dir);
|
|
1941
|
+
const pb0 = B.support(negatePt(dir));
|
|
1942
|
+
return { distance: distance(pa0, pb0), pa: pa0, pb: pb0 };
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
// 4) Map witnesses back to the original A pose
|
|
1946
|
+
// (We moved A by (hitCenter - ca); to undo, offset A's witness by (ca - hitCenter))
|
|
1947
|
+
const offset = subPts(ca, hitCenter);
|
|
1948
|
+
const pa0 = addPts(hitSnap.pa, offset);
|
|
1949
|
+
const pb0 = hitSnap.pb;
|
|
1950
|
+
// 5) True geometric separation is the distance between these boundary points
|
|
1951
|
+
const d = distance(pa0, pb0);
|
|
1952
|
+
return {
|
|
1953
|
+
distance: d,
|
|
1954
|
+
pa: d > 0 ? pa0 : null,
|
|
1955
|
+
pb: d > 0 ? pb0 : null
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
// =========================
|
|
1959
|
+
// Boolean GJK (robust)
|
|
1960
|
+
// =========================
|
|
1961
|
+
function gjkIntersection(A, B) {
|
|
1962
|
+
const MAX = 64, EPS = 1e-12;
|
|
1963
|
+
const sup = (dir) => {
|
|
1964
|
+
const a = ensurePoint(A.support(dir), A.center);
|
|
1965
|
+
const b = ensurePoint(B.support(negatePt(dir)), B.center);
|
|
1966
|
+
return { p: subPts(a, b), a, b };
|
|
1967
|
+
};
|
|
1968
|
+
// initial direction: center-to-center; fall back to x-axis
|
|
1969
|
+
let d = subPts(B.center, A.center);
|
|
1855
1970
|
if (Math.abs(d.x) < EPS && Math.abs(d.y) < EPS)
|
|
1856
1971
|
d = { x: 1, y: 0 };
|
|
1857
|
-
const
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1972
|
+
const simplex = [sup(d)];
|
|
1973
|
+
d = { x: -simplex[0].p.x, y: -simplex[0].p.y };
|
|
1974
|
+
for (let i = 0; i < MAX; i++) {
|
|
1975
|
+
// If direction collapses, steer toward origin from last point
|
|
1976
|
+
const dLen = Math.hypot(d.x, d.y);
|
|
1977
|
+
if (dLen <= EPS) {
|
|
1978
|
+
const last = simplex[simplex.length - 1];
|
|
1979
|
+
const AO = { x: -last.p.x, y: -last.p.y };
|
|
1980
|
+
const aoLen = Math.hypot(AO.x, AO.y);
|
|
1981
|
+
d = (aoLen > EPS) ? AO : { x: 1, y: 0 };
|
|
1982
|
+
}
|
|
1983
|
+
const a = sup(d);
|
|
1984
|
+
const s = a.p.x * d.x + a.p.y * d.y;
|
|
1985
|
+
if (s < -1e-12)
|
|
1986
|
+
return { hit: false }; // definite separation
|
|
1987
|
+
if (Math.abs(s) <= 1e-12) { // tangential contact: use normal ±d
|
|
1988
|
+
const L = Math.hypot(d.x, d.y) || 1;
|
|
1989
|
+
const n = { x: d.x / L, y: d.y / L };
|
|
1990
|
+
const pa = ensurePoint(A.support(n), A.center);
|
|
1991
|
+
const pb = ensurePoint(B.support(negatePt(n)), B.center);
|
|
1992
|
+
const point = { x: (pa.x + pb.x) / 2, y: (pa.y + pb.y) / 2 };
|
|
1993
|
+
return { hit: true, pa, pb, point };
|
|
1994
|
+
}
|
|
1995
|
+
simplex.push(a);
|
|
1996
|
+
{
|
|
1997
|
+
const info = doSimplexBoolean(simplex, d);
|
|
1998
|
+
if (info.hit)
|
|
1999
|
+
return info;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
// Max iterations without resolution → disjoint
|
|
2003
|
+
return { hit: false };
|
|
2004
|
+
}
|
|
2005
|
+
function doSimplexBoolean(simplex, d) {
|
|
2006
|
+
const last = simplex[simplex.length - 1];
|
|
2007
|
+
const AO = { x: -last.p.x, y: -last.p.y };
|
|
2008
|
+
if (simplex.length === 2) {
|
|
2009
|
+
const B = simplex[0];
|
|
2010
|
+
const AB = { x: B.p.x - last.p.x, y: B.p.y - last.p.y };
|
|
2011
|
+
// Perpendicular to AB toward origin
|
|
2012
|
+
const abPerp = tripleProduct(AB, AO, AB);
|
|
2013
|
+
const perpLen2 = abPerp.x * abPerp.x + abPerp.y * abPerp.y;
|
|
2014
|
+
if (perpLen2 < 1e-24) {
|
|
2015
|
+
// Colinear: project origin onto segment AB
|
|
2016
|
+
const ab2 = AB.x * AB.x + AB.y * AB.y;
|
|
2017
|
+
if (ab2 > 0) {
|
|
2018
|
+
const t = ((AO.x * AB.x + AO.y * AB.y) / ab2);
|
|
2019
|
+
if (t >= 0 && t <= 1) {
|
|
2020
|
+
// Contact at segment
|
|
2021
|
+
const pa = { x: last.a.x + t * (B.a.x - last.a.x), y: last.a.y + t * (B.a.y - last.a.y) };
|
|
2022
|
+
const pb = { x: last.b.x + t * (B.b.x - last.b.x), y: last.b.y + t * (B.b.y - last.b.y) };
|
|
2023
|
+
const point = { x: (pa.x + pb.x) / 2, y: (pa.y + pb.y) / 2 };
|
|
2024
|
+
return { hit: true, pa, pb, point };
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
// Otherwise, move toward origin from A, keep only last point to progress
|
|
2028
|
+
d.x = -AO.x;
|
|
2029
|
+
d.y = -AO.y;
|
|
2030
|
+
simplex.splice(0, simplex.length - 1);
|
|
2031
|
+
return { hit: false };
|
|
2032
|
+
}
|
|
2033
|
+
d.x = abPerp.x;
|
|
2034
|
+
d.y = abPerp.y;
|
|
2035
|
+
return { hit: false };
|
|
2036
|
+
}
|
|
2037
|
+
// Triangle case: [C, B, A] with A = last
|
|
2038
|
+
const A = last, B = simplex[simplex.length - 2], C = simplex[simplex.length - 3];
|
|
2039
|
+
const AB = { x: B.p.x - A.p.x, y: B.p.y - A.p.y };
|
|
2040
|
+
const AC = { x: C.p.x - A.p.x, y: C.p.y - A.p.y };
|
|
2041
|
+
const perpAB = tripleProduct(AC, AB, AB);
|
|
2042
|
+
const perpAC = tripleProduct(AB, AC, AC);
|
|
2043
|
+
// If origin is outside AB region
|
|
2044
|
+
if (perpAB.x * AO.x + perpAB.y * AO.y > 0) {
|
|
2045
|
+
simplex.splice(simplex.length - 3, 1);
|
|
2046
|
+
d.x = perpAB.x;
|
|
2047
|
+
d.y = perpAB.y;
|
|
2048
|
+
return { hit: false };
|
|
2049
|
+
}
|
|
2050
|
+
// If origin is outside AC region
|
|
2051
|
+
if (perpAC.x * AO.x + perpAC.y * AO.y > 0) {
|
|
2052
|
+
simplex.splice(simplex.length - 2, 1);
|
|
2053
|
+
d.x = perpAC.x;
|
|
2054
|
+
d.y = perpAC.y;
|
|
2055
|
+
return { hit: false };
|
|
2056
|
+
}
|
|
2057
|
+
// Otherwise the origin is inside the triangle → overlap
|
|
2058
|
+
// Compute barycentric weights of origin w.r.t triangle (A,B,C)
|
|
2059
|
+
const v0 = AB; // B-A
|
|
2060
|
+
const v1 = AC; // C-A
|
|
2061
|
+
const b = { x: -A.p.x, y: -A.p.y };
|
|
2062
|
+
const det = v0.x * v1.y - v0.y * v1.x;
|
|
2063
|
+
let uA, uB, uC;
|
|
2064
|
+
if (Math.abs(det) > 1e-24) {
|
|
2065
|
+
const alpha = (b.x * v1.y - v1.x * b.y) / det; // weight for v0 (B)
|
|
2066
|
+
const beta = (v0.x * b.y - b.x * v0.y) / det; // weight for v1 (C)
|
|
2067
|
+
uB = alpha;
|
|
2068
|
+
uC = beta;
|
|
2069
|
+
uA = 1 - alpha - beta;
|
|
2070
|
+
}
|
|
2071
|
+
else {
|
|
2072
|
+
// Fallback: equal weights
|
|
2073
|
+
uA = uB = uC = 1 / 3;
|
|
2074
|
+
}
|
|
2075
|
+
const pa = {
|
|
2076
|
+
x: uA * A.a.x + uB * B.a.x + uC * C.a.x,
|
|
2077
|
+
y: uA * A.a.y + uB * B.a.y + uC * C.a.y
|
|
1861
2078
|
};
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
const a = simplex[simplex.length - 1].a, b = simplex[simplex.length - 1].b;
|
|
1869
|
-
return { distance: 0, pa: a, pb: b };
|
|
1870
|
-
}
|
|
1871
|
-
const vtx = sup(dir);
|
|
1872
|
-
// termination: support didn't pass beyond previous closest along dir
|
|
1873
|
-
if (dotProduct(vtx.p, dir) - Math.sqrt(best2) <= EPS) {
|
|
1874
|
-
break;
|
|
1875
|
-
}
|
|
1876
|
-
simplex.push(vtx);
|
|
1877
|
-
const reduced = closestPointToOrigin(simplex);
|
|
1878
|
-
simplex = reduced.simplex;
|
|
1879
|
-
closest = reduced.closest;
|
|
1880
|
-
best2 = dotProduct(closest, closest);
|
|
1881
|
-
dir = { x: -closest.x, y: -closest.y };
|
|
1882
|
-
if (best2 <= EPS * EPS) {
|
|
1883
|
-
const pa = combineA(simplex, reduced.bary);
|
|
1884
|
-
const pb = combineB(simplex, reduced.bary);
|
|
1885
|
-
return { distance: 0, pa, pb };
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
const res = closestPointToOrigin(simplex);
|
|
1889
|
-
const pa = combineA(simplex, res.bary);
|
|
1890
|
-
const pb = combineB(simplex, res.bary);
|
|
1891
|
-
return { distance: Math.sqrt(dotProduct(res.closest, res.closest)), pa, pb };
|
|
2079
|
+
const pb = {
|
|
2080
|
+
x: uA * A.b.x + uB * B.b.x + uC * C.b.x,
|
|
2081
|
+
y: uA * A.b.y + uB * B.b.y + uC * C.b.y
|
|
2082
|
+
};
|
|
2083
|
+
const point = { x: (pa.x + pb.x) / 2, y: (pa.y + pb.y) / 2 };
|
|
2084
|
+
return { hit: true, pa, pb, point };
|
|
1892
2085
|
}
|
|
1893
2086
|
|
|
1894
2087
|
class Shape {
|
|
@@ -1904,17 +2097,23 @@ class Shape {
|
|
|
1904
2097
|
constructor(x, y) {
|
|
1905
2098
|
this.pt = { x, y };
|
|
1906
2099
|
}
|
|
1907
|
-
|
|
1908
|
-
return
|
|
2100
|
+
intersection(shape) {
|
|
2101
|
+
return gjkIntersection(this, shape);
|
|
2102
|
+
}
|
|
2103
|
+
intersects(shape) {
|
|
2104
|
+
return this.intersection(shape).hit;
|
|
1909
2105
|
}
|
|
1910
2106
|
minDistance(shape) {
|
|
1911
|
-
return gjkDistance(this, shape)
|
|
2107
|
+
return gjkDistance(this, shape);
|
|
2108
|
+
}
|
|
2109
|
+
distance(shape) {
|
|
2110
|
+
return this.minDistance(shape).distance;
|
|
1912
2111
|
}
|
|
1913
2112
|
}
|
|
1914
2113
|
class Point extends Shape {
|
|
1915
2114
|
static { this.Zero = new Point(0, 0); }
|
|
1916
2115
|
get length() {
|
|
1917
|
-
return
|
|
2116
|
+
return lengthOfPt(this);
|
|
1918
2117
|
}
|
|
1919
2118
|
get perpendicular() {
|
|
1920
2119
|
return new Point(perpendicular(this));
|
|
@@ -1925,32 +2124,35 @@ class Point extends Shape {
|
|
|
1925
2124
|
this.pt = isPoint(xOrP) ? xOrP : { x: isNaN(x) ? 0 : xOrP, y };
|
|
1926
2125
|
}
|
|
1927
2126
|
support() {
|
|
1928
|
-
return this.
|
|
2127
|
+
return this.center;
|
|
2128
|
+
}
|
|
2129
|
+
move(pos) {
|
|
2130
|
+
return new Point(pos);
|
|
1929
2131
|
}
|
|
1930
2132
|
add(p) {
|
|
1931
|
-
return new Point(
|
|
2133
|
+
return new Point(addPts(this, p));
|
|
2134
|
+
}
|
|
2135
|
+
subtract(p) {
|
|
2136
|
+
return new Point(subPts(this, p));
|
|
1932
2137
|
}
|
|
1933
|
-
|
|
1934
|
-
return new Point(
|
|
2138
|
+
multiply(p) {
|
|
2139
|
+
return new Point(multiplyPts(this, p));
|
|
1935
2140
|
}
|
|
1936
|
-
|
|
1937
|
-
return new Point(
|
|
2141
|
+
divide(p) {
|
|
2142
|
+
return new Point(multiplyPts(this, p));
|
|
1938
2143
|
}
|
|
1939
2144
|
dot(p) {
|
|
1940
2145
|
return new Point(dotProduct(this, p));
|
|
1941
2146
|
}
|
|
1942
|
-
distance(p) {
|
|
1943
|
-
return ptDistance(this, p);
|
|
1944
|
-
}
|
|
1945
2147
|
lerp(p, ratio) {
|
|
1946
|
-
const diff = p.
|
|
1947
|
-
return this.add(diff.
|
|
2148
|
+
const diff = p.subtract(this);
|
|
2149
|
+
return this.add(diff.multiply(ratio));
|
|
1948
2150
|
}
|
|
1949
2151
|
perpendicularTo(p, length) {
|
|
1950
|
-
const diff = p.perpendicular.
|
|
2152
|
+
const diff = p.perpendicular.subtract(this.perpendicular);
|
|
1951
2153
|
const ratio = length / diff.length;
|
|
1952
2154
|
const center = this.lerp(p, .5);
|
|
1953
|
-
return center.add(diff.
|
|
2155
|
+
return center.add(diff.multiply(ratio));
|
|
1954
2156
|
}
|
|
1955
2157
|
circleWith(a, b) {
|
|
1956
2158
|
const yDelta_a = b.y - a.y;
|
|
@@ -1965,8 +2167,8 @@ class Point extends Shape {
|
|
|
1965
2167
|
return new Circle(center.x, center.y, center.distance(this));
|
|
1966
2168
|
}
|
|
1967
2169
|
tangents(c) {
|
|
1968
|
-
const pd =
|
|
1969
|
-
const a = Math.asin(c.radius /
|
|
2170
|
+
const pd = subPts(c.center, this);
|
|
2171
|
+
const a = Math.asin(c.radius / lengthOfPt(pd));
|
|
1970
2172
|
const b = Math.atan2(pd.y, pd.x);
|
|
1971
2173
|
// Tangent points
|
|
1972
2174
|
let t = b - a;
|
|
@@ -1976,7 +2178,7 @@ class Point extends Shape {
|
|
|
1976
2178
|
return [t1, t2];
|
|
1977
2179
|
}
|
|
1978
2180
|
angle(p) {
|
|
1979
|
-
const diff = p.
|
|
2181
|
+
const diff = p.subtract(this);
|
|
1980
2182
|
return Math.atan2(diff.y, diff.x) * 180 / Math.PI;
|
|
1981
2183
|
}
|
|
1982
2184
|
rotateAround(p, angle) {
|
|
@@ -1995,11 +2197,16 @@ class Rect extends Shape {
|
|
|
1995
2197
|
}
|
|
1996
2198
|
support(dir) {
|
|
1997
2199
|
const ang = this.rotation ?? 0;
|
|
1998
|
-
const dLocal = rotateDeg(dir, -ang);
|
|
2200
|
+
const dLocal = rotateDeg(ensurePoint(dir, { x: 1, y: 0 }), -ang);
|
|
1999
2201
|
const hw = Math.max(0, this.width / 2), hh = Math.max(0, this.height / 2);
|
|
2202
|
+
if (hw === 0 && hh === 0)
|
|
2203
|
+
return ensurePoint(this.center);
|
|
2000
2204
|
const lx = dLocal.x >= 0 ? hw : -hw;
|
|
2001
2205
|
const ly = dLocal.y >= 0 ? hh : -hh;
|
|
2002
|
-
return
|
|
2206
|
+
return addPts(rotateDeg({ x: lx, y: ly }, ang), this.center);
|
|
2207
|
+
}
|
|
2208
|
+
move(pos) {
|
|
2209
|
+
return new Rect(pos.x, pos.y, this.width, this.height, this.rotation);
|
|
2003
2210
|
}
|
|
2004
2211
|
}
|
|
2005
2212
|
class Oval extends Shape {
|
|
@@ -2011,15 +2218,16 @@ class Oval extends Shape {
|
|
|
2011
2218
|
}
|
|
2012
2219
|
support(dir) {
|
|
2013
2220
|
const ang = this.rotation ?? 0;
|
|
2014
|
-
const d = rotateDeg(dir, -ang);
|
|
2015
|
-
const a = Math.max(0, this.width / 2);
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2221
|
+
const d = rotateDeg(ensurePoint(dir, { x: 1, y: 0 }), -ang);
|
|
2222
|
+
const a = Math.max(0, this.width / 2), b = Math.max(0, this.height / 2);
|
|
2223
|
+
if (a === 0 && b === 0)
|
|
2224
|
+
return ensurePoint(this.center);
|
|
2225
|
+
const q = Math.hypot(a * d.x, b * d.y) || 1;
|
|
2226
|
+
const lx = (a * a * d.x) / q, ly = (b * b * d.y) / q;
|
|
2227
|
+
return addPts(rotateDeg({ x: lx, y: ly }, ang), this.center);
|
|
2228
|
+
}
|
|
2229
|
+
move(pos) {
|
|
2230
|
+
return new Oval(pos.x, pos.y, this.width, this.height, this.rotation);
|
|
2023
2231
|
}
|
|
2024
2232
|
}
|
|
2025
2233
|
class Circle extends Oval {
|
|
@@ -2027,6 +2235,9 @@ class Circle extends Oval {
|
|
|
2027
2235
|
super(x, y, radius * 2, radius * 2, rotation);
|
|
2028
2236
|
this.radius = radius;
|
|
2029
2237
|
}
|
|
2238
|
+
move(pos) {
|
|
2239
|
+
return new Circle(pos.x, pos.y, this.radius, this.rotation);
|
|
2240
|
+
}
|
|
2030
2241
|
}
|
|
2031
2242
|
|
|
2032
2243
|
class Initializer {
|
|
@@ -2158,49 +2369,6 @@ class LoaderUtils {
|
|
|
2158
2369
|
}
|
|
2159
2370
|
}
|
|
2160
2371
|
|
|
2161
|
-
const EPSILON = 1e-9;
|
|
2162
|
-
class MathUtils {
|
|
2163
|
-
static equal(a, b, epsilon = null) {
|
|
2164
|
-
epsilon = ObjectUtils.isNumber(epsilon) ? epsilon : EPSILON;
|
|
2165
|
-
return Math.abs(a - b) < epsilon;
|
|
2166
|
-
}
|
|
2167
|
-
static clamp(value, min, max) {
|
|
2168
|
-
return Math.max(Math.min(value, max), min);
|
|
2169
|
-
}
|
|
2170
|
-
static round(value, precision = 2, divider = 1) {
|
|
2171
|
-
precision = Math.pow(10, precision);
|
|
2172
|
-
return Math.round(value * precision / divider) / precision;
|
|
2173
|
-
}
|
|
2174
|
-
static approxIndex(x, values, epsilon = null) {
|
|
2175
|
-
if (!Array.isArray(values) || values.length == 0) {
|
|
2176
|
-
return -1;
|
|
2177
|
-
}
|
|
2178
|
-
let s = 0;
|
|
2179
|
-
let e = values.length - 1;
|
|
2180
|
-
while (s <= e) {
|
|
2181
|
-
const i = Math.floor((s + e) / 2);
|
|
2182
|
-
const v = values[i];
|
|
2183
|
-
if (MathUtils.equal(v, x, epsilon)) {
|
|
2184
|
-
return i;
|
|
2185
|
-
}
|
|
2186
|
-
if (v < x) {
|
|
2187
|
-
s = i + 1;
|
|
2188
|
-
}
|
|
2189
|
-
else {
|
|
2190
|
-
e = i - 1;
|
|
2191
|
-
}
|
|
2192
|
-
}
|
|
2193
|
-
const m = Math.max(e, 0);
|
|
2194
|
-
const a = values[s];
|
|
2195
|
-
const b = values[m];
|
|
2196
|
-
return Math.abs(a - x) < Math.abs(b - x) ? s : m;
|
|
2197
|
-
}
|
|
2198
|
-
static approximate(x, values, epsilon = null) {
|
|
2199
|
-
const index = MathUtils.approxIndex(x, values, epsilon);
|
|
2200
|
-
return values[index] ?? null;
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
|
|
2204
2372
|
function isBrowser() {
|
|
2205
2373
|
return typeof window !== "undefined";
|
|
2206
2374
|
}
|
|
@@ -7669,29 +7837,31 @@ class InteractiveItemComponent {
|
|
|
7669
7837
|
}
|
|
7670
7838
|
hit(point) {
|
|
7671
7839
|
for (const shape of this.shapes) {
|
|
7672
|
-
if (shape.
|
|
7840
|
+
if (shape.intersects(point))
|
|
7673
7841
|
return true;
|
|
7674
7842
|
}
|
|
7675
7843
|
return false;
|
|
7676
7844
|
}
|
|
7677
|
-
|
|
7678
|
-
if (this.direction === "none")
|
|
7845
|
+
moveTo(x, y) {
|
|
7846
|
+
if (!this.canvas || this.direction === "none")
|
|
7679
7847
|
return;
|
|
7680
|
-
|
|
7681
|
-
|
|
7682
|
-
this.pos = new Point(this.pos.x + dx, this.pos.y);
|
|
7683
|
-
break;
|
|
7684
|
-
case "vertical":
|
|
7685
|
-
this.pos = new Point(this.pos.x, this.pos.y + dy);
|
|
7686
|
-
break;
|
|
7687
|
-
default:
|
|
7688
|
-
this.pos = new Point(this.pos.x + dx, this.pos.y + dy);
|
|
7689
|
-
break;
|
|
7690
|
-
}
|
|
7848
|
+
const target = this.restrictPosition(this.direction === "vertical" ? this.pos.x : x, this.direction === "horizontal" ? this.pos.y : y);
|
|
7849
|
+
this.pos = new Point(target);
|
|
7691
7850
|
this.calcShapes();
|
|
7692
|
-
this.valid = this.isValidByParams() &&
|
|
7851
|
+
this.valid = this.isValidByParams() &&
|
|
7852
|
+
this.canvas.items.every(other => this === other || this.isValidByDistance(other));
|
|
7693
7853
|
this.validPos = this.valid ? this.pos : this.validPos;
|
|
7694
7854
|
}
|
|
7855
|
+
moveBy(dx, dy) {
|
|
7856
|
+
const { x, y } = this.pos;
|
|
7857
|
+
this.moveTo(x + dx, y + dy);
|
|
7858
|
+
}
|
|
7859
|
+
moveX(x) {
|
|
7860
|
+
this.moveTo(x, this.pos.y);
|
|
7861
|
+
}
|
|
7862
|
+
moveY(y) {
|
|
7863
|
+
this.moveTo(this.pos.x, y);
|
|
7864
|
+
}
|
|
7695
7865
|
moveEnd() {
|
|
7696
7866
|
if (this.valid)
|
|
7697
7867
|
return;
|
|
@@ -7699,14 +7869,26 @@ class InteractiveItemComponent {
|
|
|
7699
7869
|
this.valid = true;
|
|
7700
7870
|
this.calcShapes();
|
|
7701
7871
|
}
|
|
7872
|
+
restrictPosition(x, y) {
|
|
7873
|
+
return {
|
|
7874
|
+
x: clamp(x, this.canvas.xRange),
|
|
7875
|
+
y: this.canvas.infinite
|
|
7876
|
+
? overflow(y, this.canvas.yRange)
|
|
7877
|
+
: clamp(y, this.canvas.yRange)
|
|
7878
|
+
};
|
|
7879
|
+
}
|
|
7702
7880
|
isValidByParams() {
|
|
7703
|
-
return
|
|
7881
|
+
return !this.shapes.some(shape => {
|
|
7882
|
+
return this.canvas.excludedAreas.some(ex => {
|
|
7883
|
+
return shape.intersects(ex);
|
|
7884
|
+
});
|
|
7885
|
+
});
|
|
7704
7886
|
}
|
|
7705
7887
|
isValidByDistance(other) {
|
|
7706
7888
|
const minPixels = this.distToPixels(this.getMinDistance(other));
|
|
7707
7889
|
return !this.shapes.some(shape => {
|
|
7708
7890
|
return other.shapes.some(os => {
|
|
7709
|
-
return
|
|
7891
|
+
return shape.distance(os) <= minPixels;
|
|
7710
7892
|
});
|
|
7711
7893
|
});
|
|
7712
7894
|
}
|
|
@@ -7760,19 +7942,19 @@ class InteractiveCanvasComponent {
|
|
|
7760
7942
|
set hoveredItem(item) {
|
|
7761
7943
|
this.hoveredIndex = !item ? -1 : this.items.indexOf(item);
|
|
7762
7944
|
}
|
|
7763
|
-
constructor(renderer, universal
|
|
7945
|
+
constructor(renderer, universal) {
|
|
7764
7946
|
this.renderer = renderer;
|
|
7765
7947
|
this.universal = universal;
|
|
7766
|
-
this.
|
|
7767
|
-
this.
|
|
7948
|
+
this.infinite = false;
|
|
7949
|
+
this.resizeMode = "fit";
|
|
7950
|
+
this.params = {};
|
|
7768
7951
|
this.debug = false;
|
|
7769
7952
|
this.horizontal = false;
|
|
7770
7953
|
this.selectedIndex = 0;
|
|
7771
|
-
this.resizeMode = "fit";
|
|
7772
7954
|
this.realWidth = 100;
|
|
7773
7955
|
this.realHeight = 100;
|
|
7774
7956
|
this.panOffset = 0;
|
|
7775
|
-
this.
|
|
7957
|
+
this.renderCtx = {};
|
|
7776
7958
|
this.beforeItems = [];
|
|
7777
7959
|
this.afterItems = [];
|
|
7778
7960
|
this.selectedIndexChange = new EventEmitter();
|
|
@@ -7784,16 +7966,21 @@ class InteractiveCanvasComponent {
|
|
|
7784
7966
|
this.$items = new BehaviorSubject([]);
|
|
7785
7967
|
this.tempCanvas = this.universal.isServer ? null : document.createElement("canvas");
|
|
7786
7968
|
this.shouldDraw = !this.universal.isServer;
|
|
7787
|
-
this.
|
|
7969
|
+
this.hoveredIndex = null;
|
|
7970
|
+
this.xRange = [0, 1];
|
|
7971
|
+
this.yRange = [0, 1];
|
|
7972
|
+
this.ratio = 1;
|
|
7973
|
+
this.styles = null;
|
|
7974
|
+
this.ctx = null;
|
|
7788
7975
|
this.canvasWidth = 0;
|
|
7789
7976
|
this.canvasHeight = 0;
|
|
7790
|
-
this.
|
|
7977
|
+
this.rotation = 0;
|
|
7978
|
+
this.basePan = 0;
|
|
7979
|
+
this.cycles = [0];
|
|
7980
|
+
this.excludedAreas = [];
|
|
7791
7981
|
this.touched = false;
|
|
7792
|
-
this.
|
|
7793
|
-
this.
|
|
7794
|
-
this.ctrInit();
|
|
7795
|
-
}
|
|
7796
|
-
ctrInit() {
|
|
7982
|
+
this.panStartRotation = 0;
|
|
7983
|
+
this.panStartPos = Point.Zero;
|
|
7797
7984
|
}
|
|
7798
7985
|
ngOnInit() {
|
|
7799
7986
|
this.redraw();
|
|
@@ -7807,6 +7994,8 @@ class InteractiveCanvasComponent {
|
|
|
7807
7994
|
this.renderCtx = this.renderCtx || {};
|
|
7808
7995
|
this.beforeItems = this.beforeItems || [];
|
|
7809
7996
|
this.afterItems = this.afterItems || [];
|
|
7997
|
+
this.xRange = normalizeRange(this.params.xRange || [0, this.realWidth]);
|
|
7998
|
+
this.yRange = normalizeRange(this.params.yRange || [0, this.realHeight]);
|
|
7810
7999
|
this.resize();
|
|
7811
8000
|
}
|
|
7812
8001
|
ngAfterViewInit() {
|
|
@@ -7872,20 +8061,21 @@ class InteractiveCanvasComponent {
|
|
|
7872
8061
|
this.updateCursor();
|
|
7873
8062
|
}
|
|
7874
8063
|
onMouseLeave() {
|
|
8064
|
+
if (this.touched)
|
|
8065
|
+
return;
|
|
7875
8066
|
this.hoveredIndex = null;
|
|
7876
8067
|
this.updateCursor();
|
|
7877
8068
|
}
|
|
7878
8069
|
onPanStart() {
|
|
7879
|
-
this.
|
|
7880
|
-
this.
|
|
8070
|
+
this.panStartRotation = this.rotation;
|
|
8071
|
+
this.panStartPos = this.lockedItem?.position || Point.Zero;
|
|
7881
8072
|
}
|
|
7882
8073
|
onPanMove($event) {
|
|
7883
8074
|
const item = this.lockedItem;
|
|
7884
|
-
const deltaX =
|
|
7885
|
-
const deltaY =
|
|
8075
|
+
const deltaX = $event.deltaX / this.ratio;
|
|
8076
|
+
const deltaY = $event.deltaY / this.ratio;
|
|
7886
8077
|
const data = {
|
|
7887
8078
|
canvas: this,
|
|
7888
|
-
pointers: $event.pointers,
|
|
7889
8079
|
item,
|
|
7890
8080
|
deltaX,
|
|
7891
8081
|
deltaY
|
|
@@ -7895,22 +8085,19 @@ class InteractiveCanvasComponent {
|
|
|
7895
8085
|
data.deltaY = +deltaX;
|
|
7896
8086
|
}
|
|
7897
8087
|
if (item) {
|
|
7898
|
-
item.
|
|
8088
|
+
item.moveTo(this.panStartPos.x + data.deltaX, this.panStartPos.y + data.deltaY);
|
|
7899
8089
|
this.onItemPan.emit(data);
|
|
7900
8090
|
}
|
|
7901
|
-
else if (this.
|
|
7902
|
-
this.rotation
|
|
8091
|
+
else if (this.infinite) {
|
|
8092
|
+
this.rotation = this.panStartRotation + (this.horizontal ? deltaX : deltaY) / this.realHeight * 360;
|
|
7903
8093
|
this.fixRotation();
|
|
7904
8094
|
this.onPan.emit(data);
|
|
7905
8095
|
}
|
|
7906
|
-
this.deltaX = $event.deltaX;
|
|
7907
|
-
this.deltaY = $event.deltaY;
|
|
7908
8096
|
}
|
|
7909
8097
|
onPanEnd() {
|
|
7910
8098
|
const item = this.lockedItem;
|
|
7911
8099
|
const data = {
|
|
7912
8100
|
canvas: this,
|
|
7913
|
-
pointers: [],
|
|
7914
8101
|
deltaX: 0,
|
|
7915
8102
|
deltaY: 0,
|
|
7916
8103
|
item
|
|
@@ -7927,11 +8114,20 @@ class InteractiveCanvasComponent {
|
|
|
7927
8114
|
fixRotation() {
|
|
7928
8115
|
if (this.fullHeight <= 0)
|
|
7929
8116
|
return;
|
|
7930
|
-
this.rotation = ((this.rotation
|
|
7931
|
-
this.
|
|
7932
|
-
|
|
7933
|
-
this.cycles = this.
|
|
7934
|
-
? [
|
|
8117
|
+
this.rotation = overflow(Math.round(this.rotation * 100) / 100, -180, 180);
|
|
8118
|
+
this.basePan = (this.rotation / 360 - 1) * this.fullHeight
|
|
8119
|
+
+ this.canvasHeight * this.panOffset;
|
|
8120
|
+
this.cycles = this.infinite
|
|
8121
|
+
? [this.basePan - this.fullHeight, this.basePan, this.basePan + this.fullHeight] : [0];
|
|
8122
|
+
this.excludedAreas = (this.params.excludedAreas || []).flatMap(coords => {
|
|
8123
|
+
const x = coords.x * this.ratio;
|
|
8124
|
+
const y = coords.y * this.ratio;
|
|
8125
|
+
const width = coords.width * this.ratio;
|
|
8126
|
+
const height = coords.height * this.ratio;
|
|
8127
|
+
return this.cycles.map(cycle => {
|
|
8128
|
+
return new Rect(x, y + cycle, width, height);
|
|
8129
|
+
});
|
|
8130
|
+
});
|
|
7935
8131
|
this.items.forEach(item => {
|
|
7936
8132
|
item.calcShapes();
|
|
7937
8133
|
});
|
|
@@ -7957,19 +8153,25 @@ class InteractiveCanvasComponent {
|
|
|
7957
8153
|
return;
|
|
7958
8154
|
item.active = !item.active;
|
|
7959
8155
|
}
|
|
8156
|
+
this.hoveredIndex = selected;
|
|
7960
8157
|
}
|
|
7961
|
-
|
|
7962
|
-
if (!pointer || !this.
|
|
8158
|
+
toCanvasPoint(pointer) {
|
|
8159
|
+
if (!pointer || !this.canvas)
|
|
7963
8160
|
return null;
|
|
7964
|
-
const canvasRect = this.
|
|
7965
|
-
|
|
8161
|
+
const canvasRect = this.canvas?.getBoundingClientRect();
|
|
8162
|
+
return this.horizontal
|
|
7966
8163
|
? new Point(canvasRect.bottom - pointer.clientY, pointer.clientX - canvasRect.left)
|
|
7967
8164
|
: new Point(pointer.clientX - canvasRect.left, pointer.clientY - canvasRect.top);
|
|
8165
|
+
}
|
|
8166
|
+
getIndexUnderPointer(pointer) {
|
|
8167
|
+
const point = this.toCanvasPoint(pointer);
|
|
8168
|
+
if (!point || !this.items)
|
|
8169
|
+
return -1;
|
|
7968
8170
|
const length = this.items.length;
|
|
7969
8171
|
for (let ix = 0; ix < length; ix++) {
|
|
7970
8172
|
const item = this.items[ix];
|
|
7971
8173
|
if (item?.hit(point)) {
|
|
7972
|
-
return item.disabled ?
|
|
8174
|
+
return item.disabled ? -1 : ix;
|
|
7973
8175
|
}
|
|
7974
8176
|
}
|
|
7975
8177
|
return -1;
|
|
@@ -8005,17 +8207,15 @@ class InteractiveCanvasComponent {
|
|
|
8005
8207
|
}
|
|
8006
8208
|
async drawItems() {
|
|
8007
8209
|
const ctx = this.ctx;
|
|
8210
|
+
const lockedItem = this.lockedItem;
|
|
8008
8211
|
for (const item of this.items) {
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
ctx.translate(shape.x, shape.y);
|
|
8012
|
-
ctx.lineWidth = 1;
|
|
8013
|
-
ctx.strokeStyle = "black";
|
|
8014
|
-
ctx.fillStyle = "white";
|
|
8015
|
-
await item.draw(ctx);
|
|
8016
|
-
ctx.restore();
|
|
8212
|
+
if (item !== lockedItem) {
|
|
8213
|
+
await this.drawItem(ctx, item);
|
|
8017
8214
|
}
|
|
8018
8215
|
}
|
|
8216
|
+
if (lockedItem) {
|
|
8217
|
+
await this.drawItem(ctx, lockedItem);
|
|
8218
|
+
}
|
|
8019
8219
|
if (!this.debug)
|
|
8020
8220
|
return;
|
|
8021
8221
|
ctx.lineWidth = 2;
|
|
@@ -8038,6 +8238,17 @@ class InteractiveCanvasComponent {
|
|
|
8038
8238
|
}
|
|
8039
8239
|
}
|
|
8040
8240
|
}
|
|
8241
|
+
async drawItem(ctx, item) {
|
|
8242
|
+
for (const shape of item.shapes) {
|
|
8243
|
+
ctx.save();
|
|
8244
|
+
ctx.translate(shape.x, shape.y);
|
|
8245
|
+
ctx.lineWidth = 1;
|
|
8246
|
+
ctx.strokeStyle = "black";
|
|
8247
|
+
ctx.fillStyle = "white";
|
|
8248
|
+
await item.draw(ctx);
|
|
8249
|
+
ctx.restore();
|
|
8250
|
+
}
|
|
8251
|
+
}
|
|
8041
8252
|
async draw() {
|
|
8042
8253
|
const ctx = this.ctx;
|
|
8043
8254
|
const canvas = ctx.canvas;
|
|
@@ -8063,30 +8274,29 @@ class InteractiveCanvasComponent {
|
|
|
8063
8274
|
}
|
|
8064
8275
|
ctx.restore();
|
|
8065
8276
|
}
|
|
8066
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: InteractiveCanvasComponent, deps: [{ token: i0.Renderer2 }, { token: UniversalService }
|
|
8067
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: InteractiveCanvasComponent, isStandalone: false, selector: "interactive-canvas", inputs: {
|
|
8277
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: InteractiveCanvasComponent, deps: [{ token: i0.Renderer2 }, { token: UniversalService }], target: i0.ɵɵFactoryTarget.Component }); }
|
|
8278
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: InteractiveCanvasComponent, isStandalone: false, selector: "interactive-canvas", inputs: { infinite: "infinite", resizeMode: "resizeMode", params: "params", realWidth: "realWidth", realHeight: "realHeight", debug: "debug", horizontal: "horizontal", selectedIndex: "selectedIndex", panOffset: "panOffset", renderCtx: "renderCtx", beforeItems: "beforeItems", afterItems: "afterItems" }, outputs: { selectedIndexChange: "selectedIndexChange", onRotate: "onRotate", onItemPan: "onItemPan", onItemPanned: "onItemPanned", onPan: "onPan", onPanned: "onPanned" }, host: { listeners: { "window:touchend": "onTouchEnd($event)", "window:mouseup": "onMouseUp($event)" } }, queries: [{ propertyName: "itemList", predicate: InteractiveItemComponent }], viewQueries: [{ propertyName: "containerElem", first: true, predicate: ["containerElem"], descendants: true, static: true }, { propertyName: "canvasElem", first: true, predicate: ["canvasElem"], descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: "<div #containerElem\n [ngClass]=\"['interactive-canvas-container', horizontal ? 'horizontal' : 'vertical']\"\n (resize)=\"resize()\"\n (touchstart)=\"onTouchStart($event)\"\n (mousedown)=\"onMouseDown($event)\"\n (mousemove)=\"onMouseMove($event)\"\n (mouseleave)=\"onMouseLeave()\"\n (panend)=\"onPanEnd()\"\n (panmove)=\"onPanMove($event)\"\n (panstart)=\"onPanStart()\">\n <canvas #canvasElem class=\"interactive-canvas-element\"></canvas>\n</div>\n", styles: [".interactive-canvas-container{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center}.interactive-canvas-container .interactive-canvas-element{position:absolute;pointer-events:none}\n"], dependencies: [{ kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] }); }
|
|
8068
8279
|
}
|
|
8069
8280
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: InteractiveCanvasComponent, decorators: [{
|
|
8070
8281
|
type: Component,
|
|
8071
8282
|
args: [{ standalone: false, selector: "interactive-canvas", template: "<div #containerElem\n [ngClass]=\"['interactive-canvas-container', horizontal ? 'horizontal' : 'vertical']\"\n (resize)=\"resize()\"\n (touchstart)=\"onTouchStart($event)\"\n (mousedown)=\"onMouseDown($event)\"\n (mousemove)=\"onMouseMove($event)\"\n (mouseleave)=\"onMouseLeave()\"\n (panend)=\"onPanEnd()\"\n (panmove)=\"onPanMove($event)\"\n (panstart)=\"onPanStart()\">\n <canvas #canvasElem class=\"interactive-canvas-element\"></canvas>\n</div>\n", styles: [".interactive-canvas-container{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center}.interactive-canvas-container .interactive-canvas-element{position:absolute;pointer-events:none}\n"] }]
|
|
8072
|
-
}], ctorParameters: () => [{ type: i0.Renderer2 }, { type: UniversalService },
|
|
8073
|
-
type: Inject,
|
|
8074
|
-
args: [ROOT_ELEMENT]
|
|
8075
|
-
}] }], propDecorators: { debug: [{
|
|
8076
|
-
type: Input
|
|
8077
|
-
}], horizontal: [{
|
|
8078
|
-
type: Input
|
|
8079
|
-
}], selectedIndex: [{
|
|
8283
|
+
}], ctorParameters: () => [{ type: i0.Renderer2 }, { type: UniversalService }], propDecorators: { infinite: [{
|
|
8080
8284
|
type: Input
|
|
8081
8285
|
}], resizeMode: [{
|
|
8082
8286
|
type: Input
|
|
8287
|
+
}], params: [{
|
|
8288
|
+
type: Input
|
|
8083
8289
|
}], realWidth: [{
|
|
8084
8290
|
type: Input
|
|
8085
8291
|
}], realHeight: [{
|
|
8086
8292
|
type: Input
|
|
8087
|
-
}],
|
|
8293
|
+
}], debug: [{
|
|
8088
8294
|
type: Input
|
|
8089
|
-
}],
|
|
8295
|
+
}], horizontal: [{
|
|
8296
|
+
type: Input
|
|
8297
|
+
}], selectedIndex: [{
|
|
8298
|
+
type: Input
|
|
8299
|
+
}], panOffset: [{
|
|
8090
8300
|
type: Input
|
|
8091
8301
|
}], renderCtx: [{
|
|
8092
8302
|
type: Input
|
|
@@ -8913,5 +9123,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImpo
|
|
|
8913
9123
|
* Generated bundle index. Do not edit.
|
|
8914
9124
|
*/
|
|
8915
9125
|
|
|
8916
|
-
export { API_SERVICE, APP_BASE_URL, AUTH_SERVICE, AclService, AjaxRequestHandler, ApiService, ArrayUtils, AsyncMethodBase, AsyncMethodDirective, AsyncMethodTargetDirective, AuthGuard, BASE_CONFIG, BUTTON_TYPE, BackgroundDirective, BaseDialogService, BaseHttpClient, BaseHttpService, BaseToasterService, BtnComponent, BtnDefaultComponent, CONFIG_SERVICE, CacheService, CanvasColor, CanvasUtils, ChipsComponent, ChunkPipe, Circle, CloseBtnComponent, ComponentLoaderDirective, ComponentLoaderService, ConfigService, DIALOG_SERVICE, DateUtils, DragDropEventPlugin, DropListComponent, DropdownBoxComponent, DropdownContentDirective, DropdownDirective, DropdownToggleDirective, DynamicTableComponent, DynamicTableTemplateDirective, ERROR_HANDLER, EXPRESS_REQUEST, EntriesPipe, ErrorHandlerService, EventsService, ExtraItemPropertiesPipe, FactoryDependencies, FakeModuleComponent, FileSystemEntry, FileUtils, FilterPipe, FindPipe, ForbiddenZone, FormatNumberPipe, FormatterService, GenericValue, GetOffsetPipe, GetTypePipe, GetValuePipe, GlobalTemplateDirective, GlobalTemplatePipe, GlobalTemplateService, GroupByPipe, ICON_MAP, ICON_SERVICE, ICON_TYPE, IConfiguration, IconComponent, IconDefaultComponent, IconDirective, IconService, IncludesPipe, Initializer, InteractiveCanvasComponent, InteractiveCircleComponent, InteractiveItemComponent, InteractiveRectComponent, IsTypePipe, JSONfn, JoinPipe, KeysPipe, LANGUAGE_SERVICE, LanguageService, LoaderUtils, LocalHttpService, MapPipe, MathUtils, MaxPipe, MinPipe, NgxTemplateOutletDirective, NgxUtilsModule, OPTIONS_TOKEN, ObjectType, ObjectUtils, ObservableUtils, OpenApiService, Oval, PROMISE_SERVICE, PaginationDirective, PaginationItemContext, PaginationItemDirective, PaginationMenuComponent, Point, PopPipe, PromiseService, RESIZE_DELAY, RESIZE_STRATEGY, ROOT_ELEMENT, Rect, ReducePipe, ReflectUtils, RemapPipe, ReplacePipe, ResizeEventPlugin, ResourceIfContext, ResourceIfDirective, ReversePipe, RoundPipe, SCRIPT_PARAMS, STATIC_SCHEMAS, SafeHtmlPipe, ScrollEventPlugin, SetUtils, ShiftPipe, SocketClient, SocketService, SplitPipe, StateService, StaticAuthService, StaticLanguageService, StickyClassDirective, StickyDirective, StorageMode, StorageService, StringUtils, TOASTER_SERVICE, TabsComponent, TabsItemDirective, TabsTemplateDirective, TimerUtils, TranslatePipe, TranslatedUrlSerializer, UniqueUtils, UniversalService, UnorderedListComponent, UnorderedListItemDirective, UnorderedListTemplateDirective, UploadComponent, ValuedPromise, ValuesPipe, cachedFactory, cancelablePromise, checkTransitions, computedPrevious, createTypedProvider, cssStyles, cssVariables, dotProduct, drawOval, drawRect, getComponentDef, getCssVariables, getRoot, hashCode, impatientPromise, isBrowser, isPoint,
|
|
9126
|
+
export { API_SERVICE, APP_BASE_URL, AUTH_SERVICE, AclService, AjaxRequestHandler, ApiService, ArrayUtils, AsyncMethodBase, AsyncMethodDirective, AsyncMethodTargetDirective, AuthGuard, BASE_CONFIG, BUTTON_TYPE, BackgroundDirective, BaseDialogService, BaseHttpClient, BaseHttpService, BaseToasterService, BtnComponent, BtnDefaultComponent, CONFIG_SERVICE, CacheService, CanvasColor, CanvasUtils, ChipsComponent, ChunkPipe, Circle, CloseBtnComponent, ComponentLoaderDirective, ComponentLoaderService, ConfigService, DIALOG_SERVICE, DateUtils, DragDropEventPlugin, DropListComponent, DropdownBoxComponent, DropdownContentDirective, DropdownDirective, DropdownToggleDirective, DynamicTableComponent, DynamicTableTemplateDirective, EPSILON, ERROR_HANDLER, EXPRESS_REQUEST, EntriesPipe, ErrorHandlerService, EventsService, ExtraItemPropertiesPipe, FactoryDependencies, FakeModuleComponent, FileSystemEntry, FileUtils, FilterPipe, FindPipe, ForbiddenZone, FormatNumberPipe, FormatterService, GenericValue, GetOffsetPipe, GetTypePipe, GetValuePipe, GlobalTemplateDirective, GlobalTemplatePipe, GlobalTemplateService, GroupByPipe, ICON_MAP, ICON_SERVICE, ICON_TYPE, IConfiguration, IconComponent, IconDefaultComponent, IconDirective, IconService, IncludesPipe, Initializer, InteractiveCanvasComponent, InteractiveCircleComponent, InteractiveItemComponent, InteractiveRectComponent, IsTypePipe, JSONfn, JoinPipe, KeysPipe, LANGUAGE_SERVICE, LanguageService, LoaderUtils, LocalHttpService, MapPipe, MathUtils, MaxPipe, MinPipe, NgxTemplateOutletDirective, NgxUtilsModule, OPTIONS_TOKEN, ObjectType, ObjectUtils, ObservableUtils, OpenApiService, Oval, PROMISE_SERVICE, PaginationDirective, PaginationItemContext, PaginationItemDirective, PaginationMenuComponent, Point, PopPipe, PromiseService, RESIZE_DELAY, RESIZE_STRATEGY, ROOT_ELEMENT, Rect, ReducePipe, ReflectUtils, RemapPipe, ReplacePipe, ResizeEventPlugin, ResourceIfContext, ResourceIfDirective, ReversePipe, RoundPipe, SCRIPT_PARAMS, STATIC_SCHEMAS, SafeHtmlPipe, ScrollEventPlugin, SetUtils, ShiftPipe, SocketClient, SocketService, SplitPipe, StateService, StaticAuthService, StaticLanguageService, StickyClassDirective, StickyDirective, StorageMode, StorageService, StringUtils, TOASTER_SERVICE, TabsComponent, TabsItemDirective, TabsTemplateDirective, TimerUtils, TranslatePipe, TranslatedUrlSerializer, UniqueUtils, UniversalService, UnorderedListComponent, UnorderedListItemDirective, UnorderedListTemplateDirective, UploadComponent, ValuedPromise, ValuesPipe, addPts, cachedFactory, cancelablePromise, checkTransitions, clamp, computedPrevious, createTypedProvider, cssStyles, cssVariables, distance, distanceSq, dividePts, dotProduct, drawOval, drawRect, ensurePoint, getComponentDef, getCssVariables, getRoot, gjkDistance, gjkIntersection, hashCode, impatientPromise, isBrowser, isPoint, lengthOfPt, lerpPts, multiplyPts, negatePt, normalizePt, normalizeRange, overflow, parseSelector, perpendicular, provideEntryComponents, provideWithOptions, rotateDeg, rotateRad, selectorMatchesList, subPts, switchClass, toDegrees, toRadians, tripleProduct };
|
|
8917
9127
|
//# sourceMappingURL=stemy-ngx-utils.mjs.map
|