novac 2.0.1 → 2.2.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/LICENSE +1 -1
- package/README.md +1574 -597
- package/bin/novac +468 -171
- package/bin/nvc +522 -0
- package/bin/nvml +78 -17
- package/demo.nv +0 -0
- package/demo_builtins.nv +0 -0
- package/demo_http.nv +0 -0
- package/examples/bf.nv +69 -0
- package/examples/math.nv +21 -0
- package/kits/birdAPI/kitdef.js +954 -0
- package/kits/kitRNG/kitdef.js +740 -0
- package/kits/kitSSH/kitdef.js +1272 -0
- package/kits/kitadb/kitdef.js +606 -0
- package/kits/kitai/kitdef.js +2185 -0
- package/kits/kitansi/kitdef.js +1402 -0
- package/kits/kitcanvas/kitdef.js +914 -0
- package/kits/kitclippy/kitdef.js +925 -0
- package/kits/kitformat/kitdef.js +1485 -0
- package/kits/kitgps/kitdef.js +1862 -0
- package/kits/kitlibproc/kitdef.js +3 -2
- package/kits/kitmatrix/ex.js +19 -0
- package/kits/kitmatrix/kitdef.js +960 -0
- package/kits/kitmorse/kitdef.js +229 -0
- package/kits/kitmpatch/kitdef.js +906 -0
- package/kits/kitnet/kitdef.js +1401 -0
- package/kits/kitnovacweb/README.md +1416 -143
- package/kits/kitnovacweb/kitdef.js +92 -2
- package/kits/kitnovacweb/nvml/executor.js +578 -176
- package/kits/kitnovacweb/nvml/index.js +2 -2
- package/kits/kitnovacweb/nvml/lexer.js +72 -69
- package/kits/kitnovacweb/nvml/parser.js +328 -159
- package/kits/kitnovacweb/nvml/renderer.js +770 -270
- package/kits/kitparse/kitdef.js +1688 -0
- package/kits/kitproto/kitdef.js +613 -0
- package/kits/kitqr/kitdef.js +637 -0
- package/kits/kitregex++/kitdef.js +1353 -0
- package/kits/kitrequire/kitdef.js +1599 -0
- package/kits/kitx11/kitdef.js +1 -0
- package/kits/kitx11/kitx11.js +2472 -0
- package/kits/kitx11/kitx11_conn.js +948 -0
- package/kits/kitx11/kitx11_worker.js +121 -0
- package/kits/libtea/kitdef.js +2691 -0
- package/kits/libterm/ex.js +285 -0
- package/kits/libterm/kitdef.js +1927 -0
- package/novac/LICENSE +21 -0
- package/novac/README.md +1823 -0
- package/novac/bin/novac +950 -0
- package/novac/bin/nvc +522 -0
- package/novac/bin/nvml +542 -0
- package/novac/demo.nv +245 -0
- package/novac/demo_builtins.nv +209 -0
- package/novac/demo_http.nv +62 -0
- package/novac/examples/bf.nv +69 -0
- package/novac/examples/math.nv +21 -0
- package/novac/kits/kitai/kitdef.js +2185 -0
- package/novac/kits/kitansi/kitdef.js +1402 -0
- package/novac/kits/kitformat/kitdef.js +1485 -0
- package/novac/kits/kitgps/kitdef.js +1862 -0
- package/novac/kits/kitlibfs/kitdef.js +231 -0
- package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
- package/novac/kits/kitmatrix/ex.js +19 -0
- package/novac/kits/kitmatrix/kitdef.js +960 -0
- package/novac/kits/kitmpatch/kitdef.js +906 -0
- package/novac/kits/kitnovacweb/README.md +1572 -0
- package/novac/kits/kitnovacweb/demo.nv +12 -0
- package/novac/kits/kitnovacweb/demo.nvml +71 -0
- package/novac/kits/kitnovacweb/index.nova +12 -0
- package/novac/kits/kitnovacweb/kitdef.js +692 -0
- package/novac/kits/kitnovacweb/nova.kit.json +8 -0
- package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
- package/novac/kits/kitnovacweb/nvml/index.js +67 -0
- package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
- package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
- package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
- package/novac/kits/kitparse/kitdef.js +1688 -0
- package/novac/kits/kitregex++/kitdef.js +1353 -0
- package/novac/kits/kitrequire/kitdef.js +1599 -0
- package/novac/kits/kitx11/kitdef.js +1 -0
- package/novac/kits/kitx11/kitx11.js +2472 -0
- package/novac/kits/kitx11/kitx11_conn.js +948 -0
- package/novac/kits/kitx11/kitx11_worker.js +121 -0
- package/novac/kits/libtea/tf.js +2691 -0
- package/novac/kits/libterm/ex.js +285 -0
- package/novac/kits/libterm/kitdef.js +1927 -0
- package/novac/node_modules/chalk/license +9 -0
- package/novac/node_modules/chalk/package.json +83 -0
- package/novac/node_modules/chalk/readme.md +297 -0
- package/novac/node_modules/chalk/source/index.d.ts +325 -0
- package/novac/node_modules/chalk/source/index.js +225 -0
- package/novac/node_modules/chalk/source/utilities.js +33 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/novac/node_modules/commander/LICENSE +22 -0
- package/novac/node_modules/commander/Readme.md +1176 -0
- package/novac/node_modules/commander/esm.mjs +16 -0
- package/novac/node_modules/commander/index.js +24 -0
- package/novac/node_modules/commander/lib/argument.js +150 -0
- package/novac/node_modules/commander/lib/command.js +2777 -0
- package/novac/node_modules/commander/lib/error.js +39 -0
- package/novac/node_modules/commander/lib/help.js +747 -0
- package/novac/node_modules/commander/lib/option.js +380 -0
- package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
- package/novac/node_modules/commander/package-support.json +19 -0
- package/novac/node_modules/commander/package.json +82 -0
- package/novac/node_modules/commander/typings/esm.d.mts +3 -0
- package/novac/node_modules/commander/typings/index.d.ts +1113 -0
- package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
- package/novac/node_modules/node-addon-api/README.md +95 -0
- package/novac/node_modules/node-addon-api/common.gypi +21 -0
- package/novac/node_modules/node-addon-api/except.gypi +25 -0
- package/novac/node_modules/node-addon-api/index.js +14 -0
- package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
- package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
- package/novac/node_modules/node-addon-api/napi.h +3364 -0
- package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
- package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
- package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
- package/novac/node_modules/node-addon-api/package-support.json +21 -0
- package/novac/node_modules/node-addon-api/package.json +480 -0
- package/novac/node_modules/node-addon-api/tools/README.md +73 -0
- package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
- package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
- package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
- package/novac/node_modules/serialize-javascript/LICENSE +27 -0
- package/novac/node_modules/serialize-javascript/README.md +149 -0
- package/novac/node_modules/serialize-javascript/index.js +297 -0
- package/novac/node_modules/serialize-javascript/package.json +33 -0
- package/novac/package.json +27 -0
- package/novac/scripts/update-bin.js +24 -0
- package/novac/src/core/bstd.js +1035 -0
- package/novac/src/core/config.js +155 -0
- package/novac/src/core/describe.js +187 -0
- package/novac/src/core/emitter.js +499 -0
- package/novac/src/core/error.js +86 -0
- package/novac/src/core/executor.js +5606 -0
- package/novac/src/core/formatter.js +686 -0
- package/novac/src/core/lexer.js +1026 -0
- package/novac/src/core/nova_builtins.js +717 -0
- package/novac/src/core/nova_thread_worker.js +166 -0
- package/novac/src/core/parser.js +2181 -0
- package/novac/src/core/types.js +112 -0
- package/novac/src/index.js +28 -0
- package/novac/src/runtime/stdlib.js +244 -0
- package/package.json +6 -3
- package/scripts/update-bin.js +0 -0
- package/src/core/bstd.js +838 -362
- package/src/core/executor.js +2578 -170
- package/src/core/lexer.js +502 -54
- package/src/core/nova_builtins.js +21 -3
- package/src/core/parser.js +413 -72
- package/src/core/types.js +30 -2
- package/src/index.js +0 -0
- package/examples/example-project/README.md +0 -3
- package/examples/example-project/src/main.nova +0 -3
- package/src/core/environment.js +0 -0
- /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* kitmatrix — Nova Spatial Computation Kit
|
|
5
|
+
*
|
|
6
|
+
* A coordinate space model for geometry, movement, simulation, and layout.
|
|
7
|
+
* Not a rendering engine by default — a pure spatial model with optional
|
|
8
|
+
* terminal rendering (ANSI) and NVML rendering.
|
|
9
|
+
*
|
|
10
|
+
* Usage in Nova:
|
|
11
|
+
* import "kitmatrix"
|
|
12
|
+
*
|
|
13
|
+
* let m = new Matrix(40, 20)
|
|
14
|
+
* let a = m.point(5, 5)
|
|
15
|
+
* let b = m.point(20, 15)
|
|
16
|
+
* m.debug.drawPoints()
|
|
17
|
+
* m.debug.drawGraph(myGraph)
|
|
18
|
+
* m.render.frame() // full ANSI terminal frame
|
|
19
|
+
* m.nvml.page() // generate NVML page source
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// ── Utilities ────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
function _deg2rad(d) { return d * Math.PI / 180; }
|
|
25
|
+
function _rad2deg(r) { return r * 180 / Math.PI; }
|
|
26
|
+
function _clamp(v, lo, hi) { return Math.max(lo, Math.min(hi, v)); }
|
|
27
|
+
function _approxEq(a, b, eps = 1e-10) { return Math.abs(a - b) < eps; }
|
|
28
|
+
|
|
29
|
+
// ANSI helpers
|
|
30
|
+
const A = {
|
|
31
|
+
reset: '\x1b[0m',
|
|
32
|
+
bold: '\x1b[1m',
|
|
33
|
+
dim: '\x1b[2m',
|
|
34
|
+
fg: (n) => `\x1b[38;5;${n}m`,
|
|
35
|
+
bg: (n) => `\x1b[48;5;${n}m`,
|
|
36
|
+
rgb: (r,g,b) => `\x1b[38;2;${r};${g};${b}m`,
|
|
37
|
+
bgRgb:(r,g,b)=> `\x1b[48;2;${r};${g};${b}m`,
|
|
38
|
+
pos: (r,c) => `\x1b[${r};${c}H`,
|
|
39
|
+
cls: '\x1b[2J',
|
|
40
|
+
home: '\x1b[H',
|
|
41
|
+
hide: '\x1b[?25l',
|
|
42
|
+
show: '\x1b[?25h',
|
|
43
|
+
alt: '\x1b[?1049h',
|
|
44
|
+
main: '\x1b[?1049l',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Box drawing
|
|
48
|
+
const BOX = {
|
|
49
|
+
tl:'┌', tr:'┐', bl:'└', br:'┘', h:'─', v:'│',
|
|
50
|
+
ml:'├', mr:'┤', mt:'┬', mb:'┴', x:'┼',
|
|
51
|
+
dtl:'╔',dtr:'╗',dbl:'╚',dbr:'╝',dh:'═',dv:'║',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// ── MatrixAxis ───────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
class MatrixAxis {
|
|
57
|
+
constructor(angle = 0, margin = 1) { this.angle = angle; this.margin = margin; }
|
|
58
|
+
unitVector() { const r=_deg2rad(this.angle); return {x:Math.cos(r),y:Math.sin(r)}; }
|
|
59
|
+
project(x,y) { const u=this.unitVector(); return (x*u.x+y*u.y)*this.margin; }
|
|
60
|
+
toOffset(s) { const u=this.unitVector(),sc=s*this.margin; return {x:u.x*sc,y:u.y*sc}; }
|
|
61
|
+
clone() { return new MatrixAxis(this.angle,this.margin); }
|
|
62
|
+
toString() { return `MatrixAxis(${this.angle}°, margin=${this.margin})`; }
|
|
63
|
+
static horizontal() { return new MatrixAxis(0,1); }
|
|
64
|
+
static vertical() { return new MatrixAxis(90,1); }
|
|
65
|
+
static diagonal() { return new MatrixAxis(45,1); }
|
|
66
|
+
static isometricX() { return new MatrixAxis(30,1); }
|
|
67
|
+
static isometricY() { return new MatrixAxis(150,1); }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── MatrixVector ─────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
class MatrixVector {
|
|
73
|
+
constructor(dx=0,dy=0) { this.dx=dx; this.dy=dy; }
|
|
74
|
+
magnitude() { return Math.hypot(this.dx,this.dy); }
|
|
75
|
+
magnitudeSq() { return this.dx*this.dx+this.dy*this.dy; }
|
|
76
|
+
normalize() { const m=this.magnitude(); return m===0?new MatrixVector(0,0):new MatrixVector(this.dx/m,this.dy/m); }
|
|
77
|
+
scale(n) { return new MatrixVector(this.dx*n,this.dy*n); }
|
|
78
|
+
dot(v) { return this.dx*v.dx+this.dy*v.dy; }
|
|
79
|
+
cross(v) { return this.dx*v.dy-this.dy*v.dx; }
|
|
80
|
+
angle() { return _rad2deg(Math.atan2(this.dy,this.dx)); }
|
|
81
|
+
add(v) { return new MatrixVector(this.dx+v.dx,this.dy+v.dy); }
|
|
82
|
+
sub(v) { return new MatrixVector(this.dx-v.dx,this.dy-v.dy); }
|
|
83
|
+
negate() { return new MatrixVector(-this.dx,-this.dy); }
|
|
84
|
+
perpendicular(){ return new MatrixVector(-this.dy,this.dx); }
|
|
85
|
+
rotate(deg) { const r=_deg2rad(deg),c=Math.cos(r),s=Math.sin(r); return new MatrixVector(this.dx*c-this.dy*s,this.dx*s+this.dy*c); }
|
|
86
|
+
reflect(n) { const u=n.normalize(),d=this.dot(u)*2; return new MatrixVector(this.dx-u.dx*d,this.dy-u.dy*d); }
|
|
87
|
+
lerp(v,t) { return new MatrixVector(this.dx+(v.dx-this.dx)*t,this.dy+(v.dy-this.dy)*t); }
|
|
88
|
+
projectOnto(v){ const d=v.dot(v); return d===0?MatrixVector.zero():v.scale(this.dot(v)/d); }
|
|
89
|
+
rejectFrom(v) { return this.sub(this.projectOnto(v)); }
|
|
90
|
+
isZero(eps=1e-10) { return this.magnitude()<eps; }
|
|
91
|
+
equals(v,eps=0){ return eps===0?this.dx===v.dx&&this.dy===v.dy:_approxEq(this.dx,v.dx,eps)&&_approxEq(this.dy,v.dy,eps); }
|
|
92
|
+
toArray() { return [this.dx,this.dy]; }
|
|
93
|
+
toObject() { return {dx:this.dx,dy:this.dy}; }
|
|
94
|
+
clone() { return new MatrixVector(this.dx,this.dy); }
|
|
95
|
+
toString() { return `MatrixVector(${this.dx},${this.dy} |${this.magnitude().toFixed(3)}|)`; }
|
|
96
|
+
static fromPoints(a,b){ return new MatrixVector(b.x-a.x,b.y-a.y); }
|
|
97
|
+
static fromAngle(deg,mag=1){ const r=_deg2rad(deg); return new MatrixVector(Math.cos(r)*mag,Math.sin(r)*mag); }
|
|
98
|
+
static zero() { return new MatrixVector(0,0); }
|
|
99
|
+
static unitX() { return new MatrixVector(1,0); }
|
|
100
|
+
static unitY() { return new MatrixVector(0,1); }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── MatrixPoint ──────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
class MatrixPoint {
|
|
106
|
+
constructor(matrix,x=0,y=0) { this.matrix=matrix; this.x=x; this.y=y; this.label=null; this.color=null; }
|
|
107
|
+
oppositingPoint(cx=0,cy=0) { return new MatrixPoint(this.matrix,2*cx-this.x,2*cy-this.y); }
|
|
108
|
+
distanceTo(o) { return Math.hypot(o.x-this.x,o.y-this.y); }
|
|
109
|
+
distanceSqTo(o) { return (o.x-this.x)**2+(o.y-this.y)**2; }
|
|
110
|
+
manhattanTo(o) { return Math.abs(o.x-this.x)+Math.abs(o.y-this.y); }
|
|
111
|
+
chebyshevTo(o) { return Math.max(Math.abs(o.x-this.x),Math.abs(o.y-this.y)); }
|
|
112
|
+
vectorTo(o) { return new MatrixVector(o.x-this.x,o.y-this.y); }
|
|
113
|
+
midpointTo(o) { return new MatrixPoint(this.matrix,(this.x+o.x)/2,(this.y+o.y)/2); }
|
|
114
|
+
angleTo(o) { return _rad2deg(Math.atan2(o.y-this.y,o.x-this.x)); }
|
|
115
|
+
projectOnAxis(ax){ return ax.project(this.x,this.y); }
|
|
116
|
+
isInBounds(b) { return b.contains(this); }
|
|
117
|
+
translate(dxOrV,dy=0) {
|
|
118
|
+
if (dxOrV instanceof MatrixVector) return new MatrixPoint(this.matrix,this.x+dxOrV.dx,this.y+dxOrV.dy);
|
|
119
|
+
return new MatrixPoint(this.matrix,this.x+dxOrV,this.y+dy);
|
|
120
|
+
}
|
|
121
|
+
rotateAround(ox,oy,deg) {
|
|
122
|
+
const r=_deg2rad(deg),c=Math.cos(r),s=Math.sin(r),dx=this.x-ox,dy=this.y-oy;
|
|
123
|
+
return new MatrixPoint(this.matrix,ox+dx*c-dy*s,oy+dx*s+dy*c);
|
|
124
|
+
}
|
|
125
|
+
scaleFrom(ox,oy,sx,sy=sx) { return new MatrixPoint(this.matrix,ox+(this.x-ox)*sx,oy+(this.y-oy)*sy); }
|
|
126
|
+
lerp(o,t) { return new MatrixPoint(this.matrix,this.x+(o.x-this.x)*t,this.y+(o.y-this.y)*t); }
|
|
127
|
+
snap(sx,sy=sx) { return new MatrixPoint(this.matrix,Math.round(this.x/sx)*sx,Math.round(this.y/sy)*sy); }
|
|
128
|
+
equals(o,eps=0) { return eps===0?this.x===o.x&&this.y===o.y:_approxEq(this.x,o.x,eps)&&_approxEq(this.y,o.y,eps); }
|
|
129
|
+
withLabel(l) { this.label=l; return this; }
|
|
130
|
+
withColor(c) { this.color=c; return this; }
|
|
131
|
+
toArray() { return [this.x,this.y]; }
|
|
132
|
+
toObject() { return {x:this.x,y:this.y}; }
|
|
133
|
+
clone() { const p=new MatrixPoint(this.matrix,this.x,this.y); p.label=this.label; p.color=this.color; return p; }
|
|
134
|
+
toString() { return `MatrixPoint(${this.x},${this.y}${this.label?` "${this.label}"`:''})`; }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── MatrixBounds ─────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
class MatrixBounds {
|
|
140
|
+
constructor(matrix,minX,minY,maxX,maxY) {
|
|
141
|
+
this.matrix=matrix;
|
|
142
|
+
this.minX=Math.min(minX,maxX); this.minY=Math.min(minY,maxY);
|
|
143
|
+
this.maxX=Math.max(minX,maxX); this.maxY=Math.max(minY,maxY);
|
|
144
|
+
}
|
|
145
|
+
get width() { return this.maxX-this.minX; }
|
|
146
|
+
get height() { return this.maxY-this.minY; }
|
|
147
|
+
get area() { return this.width*this.height; }
|
|
148
|
+
get perimeter(){ return 2*(this.width+this.height); }
|
|
149
|
+
get centerX() { return (this.minX+this.maxX)/2; }
|
|
150
|
+
get centerY() { return (this.minY+this.maxY)/2; }
|
|
151
|
+
get center() { return new MatrixPoint(this.matrix,this.centerX,this.centerY); }
|
|
152
|
+
contains(p) { return p.x>=this.minX&&p.x<=this.maxX&&p.y>=this.minY&&p.y<=this.maxY; }
|
|
153
|
+
clamp(p) { return new MatrixPoint(this.matrix,_clamp(p.x,this.minX,this.maxX),_clamp(p.y,this.minY,this.maxY)); }
|
|
154
|
+
overlaps(o) { return this.minX<=o.maxX&&this.maxX>=o.minX&&this.minY<=o.maxY&&this.maxY>=o.minY; }
|
|
155
|
+
intersection(o){
|
|
156
|
+
const mnX=Math.max(this.minX,o.minX),mnY=Math.max(this.minY,o.minY);
|
|
157
|
+
const mxX=Math.min(this.maxX,o.maxX),mxY=Math.min(this.maxY,o.maxY);
|
|
158
|
+
if (mnX>mxX||mnY>mxY) return null;
|
|
159
|
+
return new MatrixBounds(this.matrix,mnX,mnY,mxX,mxY);
|
|
160
|
+
}
|
|
161
|
+
union(o) { return new MatrixBounds(this.matrix,Math.min(this.minX,o.minX),Math.min(this.minY,o.minY),Math.max(this.maxX,o.maxX),Math.max(this.maxY,o.maxY)); }
|
|
162
|
+
expand(mx,my=mx){ return new MatrixBounds(this.matrix,this.minX-mx,this.minY-my,this.maxX+mx,this.maxY+my); }
|
|
163
|
+
corners() { return [new MatrixPoint(this.matrix,this.minX,this.minY),new MatrixPoint(this.matrix,this.maxX,this.minY),new MatrixPoint(this.matrix,this.maxX,this.maxY),new MatrixPoint(this.matrix,this.minX,this.maxY)]; }
|
|
164
|
+
randomPoint() { return new MatrixPoint(this.matrix,this.minX+Math.random()*this.width,this.minY+Math.random()*this.height); }
|
|
165
|
+
toObject() { return {minX:this.minX,minY:this.minY,maxX:this.maxX,maxY:this.maxY,width:this.width,height:this.height}; }
|
|
166
|
+
toString() { return `MatrixBounds([${this.minX},${this.minY}]→[${this.maxX},${this.maxY}])`; }
|
|
167
|
+
static fromPoints(matrix,pts) { const xs=pts.map(p=>p.x),ys=pts.map(p=>p.y); return new MatrixBounds(matrix,Math.min(...xs),Math.min(...ys),Math.max(...xs),Math.max(...ys)); }
|
|
168
|
+
static fromCenter(matrix,cx,cy,hw,hh=hw){ return new MatrixBounds(matrix,cx-hw,cy-hh,cx+hw,cy+hh); }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── MatrixTranslation ─────────────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
class MatrixTranslation {
|
|
174
|
+
constructor(matrix,a,b) { this.matrix=matrix; this.a=a; this.b=b; this.dx=b.x-a.x; this.dy=b.y-a.y; }
|
|
175
|
+
for(p) { return new MatrixPoint(this.matrix,p.x+this.dx,p.y+this.dy); }
|
|
176
|
+
inverse() { return new MatrixTranslation(this.matrix,this.b,this.a); }
|
|
177
|
+
toVector() { return new MatrixVector(this.dx,this.dy); }
|
|
178
|
+
magnitude() { return Math.hypot(this.dx,this.dy); }
|
|
179
|
+
toString() { return `MatrixTranslation(dx=${this.dx},dy=${this.dy})`; }
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── MatrixTransform ───────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
class MatrixTransform {
|
|
185
|
+
constructor(matrix,{a=1,b=0,c=0,d=1,tx=0,ty=0}={}) { this.matrix=matrix; this.a=a;this.b=b;this.c=c;this.d=d;this.tx=tx;this.ty=ty; }
|
|
186
|
+
apply(p) { return new MatrixPoint(this.matrix,this.a*p.x+this.c*p.y+this.tx,this.b*p.x+this.d*p.y+this.ty); }
|
|
187
|
+
applyVector(v){ return new MatrixVector(this.a*v.dx+this.c*v.dy,this.b*v.dx+this.d*v.dy); }
|
|
188
|
+
combine(o) {
|
|
189
|
+
return new MatrixTransform(this.matrix,{
|
|
190
|
+
a:this.a*o.a+this.c*o.b, b:this.b*o.a+this.d*o.b, c:this.a*o.c+this.c*o.d, d:this.b*o.c+this.d*o.d,
|
|
191
|
+
tx:this.a*o.tx+this.c*o.ty+this.tx, ty:this.b*o.tx+this.d*o.ty+this.ty
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
inverse() {
|
|
195
|
+
const det=this.a*this.d-this.b*this.c;
|
|
196
|
+
if (_approxEq(det,0)) throw new Error('MatrixTransform: singular');
|
|
197
|
+
const inv=1/det;
|
|
198
|
+
return new MatrixTransform(this.matrix,{a:this.d*inv,b:-this.b*inv,c:-this.c*inv,d:this.a*inv,tx:(this.c*this.ty-this.d*this.tx)*inv,ty:(this.b*this.tx-this.a*this.ty)*inv});
|
|
199
|
+
}
|
|
200
|
+
determinant(){ return this.a*this.d-this.b*this.c; }
|
|
201
|
+
isIdentity() { return _approxEq(this.a,1)&&_approxEq(this.b,0)&&_approxEq(this.c,0)&&_approxEq(this.d,1)&&_approxEq(this.tx,0)&&_approxEq(this.ty,0); }
|
|
202
|
+
decompose() {
|
|
203
|
+
const sx=Math.hypot(this.a,this.b),shear=(this.a*this.c+this.b*this.d)/(sx*sx);
|
|
204
|
+
const sy=Math.hypot(this.c-shear*this.a,this.d-shear*this.b);
|
|
205
|
+
return {translation:{x:this.tx,y:this.ty},rotation:_rad2deg(Math.atan2(this.b,this.a)),scale:{x:sx,y:sy},shear};
|
|
206
|
+
}
|
|
207
|
+
toArray() { return [this.a,this.b,this.c,this.d,this.tx,this.ty]; }
|
|
208
|
+
toString() { return `MatrixTransform([${this.a},${this.b},${this.c},${this.d}] t=[${this.tx},${this.ty}])`; }
|
|
209
|
+
static identity(m) { return new MatrixTransform(m); }
|
|
210
|
+
static translate(m,dx,dy) { return new MatrixTransform(m,{tx:dx,ty:dy}); }
|
|
211
|
+
static scale(m,sx,sy=sx,cx=0,cy=0){ return new MatrixTransform(m,{a:sx,d:sy,tx:cx-sx*cx,ty:cy-sy*cy}); }
|
|
212
|
+
static rotate(m,deg,cx=0,cy=0){
|
|
213
|
+
const r=_deg2rad(deg),c=Math.cos(r),s=Math.sin(r);
|
|
214
|
+
return new MatrixTransform(m,{a:c,b:s,c:-s,d:c,tx:cx-c*cx+s*cy,ty:cy-s*cx-c*cy});
|
|
215
|
+
}
|
|
216
|
+
static shear(m,shx,shy=0) { return new MatrixTransform(m,{c:shx,b:shy}); }
|
|
217
|
+
static reflectX(m) { return new MatrixTransform(m,{d:-1}); }
|
|
218
|
+
static reflectY(m) { return new MatrixTransform(m,{a:-1}); }
|
|
219
|
+
static reflectOrigin(m) { return new MatrixTransform(m,{a:-1,d:-1}); }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ── MatrixTransformChain ──────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
class MatrixTransformChain {
|
|
225
|
+
constructor(matrix,transforms=[]) { this.matrix=matrix; this.transforms=[...transforms]; }
|
|
226
|
+
add(t) { this.transforms.push(t); return this; }
|
|
227
|
+
clear() { this.transforms=[]; return this; }
|
|
228
|
+
resolve() { return this.transforms.reduce((acc,t)=>acc.combine(t),MatrixTransform.identity(this.matrix)); }
|
|
229
|
+
apply(p) { return this.resolve().apply(p); }
|
|
230
|
+
applyInverse(p) { return this.resolve().inverse().apply(p); }
|
|
231
|
+
toString() { return `MatrixTransformChain(${this.transforms.length})`; }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── MatrixConnector ───────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
class MatrixConnector {
|
|
237
|
+
constructor(matrix,points=[]) { this.matrix=matrix; this.points=[...points]; }
|
|
238
|
+
length() { let t=0; for(let i=1;i<this.points.length;i++) t+=this.points[i-1].distanceTo(this.points[i]); return t; }
|
|
239
|
+
reverse() { return new MatrixConnector(this.matrix,[...this.points].reverse()); }
|
|
240
|
+
extend(p) { return new MatrixConnector(this.matrix,[...this.points,p]); }
|
|
241
|
+
bounds() { return MatrixBounds.fromPoints(this.matrix,this.points); }
|
|
242
|
+
sample(t) {
|
|
243
|
+
if (!this.points.length) return null;
|
|
244
|
+
if (this.points.length===1) return this.points[0].clone();
|
|
245
|
+
t=_clamp(t,0,1);
|
|
246
|
+
const total=this.length(); if (total===0) return this.points[0].clone();
|
|
247
|
+
let target=total*t,walked=0;
|
|
248
|
+
for (let i=1;i<this.points.length;i++) {
|
|
249
|
+
const seg=this.points[i-1].distanceTo(this.points[i]);
|
|
250
|
+
if (walked+seg>=target) return this.points[i-1].lerp(this.points[i],(target-walked)/seg);
|
|
251
|
+
walked+=seg;
|
|
252
|
+
}
|
|
253
|
+
return this.points[this.points.length-1].clone();
|
|
254
|
+
}
|
|
255
|
+
toString() { return `MatrixConnector(${this.points.length}pts, len=${this.length().toFixed(3)})`; }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── MatrixPath ────────────────────────────────────────────────────────────────
|
|
259
|
+
|
|
260
|
+
class MatrixPath {
|
|
261
|
+
constructor(matrix,points=[]) { this.matrix=matrix; this.points=[...points]; }
|
|
262
|
+
length() { let t=0; for(let i=1;i<this.points.length;i++) t+=this.points[i-1].distanceTo(this.points[i]); return t; }
|
|
263
|
+
sample(t) { return new MatrixConnector(this.matrix,this.points).sample(t); }
|
|
264
|
+
reverse() { return new MatrixPath(this.matrix,[...this.points].reverse()); }
|
|
265
|
+
append(o) { return new MatrixPath(this.matrix,[...this.points,...o.points]); }
|
|
266
|
+
bounds() { return MatrixBounds.fromPoints(this.matrix,this.points); }
|
|
267
|
+
subdivide(n=2) {
|
|
268
|
+
if (this.points.length<2) return this.clone();
|
|
269
|
+
const pts=[];
|
|
270
|
+
for(let i=0;i<this.points.length-1;i++) for(let j=0;j<n;j++) pts.push(this.points[i].lerp(this.points[i+1],j/n));
|
|
271
|
+
pts.push(this.points[this.points.length-1].clone());
|
|
272
|
+
return new MatrixPath(this.matrix,pts);
|
|
273
|
+
}
|
|
274
|
+
transform(t){ return new MatrixPath(this.matrix,this.points.map(p=>t.apply(p))); }
|
|
275
|
+
clone() { return new MatrixPath(this.matrix,this.points.map(p=>p.clone())); }
|
|
276
|
+
toString() { return `MatrixPath(${this.points.length}pts, len=${this.length().toFixed(3)})`; }
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ── MatrixGrid ────────────────────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
class MatrixGrid {
|
|
282
|
+
constructor(matrix,rows,cols,ox=0,oy=0,cw=1,ch=1) {
|
|
283
|
+
this.matrix=matrix; this.rows=rows; this.cols=cols;
|
|
284
|
+
this.originX=ox; this.originY=oy; this.cellW=cw; this.cellH=ch; this._pts=null;
|
|
285
|
+
}
|
|
286
|
+
_build() {
|
|
287
|
+
if (this._pts) return;
|
|
288
|
+
this._pts=[];
|
|
289
|
+
for(let r=0;r<this.rows;r++){
|
|
290
|
+
const row=[];
|
|
291
|
+
for(let c=0;c<this.cols;c++) row.push(new MatrixPoint(this.matrix,this.originX+c*this.cellW,this.originY+r*this.cellH));
|
|
292
|
+
this._pts.push(row);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
at(r,c) { this._build(); return this._pts[r]?.[c]??null; }
|
|
296
|
+
flat() { this._build(); return this._pts.flat(); }
|
|
297
|
+
all() { this._build(); return this._pts.map(r=>[...r]); }
|
|
298
|
+
forEach(fn){ this._build(); this._pts.forEach((row,r)=>row.forEach((p,c)=>fn(p,r,c))); }
|
|
299
|
+
map(fn) { this._build(); return this._pts.map((row,r)=>row.map((p,c)=>fn(p,r,c))); }
|
|
300
|
+
filter(fn) { this._build(); return this._pts.flat().filter((p,i)=>fn(p,Math.floor(i/this.cols),i%this.cols)); }
|
|
301
|
+
neighbors(r,c,diag=false) {
|
|
302
|
+
this._build();
|
|
303
|
+
const dirs=[[0,1],[0,-1],[1,0],[-1,0]];
|
|
304
|
+
if(diag) dirs.push([1,1],[1,-1],[-1,1],[-1,-1]);
|
|
305
|
+
return dirs.map(([dr,dc])=>this.at(r+dr,c+dc)).filter(Boolean);
|
|
306
|
+
}
|
|
307
|
+
snap(p) {
|
|
308
|
+
this._build();
|
|
309
|
+
const c=_clamp(Math.round((p.x-this.originX)/this.cellW),0,this.cols-1);
|
|
310
|
+
const r=_clamp(Math.round((p.y-this.originY)/this.cellH),0,this.rows-1);
|
|
311
|
+
return this.at(r,c);
|
|
312
|
+
}
|
|
313
|
+
indexOf(p) {
|
|
314
|
+
const c=Math.round((p.x-this.originX)/this.cellW),r=Math.round((p.y-this.originY)/this.cellH);
|
|
315
|
+
if(c<0||c>=this.cols||r<0||r>=this.rows) return null;
|
|
316
|
+
return {row:r,col:c};
|
|
317
|
+
}
|
|
318
|
+
cellBounds(r,c) { const x=this.originX+c*this.cellW,y=this.originY+r*this.cellH; return new MatrixBounds(this.matrix,x,y,x+this.cellW,y+this.cellH); }
|
|
319
|
+
toString() { return `MatrixGrid(${this.rows}×${this.cols} cell=${this.cellW}×${this.cellH})`; }
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ── MatrixGraph ───────────────────────────────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
class MatrixGraph {
|
|
325
|
+
constructor(matrix) { this.matrix=matrix; this._nodes=new Map(); this._edges=new Map(); this._ids=new WeakMap(); this._nextId=0; }
|
|
326
|
+
_id(p) {
|
|
327
|
+
if(!this._ids.has(p)){const id=this._nextId++;this._ids.set(p,id);this._nodes.set(id,p);this._edges.set(id,new Map());}
|
|
328
|
+
return this._ids.get(p);
|
|
329
|
+
}
|
|
330
|
+
connect(a,b,w=null,dir=false) {
|
|
331
|
+
const ia=this._id(a),ib=this._id(b),wt=w??a.distanceTo(b);
|
|
332
|
+
this._edges.get(ia).set(ib,wt); if(!dir) this._edges.get(ib).set(ia,wt); return this;
|
|
333
|
+
}
|
|
334
|
+
disconnect(a,b,dir=false) {
|
|
335
|
+
const ia=this._id(a),ib=this._id(b);
|
|
336
|
+
this._edges.get(ia)?.delete(ib); if(!dir) this._edges.get(ib)?.delete(ia); return this;
|
|
337
|
+
}
|
|
338
|
+
neighbors(p) { const id=this._id(p); return [...(this._edges.get(id)||new Map()).keys()].map(nid=>this._nodes.get(nid)).filter(Boolean); }
|
|
339
|
+
weight(a,b) { return this._edges.get(this._id(a))?.get(this._id(b))??Infinity; }
|
|
340
|
+
hasEdge(a,b) { return (this._edges.get(this._id(a))||new Map()).has(this._id(b)); }
|
|
341
|
+
nodes() { return [...this._nodes.values()]; }
|
|
342
|
+
get nodeCount(){ return this._nodes.size; }
|
|
343
|
+
get edgeCount(){ let t=0; for(const e of this._edges.values()) t+=e.size; return t/2; }
|
|
344
|
+
|
|
345
|
+
shortestPath(start,goal) {
|
|
346
|
+
const sid=this._id(start),gid=this._id(goal),dist=new Map(),prev=new Map(),q=new Set();
|
|
347
|
+
for(const id of this._nodes.keys()){dist.set(id,Infinity);q.add(id);}
|
|
348
|
+
dist.set(sid,0);
|
|
349
|
+
while(q.size>0){
|
|
350
|
+
let u=null; for(const id of q) if(u===null||dist.get(id)<dist.get(u)) u=id;
|
|
351
|
+
if(u===null||u===gid) break; q.delete(u);
|
|
352
|
+
for(const [v,w] of (this._edges.get(u)||new Map())){ if(!q.has(v)) continue; const alt=dist.get(u)+w; if(alt<dist.get(v)){dist.set(v,alt);prev.set(v,u);} }
|
|
353
|
+
}
|
|
354
|
+
if(dist.get(gid)===Infinity) return {path:[],cost:Infinity};
|
|
355
|
+
const path=[];let cur=gid; while(cur!==undefined){path.unshift(this._nodes.get(cur));cur=prev.get(cur);}
|
|
356
|
+
return {path,cost:dist.get(gid)};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
astar(start,goal) {
|
|
360
|
+
const sid=this._id(start),gid=this._id(goal),h=(id)=>{const p=this._nodes.get(id);return p?p.distanceTo(goal):0;};
|
|
361
|
+
const gS=new Map(),fS=new Map(),prev=new Map(),open=new Set([sid]);
|
|
362
|
+
for(const id of this._nodes.keys()){gS.set(id,Infinity);fS.set(id,Infinity);}
|
|
363
|
+
gS.set(sid,0);fS.set(sid,h(sid));
|
|
364
|
+
while(open.size>0){
|
|
365
|
+
let u=null; for(const id of open) if(u===null||fS.get(id)<fS.get(u)) u=id;
|
|
366
|
+
if(u===gid) break; open.delete(u);
|
|
367
|
+
for(const [v,w] of (this._edges.get(u)||new Map())){
|
|
368
|
+
const g=gS.get(u)+w; if(g<gS.get(v)){prev.set(v,u);gS.set(v,g);fS.set(v,g+h(v));open.add(v);}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if(gS.get(gid)===Infinity) return {path:[],cost:Infinity};
|
|
372
|
+
const path=[];let cur=gid; while(cur!==undefined){path.unshift(this._nodes.get(cur));cur=prev.get(cur);}
|
|
373
|
+
return {path,cost:gS.get(gid)};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
reachable(start) {
|
|
377
|
+
const sid=this._id(start),visited=new Set([sid]),q=[sid];
|
|
378
|
+
while(q.length>0){const u=q.shift();for(const v of (this._edges.get(u)||new Map()).keys()) if(!visited.has(v)){visited.add(v);q.push(v);}}
|
|
379
|
+
return [...visited].map(id=>this._nodes.get(id)).filter(Boolean);
|
|
380
|
+
}
|
|
381
|
+
toString() { return `MatrixGraph(${this.nodeCount}nodes, ${this.edgeCount}edges)`; }
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// ── MatrixProjection ──────────────────────────────────────────────────────────
|
|
385
|
+
|
|
386
|
+
class MatrixProjection {
|
|
387
|
+
constructor(matrix){ this.matrix=matrix; }
|
|
388
|
+
ontoAxis(p,ax){ const s=ax.project(p.x,p.y),off=ax.toOffset(s/ax.margin); return {scalar:s,projected:new MatrixPoint(this.matrix,off.x,off.y)}; }
|
|
389
|
+
isometric(x,y,z=0,tw=1,th=0.5){ return new MatrixPoint(this.matrix,(x-y)*tw/2,(x+y)*th/2-z*th); }
|
|
390
|
+
oblique(x,y,z=0,za=45,zs=0.5){ const r=_deg2rad(za); return new MatrixPoint(this.matrix,x+z*zs*Math.cos(r),y+z*zs*Math.sin(r)); }
|
|
391
|
+
perspective(x,y,z,f=1,cx=0,cy=0){ if(z===0) return new MatrixPoint(this.matrix,cx,cy); return new MatrixPoint(this.matrix,cx+(x-cx)*f/z,cy+(y-cy)*f/z); }
|
|
392
|
+
remap(p,src,dst){ const tx=(p.x-src.minX)/(src.width||1),ty=(p.y-src.minY)/(src.height||1); return new MatrixPoint(this.matrix,dst.minX+tx*dst.width,dst.minY+ty*dst.height); }
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ── MatrixInterpolation ───────────────────────────────────────────────────────
|
|
396
|
+
|
|
397
|
+
class MatrixInterpolation {
|
|
398
|
+
constructor(matrix){ this.matrix=matrix; }
|
|
399
|
+
lerp(a,b,t) { return a.lerp(b,_clamp(t,0,1)); }
|
|
400
|
+
cosine(a,b,t) { return a.lerp(b,(1-Math.cos(t*Math.PI))/2); }
|
|
401
|
+
smoothstep(a,b,t) { const s=_clamp(t,0,1); return a.lerp(b,s*s*(3-2*s)); }
|
|
402
|
+
smootherstep(a,b,t) { const s=_clamp(t,0,1); return a.lerp(b,s*s*s*(s*(6*s-15)+10)); }
|
|
403
|
+
catmullRom(p0,p1,p2,p3,t){
|
|
404
|
+
const t2=t*t,t3=t2*t;
|
|
405
|
+
return new MatrixPoint(this.matrix,
|
|
406
|
+
0.5*((2*p1.x)+(-p0.x+p2.x)*t+(2*p0.x-5*p1.x+4*p2.x-p3.x)*t2+(-p0.x+3*p1.x-3*p2.x+p3.x)*t3),
|
|
407
|
+
0.5*((2*p1.y)+(-p0.y+p2.y)*t+(2*p0.y-5*p1.y+4*p2.y-p3.y)*t2+(-p0.y+3*p1.y-3*p2.y+p3.y)*t3)
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
bezierQuadratic(p0,cp,p2,t){ const mt=1-t; return new MatrixPoint(this.matrix,mt*mt*p0.x+2*mt*t*cp.x+t*t*p2.x,mt*mt*p0.y+2*mt*t*cp.y+t*t*p2.y); }
|
|
411
|
+
bezierCubic(p0,c1,c2,p3,t){ const mt=1-t,m2=mt*mt,t2=t*t; return new MatrixPoint(this.matrix,m2*mt*p0.x+3*m2*t*c1.x+3*mt*t2*c2.x+t2*t*p3.x,m2*mt*p0.y+3*m2*t*c1.y+3*mt*t2*c2.y+t2*t*p3.y); }
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ── MatrixCanvas ──────────────────────────────────────────────────────────────
|
|
415
|
+
// Internal braille/block pixel canvas for terminal rendering.
|
|
416
|
+
// Uses 2×4 braille dots per character cell → 2× horizontal, 4× vertical resolution.
|
|
417
|
+
|
|
418
|
+
class MatrixCanvas {
|
|
419
|
+
constructor(charW, charH) {
|
|
420
|
+
this.charW = charW;
|
|
421
|
+
this.charH = charH;
|
|
422
|
+
this.pixelW = charW * 2; // braille: 2 dots wide
|
|
423
|
+
this.pixelH = charH * 4; // braille: 4 dots tall
|
|
424
|
+
// braille cell dot pattern index: [col0row0, col0row1, col0row2, col0row3, col1row0, col1row1, col1row2, col1row3]
|
|
425
|
+
// Unicode braille: U+2800 + bitmask
|
|
426
|
+
// Bit layout: dot1=0x01 dot2=0x02 dot3=0x04 dot4=0x40 (left col, top→bottom)
|
|
427
|
+
// dot5=0x08 dot6=0x10 dot7=0x20 dot8=0x80 (right col, top→bottom)
|
|
428
|
+
this._dots = new Uint8Array(charW * charH); // bitmask per cell
|
|
429
|
+
this._color = new Array(charW * charH).fill(''); // ANSI color prefix per cell
|
|
430
|
+
this._bg = new Array(charW * charH).fill(''); // ANSI bg per cell
|
|
431
|
+
// Braille bit index: [left-col bits top→bottom, right-col bits top→bottom]
|
|
432
|
+
this._brailleBits = [
|
|
433
|
+
[0x01,0x02,0x04,0x40], // left column, rows 0..3
|
|
434
|
+
[0x08,0x10,0x20,0x80], // right column, rows 0..3
|
|
435
|
+
];
|
|
436
|
+
this._charBuf = new Array(charW * charH).fill(' '); // fallback char buf
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
_cell(cx, cy) { return cy * this.charW + cx; }
|
|
440
|
+
|
|
441
|
+
// Set a pixel (px,py in pixel space)
|
|
442
|
+
setPixel(px, py, colorAnsi = '') {
|
|
443
|
+
px = Math.round(px); py = Math.round(py);
|
|
444
|
+
if (px < 0 || px >= this.pixelW || py < 0 || py >= this.pixelH) return;
|
|
445
|
+
const cx = Math.floor(px / 2), cy = Math.floor(py / 4);
|
|
446
|
+
const col = px % 2, row = py % 4;
|
|
447
|
+
const idx = this._cell(cx, cy);
|
|
448
|
+
this._dots[idx] |= this._brailleBits[col][row];
|
|
449
|
+
if (colorAnsi) this._color[idx] = colorAnsi;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Clear all pixels
|
|
453
|
+
clear() {
|
|
454
|
+
this._dots.fill(0);
|
|
455
|
+
this._color.fill('');
|
|
456
|
+
this._bg.fill('');
|
|
457
|
+
this._charBuf.fill(' ');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Place a text character at a char-space position
|
|
461
|
+
setChar(cx, cy, ch, colorAnsi = '') {
|
|
462
|
+
if (cx < 0 || cx >= this.charW || cy < 0 || cy >= this.charH) return;
|
|
463
|
+
const idx = this._cell(cx, cy);
|
|
464
|
+
this._charBuf[idx] = ch;
|
|
465
|
+
if (colorAnsi) this._color[idx] = colorAnsi;
|
|
466
|
+
this._dots[idx] = 0; // char overrides braille
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Render to a string (no cursor positioning — just rows)
|
|
470
|
+
render(options = {}) {
|
|
471
|
+
const { border = true, title = '' } = options;
|
|
472
|
+
const rows = [];
|
|
473
|
+
if (border) {
|
|
474
|
+
const titleStr = title ? ` ${title} ` : '';
|
|
475
|
+
const totalW = this.charW;
|
|
476
|
+
const pad = Math.max(0, totalW - titleStr.length - 2);
|
|
477
|
+
const lpad = Math.floor(pad / 2), rpad = pad - lpad;
|
|
478
|
+
rows.push(BOX.dtl + BOX.dh.repeat(lpad) + (titleStr ? A.bold + titleStr + A.reset : '') + BOX.dh.repeat(rpad) + BOX.dtr);
|
|
479
|
+
}
|
|
480
|
+
for (let cy = 0; cy < this.charH; cy++) {
|
|
481
|
+
let row = border ? BOX.dv : '';
|
|
482
|
+
for (let cx = 0; cx < this.charW; cx++) {
|
|
483
|
+
const idx = this._cell(cx, cy);
|
|
484
|
+
const bits = this._dots[idx];
|
|
485
|
+
const col = this._color[idx];
|
|
486
|
+
const bg = this._bg[idx];
|
|
487
|
+
const ch = this._charBuf[idx];
|
|
488
|
+
let glyph;
|
|
489
|
+
if (ch !== ' ' && bits === 0) {
|
|
490
|
+
// explicit char placed
|
|
491
|
+
glyph = ch;
|
|
492
|
+
} else if (bits === 0) {
|
|
493
|
+
glyph = ' ';
|
|
494
|
+
} else {
|
|
495
|
+
glyph = String.fromCodePoint(0x2800 + bits);
|
|
496
|
+
}
|
|
497
|
+
row += (col || bg) ? (bg + col + glyph + A.reset) : glyph;
|
|
498
|
+
}
|
|
499
|
+
row += border ? BOX.dv : '';
|
|
500
|
+
rows.push(row);
|
|
501
|
+
}
|
|
502
|
+
if (border) rows.push(BOX.dbl + BOX.dh.repeat(this.charW) + BOX.dbr);
|
|
503
|
+
return rows.join('\n');
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// ── MatrixRenderer ────────────────────────────────────────────────────────────
|
|
508
|
+
// Terminal + NVML renderer attached to a Matrix.
|
|
509
|
+
// m.render.frame(options) — print a full ANSI frame to stdout
|
|
510
|
+
// m.render.toNVML(options) — return an NVML page source string
|
|
511
|
+
// m.render.toHTML(options) — return an HTML <pre> block string
|
|
512
|
+
|
|
513
|
+
class MatrixRenderer {
|
|
514
|
+
constructor(matrix) {
|
|
515
|
+
this.matrix = matrix;
|
|
516
|
+
this._canvas = null;
|
|
517
|
+
this.options = {
|
|
518
|
+
charW: 80,
|
|
519
|
+
charH: 24,
|
|
520
|
+
padding: 1,
|
|
521
|
+
bg: '',
|
|
522
|
+
pointColor: A.rgb(100,200,255),
|
|
523
|
+
edgeColor: A.rgb(80,80,120),
|
|
524
|
+
pathColor: A.rgb(255,200,80),
|
|
525
|
+
gridColor: A.rgb(60,80,60),
|
|
526
|
+
labelColor: A.rgb(255,255,180),
|
|
527
|
+
axisColor: A.rgb(120,120,200),
|
|
528
|
+
title: '',
|
|
529
|
+
showAxes: true,
|
|
530
|
+
showLabels: true,
|
|
531
|
+
showGrid: false,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
configure(opts) { Object.assign(this.options, opts); return this; }
|
|
536
|
+
|
|
537
|
+
// Map a matrix (x,y) to canvas pixel (px,py)
|
|
538
|
+
_toPixel(x, y, canvas, matBounds) {
|
|
539
|
+
const o = this.options;
|
|
540
|
+
const mb = matBounds || new MatrixBounds(this.matrix, 0, 0, this.matrix.width, this.matrix.height);
|
|
541
|
+
const padX = o.padding, padY = o.padding;
|
|
542
|
+
const pw = canvas.pixelW - padX * 2;
|
|
543
|
+
const ph = canvas.pixelH - padY * 2;
|
|
544
|
+
const tx = (x - mb.minX) / (mb.width || 1);
|
|
545
|
+
const ty = (y - mb.minY) / (mb.height || 1);
|
|
546
|
+
return {
|
|
547
|
+
px: Math.round(padX + tx * pw),
|
|
548
|
+
py: Math.round(padY + (1 - ty) * ph), // flip Y so +y is up
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Bresenham line in pixel space on canvas
|
|
553
|
+
_drawLine(canvas, x0, y0, x1, y1, colorAnsi) {
|
|
554
|
+
let dx = Math.abs(x1-x0), dy = Math.abs(y1-y0);
|
|
555
|
+
let sx = x0<x1?1:-1, sy = y0<y1?1:-1, err = dx-dy;
|
|
556
|
+
while(true){
|
|
557
|
+
canvas.setPixel(x0, y0, colorAnsi);
|
|
558
|
+
if(x0===x1&&y0===y1) break;
|
|
559
|
+
const e2=2*err;
|
|
560
|
+
if(e2>-dy){err-=dy;x0+=sx;}
|
|
561
|
+
if(e2< dx){err+=dx;y0+=sy;}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Draw a circle outline in pixel space
|
|
566
|
+
_drawCircle(canvas, cx, cy, r, colorAnsi) {
|
|
567
|
+
const steps = Math.max(12, Math.round(2 * Math.PI * r));
|
|
568
|
+
for (let i = 0; i < steps; i++) {
|
|
569
|
+
const a = (i / steps) * Math.PI * 2;
|
|
570
|
+
canvas.setPixel(Math.round(cx + Math.cos(a) * r), Math.round(cy + Math.sin(a) * r), colorAnsi);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Build a canvas from the current matrix state
|
|
575
|
+
_build(overrideOpts = {}) {
|
|
576
|
+
const o = Object.assign({}, this.options, overrideOpts);
|
|
577
|
+
const m = this.matrix;
|
|
578
|
+
const c = new MatrixCanvas(o.charW, o.charH);
|
|
579
|
+
const mb = m._registeredBounds || new MatrixBounds(m, 0, 0, m.width, m.height);
|
|
580
|
+
const tp = (x, y) => this._toPixel(x, y, c, mb);
|
|
581
|
+
|
|
582
|
+
// Draw axes
|
|
583
|
+
if (o.showAxes) {
|
|
584
|
+
const originPx = tp(0, 0);
|
|
585
|
+
// x axis
|
|
586
|
+
const xEnd = tp(m.width, 0);
|
|
587
|
+
this._drawLine(c, tp(0, 0).px, tp(0, 0).py, xEnd.px, xEnd.py, o.axisColor);
|
|
588
|
+
// y axis
|
|
589
|
+
const yEnd = tp(0, m.height);
|
|
590
|
+
this._drawLine(c, tp(0, 0).px, tp(0, 0).py, yEnd.px, yEnd.py, o.axisColor);
|
|
591
|
+
// origin marker
|
|
592
|
+
c.setChar(Math.floor(originPx.px/2), Math.floor(originPx.py/4), '0', o.axisColor);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Draw registered grid if any
|
|
596
|
+
if (o.showGrid && m._registeredGrid) {
|
|
597
|
+
const g = m._registeredGrid;
|
|
598
|
+
g.forEach((p) => {
|
|
599
|
+
const {px,py} = tp(p.x, p.y);
|
|
600
|
+
c.setPixel(px, py, o.gridColor);
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Draw registered edges (graphs, connectors, paths)
|
|
605
|
+
for (const edge of (m._renderEdges || [])) {
|
|
606
|
+
const {a, b, color} = edge;
|
|
607
|
+
const pa = tp(a.x, a.y), pb = tp(b.x, b.y);
|
|
608
|
+
this._drawLine(c, pa.px, pa.py, pb.px, pb.py, color || o.edgeColor);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Draw path lines
|
|
612
|
+
for (const pathObj of (m._renderPaths || [])) {
|
|
613
|
+
const {points, color} = pathObj;
|
|
614
|
+
for (let i = 1; i < points.length; i++) {
|
|
615
|
+
const pa = tp(points[i-1].x, points[i-1].y);
|
|
616
|
+
const pb = tp(points[i].x, points[i].y);
|
|
617
|
+
this._drawLine(c, pa.px, pa.py, pb.px, pb.py, color || o.pathColor);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Draw points
|
|
622
|
+
for (const p of m._points) {
|
|
623
|
+
const {px, py} = tp(p.x, p.y);
|
|
624
|
+
const col = p.color || o.pointColor;
|
|
625
|
+
this._drawCircle(c, px, py, 2, col);
|
|
626
|
+
c.setPixel(px, py, col);
|
|
627
|
+
if (o.showLabels && p.label) {
|
|
628
|
+
const cx = Math.floor(px/2) + 1, cy = Math.floor(py/4);
|
|
629
|
+
const lbl = p.label.slice(0,6);
|
|
630
|
+
for (let i = 0; i < lbl.length; i++) c.setChar(cx+i, cy, lbl[i], o.labelColor);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
return { canvas: c, options: o };
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// ── Terminal render ──────────────────────────────────────────────────────
|
|
638
|
+
|
|
639
|
+
// Register a graph for rendering — draws all its edges
|
|
640
|
+
addGraph(graph, color) {
|
|
641
|
+
if (!this.matrix._renderEdges) this.matrix._renderEdges = [];
|
|
642
|
+
for (const [id, edges] of graph._edges) {
|
|
643
|
+
const a = graph._nodes.get(id);
|
|
644
|
+
for (const [nid] of edges) {
|
|
645
|
+
const b = graph._nodes.get(nid);
|
|
646
|
+
if (a && b) this.matrix._renderEdges.push({ a, b, color });
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return this;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Register a path for rendering
|
|
653
|
+
addPath(path, color) {
|
|
654
|
+
if (!this.matrix._renderPaths) this.matrix._renderPaths = [];
|
|
655
|
+
this.matrix._renderPaths.push({ points: path.points, color });
|
|
656
|
+
return this;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Set the visible bounds explicitly (overrides 0,0→w,h)
|
|
660
|
+
setBounds(bounds) { this.matrix._registeredBounds = bounds; return this; }
|
|
661
|
+
|
|
662
|
+
// Clear all registered render overlays
|
|
663
|
+
clearOverlays() { this.matrix._renderEdges = []; this.matrix._renderPaths = []; return this; }
|
|
664
|
+
|
|
665
|
+
// Print a frame to stdout
|
|
666
|
+
frame(opts = {}) {
|
|
667
|
+
const { canvas, options: o } = this._build(opts);
|
|
668
|
+
process.stdout.write(canvas.render({ border: true, title: o.title || this.matrix.toString() }) + '\n');
|
|
669
|
+
return this;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Print frame with cursor positioning (for live update)
|
|
673
|
+
frameAt(row = 1, col = 1, opts = {}) {
|
|
674
|
+
const { canvas, options: o } = this._build(opts);
|
|
675
|
+
const lines = canvas.render({ border: true, title: o.title || '' }).split('\n');
|
|
676
|
+
let out = A.hide;
|
|
677
|
+
lines.forEach((line, i) => { out += A.pos(row + i, col) + line; });
|
|
678
|
+
out += A.show;
|
|
679
|
+
process.stdout.write(out);
|
|
680
|
+
return this;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Start a live animation loop: fn(matrix, renderer, frame) called each tick
|
|
684
|
+
animate(fn, fps = 12, opts = {}) {
|
|
685
|
+
const ms = Math.round(1000 / fps);
|
|
686
|
+
let frame = 0;
|
|
687
|
+
const charW = opts.charW || this.options.charW;
|
|
688
|
+
const charH = opts.charH || this.options.charH;
|
|
689
|
+
process.stdout.write(A.alt + A.hide + A.cls + A.home);
|
|
690
|
+
const interval = setInterval(() => {
|
|
691
|
+
try {
|
|
692
|
+
fn(this.matrix, this, frame++);
|
|
693
|
+
this.frameAt(1, 1, opts);
|
|
694
|
+
} catch (e) {
|
|
695
|
+
clearInterval(interval);
|
|
696
|
+
process.stdout.write(A.main + A.show);
|
|
697
|
+
throw e;
|
|
698
|
+
}
|
|
699
|
+
}, ms);
|
|
700
|
+
// Stop on Ctrl+C
|
|
701
|
+
process.on('SIGINT', () => {
|
|
702
|
+
clearInterval(interval);
|
|
703
|
+
process.stdout.write(A.main + A.show + '\n');
|
|
704
|
+
process.exit(0);
|
|
705
|
+
});
|
|
706
|
+
return () => { clearInterval(interval); process.stdout.write(A.main + A.show + '\n'); };
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// ── HTML render ──────────────────────────────────────────────────────────
|
|
710
|
+
|
|
711
|
+
toHTML(opts = {}) {
|
|
712
|
+
const { canvas } = this._build(opts);
|
|
713
|
+
const o = Object.assign({}, this.options, opts);
|
|
714
|
+
const title = o.title || this.matrix.toString();
|
|
715
|
+
// Strip ANSI codes and produce an HTML <pre> block with spans for colors
|
|
716
|
+
const raw = canvas.render({ border: true, title });
|
|
717
|
+
// Simple ANSI→HTML (handles 24-bit RGB and reset)
|
|
718
|
+
let html = raw
|
|
719
|
+
.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
|
720
|
+
.replace(/\x1b\[38;2;(\d+);(\d+);(\d+)m/g, (_,r,g,b) => `<span style="color:rgb(${r},${g},${b})">`)
|
|
721
|
+
.replace(/\x1b\[0m/g, '</span>')
|
|
722
|
+
.replace(/\x1b\[[0-9;]*m/g, '');
|
|
723
|
+
return `<pre class="nvml-matrix" style="font-family:monospace;line-height:1.1;background:#0a0a0f;color:#ccc;padding:1rem;border-radius:6px;overflow:auto">${html}</pre>`;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// ── NVML render ──────────────────────────────────────────────────────────
|
|
727
|
+
// Returns a complete .nvml page source string.
|
|
728
|
+
|
|
729
|
+
toNVML(opts = {}) {
|
|
730
|
+
const o = Object.assign({}, this.options, opts);
|
|
731
|
+
const title = o.title || `Matrix ${this.matrix.width}×${this.matrix.height}`;
|
|
732
|
+
const html = this.toHTML(opts);
|
|
733
|
+
|
|
734
|
+
// Build signal data for any named points
|
|
735
|
+
const signals = this.matrix._points
|
|
736
|
+
.filter(p => p.label)
|
|
737
|
+
.map(p => ` ${p.label}_x=${p.x},\n ${p.label}_y=${p.y},`)
|
|
738
|
+
.join('\n');
|
|
739
|
+
|
|
740
|
+
const pointRows = this.matrix._points
|
|
741
|
+
.filter(p => p.label)
|
|
742
|
+
.map(p => ` {tr} [\n {td}='${p.label}'\n {td} [ text -> ${p.label}_x ]\n {td} [ text -> ${p.label}_y ]\n ]`)
|
|
743
|
+
.join('\n');
|
|
744
|
+
|
|
745
|
+
const stateBlock = signals
|
|
746
|
+
? `@state [\n${signals}\n]\n\n`
|
|
747
|
+
: '';
|
|
748
|
+
|
|
749
|
+
return `@config [\n title='${title}',\n lang='en',\n charset='UTF-8',\n]\n\n${stateBlock}@ss [\n {body} [ font-family='monospace', background='#0a0a0f', color='#ccc', margin='0', padding='2rem' ]\n {h1} [ color='#6c63ff', font-size='1.4rem', margin-bottom='1rem' ]\n {.matrix-wrap} [ overflow='auto', margin-bottom='2rem' ]\n {table} [ border-collapse='collapse', font-size='0.85rem', color='#ccc' ]\n {th} [ padding='0.3rem 0.8rem', background='#1a1a2e', color='#6c63ff', border='1px solid #333' ]\n {td} [ padding='0.3rem 0.8rem', border='1px solid #222', text-align='center' ]\n]\n\n@visual [\n {h1}='${title}'\n\n {div} [\n class='matrix-wrap',\n [..]::code=(\n${html.replace(/\\/g,'\\\\').replace(/`/g,'\\`')}\n )\n ]\n\n${pointRows ? ` {h2}='Named Points'\n {table} [\n {thead} [ {tr} [ {th}='Name' {th}='X' {th}='Y' ] ]\n {tbody} [\n${pointRows}\n ]\n ]\n` : ''}\n]`;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// ── MatrixDebug ───────────────────────────────────────────────────────────────
|
|
754
|
+
// ASCII debug + quick terminal output (no braille — plain chars)
|
|
755
|
+
|
|
756
|
+
class MatrixDebug {
|
|
757
|
+
constructor(matrix) { this.matrix = matrix; }
|
|
758
|
+
|
|
759
|
+
drawPoints(opts = {}) {
|
|
760
|
+
const m = this.matrix;
|
|
761
|
+
const scale = opts.scale ?? 2;
|
|
762
|
+
const W = Math.ceil(m.width * scale) + 3;
|
|
763
|
+
const H = Math.ceil(m.height * scale) + 3;
|
|
764
|
+
const buf = Array.from({length:H},()=>Array(W).fill(' '));
|
|
765
|
+
const plot = (x, y, ch, col='') => {
|
|
766
|
+
const cx=Math.round(x*scale)+1, cy=H-Math.round(y*scale)-2;
|
|
767
|
+
if(cx>=0&&cx<W&&cy>=0&&cy<H) buf[cy][cx]={ ch, col };
|
|
768
|
+
};
|
|
769
|
+
// axes
|
|
770
|
+
for(let i=0;i<W;i++) buf[H-2][i]={ ch:'─', col:A.fg(240) };
|
|
771
|
+
for(let i=0;i<H;i++) buf[i][1] ={ ch:'│', col:A.fg(240) };
|
|
772
|
+
buf[H-2][1]={ ch:'└', col:A.fg(240) };
|
|
773
|
+
// points
|
|
774
|
+
for(const p of m._points) {
|
|
775
|
+
const col = p.color || A.rgb(100,200,255);
|
|
776
|
+
plot(p.x, p.y, p.label?p.label[0]:'●', col);
|
|
777
|
+
}
|
|
778
|
+
// output
|
|
779
|
+
console.log(A.bold + (opts.title||m.toString()) + A.reset);
|
|
780
|
+
for(const row of buf){
|
|
781
|
+
let line='';
|
|
782
|
+
for(const cell of row){
|
|
783
|
+
if(typeof cell==='object'&&cell!==null) line+=cell.col+cell.ch+A.reset;
|
|
784
|
+
else line+=' ';
|
|
785
|
+
}
|
|
786
|
+
console.log(line);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
drawGraph(graph, opts = {}) {
|
|
791
|
+
const m = this.matrix;
|
|
792
|
+
const scale = opts.scale ?? 2;
|
|
793
|
+
const W = Math.ceil(m.width * scale) + 3;
|
|
794
|
+
const H = Math.ceil(m.height * scale) + 3;
|
|
795
|
+
const buf = Array.from({length:H},()=>Array(W).fill(' '));
|
|
796
|
+
const toC = (x,y) => ({cx:Math.round(x*scale)+1, cy:H-Math.round(y*scale)-2});
|
|
797
|
+
|
|
798
|
+
const plotLine = (x0,y0,x1,y1,col) => {
|
|
799
|
+
// Bresenham in char space
|
|
800
|
+
let dx=Math.abs(x1-x0),dy=Math.abs(y1-y0),sx=x0<x1?1:-1,sy=y0<y1?1:-1,err=dx-dy;
|
|
801
|
+
while(true){
|
|
802
|
+
if(x0>=0&&x0<W&&y0>=0&&y0<H) buf[y0][x0]={ ch:dx>dy?'─':'│', col };
|
|
803
|
+
if(x0===x1&&y0===y1) break;
|
|
804
|
+
const e2=2*err; if(e2>-dy){err-=dy;x0+=sx;} if(e2<dx){err+=dx;y0+=sy;}
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
// axes
|
|
809
|
+
for(let i=0;i<W;i++) buf[H-2][i]={ ch:'─', col:A.fg(240) };
|
|
810
|
+
for(let i=0;i<H;i++) buf[i][1] ={ ch:'│', col:A.fg(240) };
|
|
811
|
+
buf[H-2][1]={ ch:'└', col:A.fg(240) };
|
|
812
|
+
|
|
813
|
+
// edges
|
|
814
|
+
const seen = new Set();
|
|
815
|
+
for(const [id,edges] of graph._edges){
|
|
816
|
+
const a = graph._nodes.get(id);
|
|
817
|
+
if(!a) continue;
|
|
818
|
+
for(const [nid] of edges){
|
|
819
|
+
const key=[Math.min(id,nid),Math.max(id,nid)].join('-');
|
|
820
|
+
if(seen.has(key)) continue; seen.add(key);
|
|
821
|
+
const b = graph._nodes.get(nid);
|
|
822
|
+
if(!b) continue;
|
|
823
|
+
const pa=toC(a.x,a.y), pb=toC(b.x,b.y);
|
|
824
|
+
plotLine(pa.cx,pa.cy,pb.cx,pb.cy,A.rgb(80,100,160));
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
// nodes
|
|
828
|
+
for(const p of graph.nodes()){
|
|
829
|
+
const {cx,cy}=toC(p.x,p.y);
|
|
830
|
+
const col=p.color||A.rgb(100,200,255);
|
|
831
|
+
if(cx>=0&&cx<W&&cy>=0&&cy<H) buf[cy][cx]={ ch:p.label?p.label[0]:'●', col };
|
|
832
|
+
if(p.label){
|
|
833
|
+
for(let i=0;i<Math.min(p.label.length,5);i++){
|
|
834
|
+
const lx=cx+i+1;
|
|
835
|
+
if(lx<W) buf[cy][lx]={ ch:p.label[i], col:A.rgb(255,255,180) };
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
console.log(A.bold+(opts.title||`Graph: ${graph.nodeCount}n ${graph.edgeCount}e`)+A.reset);
|
|
841
|
+
for(const row of buf){
|
|
842
|
+
let line='';
|
|
843
|
+
for(const cell of row){
|
|
844
|
+
if(typeof cell==='object'&&cell!==null) line+=cell.col+cell.ch+A.reset;
|
|
845
|
+
else line+=' ';
|
|
846
|
+
}
|
|
847
|
+
console.log(line);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
drawPath(path, opts = {}) {
|
|
852
|
+
console.log(A.bold+(opts.title||path.toString())+A.reset);
|
|
853
|
+
path.points.forEach((p,i) => {
|
|
854
|
+
const bar = '█'.repeat(Math.min(40, Math.round(p.x)));
|
|
855
|
+
const col = p.color||A.rgb(255,200,80);
|
|
856
|
+
console.log(` ${String(i).padStart(3,' ')} (${String(p.x).padStart(6,' ')}, ${String(p.y).padStart(6,' ')}) ${col}${bar}${A.reset}${p.label?' '+p.label:''}`);
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
drawGrid(grid, opts = {}) {
|
|
861
|
+
console.log(A.bold+(opts.title||grid.toString())+A.reset);
|
|
862
|
+
const col = A.fg(240);
|
|
863
|
+
for(let r=0;r<grid.rows;r++){
|
|
864
|
+
let line=' ';
|
|
865
|
+
for(let c=0;c<grid.cols;c++){
|
|
866
|
+
const p=grid.at(r,c);
|
|
867
|
+
const ch=p?.label?p.label[0]:'+';
|
|
868
|
+
const pcol=p?.color||col;
|
|
869
|
+
line+=pcol+ch+A.reset+' ';
|
|
870
|
+
}
|
|
871
|
+
console.log(line);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
drawAxes() {
|
|
876
|
+
const m=this.matrix;
|
|
877
|
+
console.log(A.bold+`Matrix ${m.width}×${m.height} axes:`+A.reset);
|
|
878
|
+
m.axes.forEach((ax,i)=>{
|
|
879
|
+
const u=ax.unitVector();
|
|
880
|
+
console.log(` ${A.fg(99)}[${i}]${A.reset} angle=${ax.angle}° margin=${ax.margin} unit=(${u.x.toFixed(3)},${u.y.toFixed(3)})`);
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
info() {
|
|
885
|
+
const m=this.matrix;
|
|
886
|
+
console.log([
|
|
887
|
+
A.bold+`Matrix {`+A.reset,
|
|
888
|
+
` ${A.fg(99)}size${A.reset}: ${m.width} × ${m.height}`,
|
|
889
|
+
` ${A.fg(99)}axes${A.reset}: ${m.axes.length}`,
|
|
890
|
+
` ${A.fg(99)}points${A.reset}: ${m._points.length}`,
|
|
891
|
+
`}`
|
|
892
|
+
].join('\n'));
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// ── Matrix ────────────────────────────────────────────────────────────────────
|
|
897
|
+
|
|
898
|
+
class Matrix {
|
|
899
|
+
constructor(width=100,height=100,axes=[]) {
|
|
900
|
+
this.width = width;
|
|
901
|
+
this.height = height;
|
|
902
|
+
this.axes = axes.length>0?axes:[new MatrixAxis(0,1),new MatrixAxis(90,1)];
|
|
903
|
+
this._points = [];
|
|
904
|
+
this._renderEdges = [];
|
|
905
|
+
this._renderPaths = [];
|
|
906
|
+
this.debug = new MatrixDebug(this);
|
|
907
|
+
this.render = new MatrixRenderer(this);
|
|
908
|
+
this.interp = new MatrixInterpolation(this);
|
|
909
|
+
this.projection = new MatrixProjection(this);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
point(x=0,y=0) { const p=new MatrixPoint(this,x,y); this._points.push(p); return p; }
|
|
913
|
+
removePoint(p) { const i=this._points.indexOf(p); if(i!==-1) this._points.splice(i,1); }
|
|
914
|
+
clearPoints() { this._points=[]; }
|
|
915
|
+
|
|
916
|
+
distance(a,b) { return a.distanceTo(b); }
|
|
917
|
+
manhattan(a,b) { return a.manhattanTo(b); }
|
|
918
|
+
chebyshev(a,b) { return a.chebyshevTo(b); }
|
|
919
|
+
midpoint(a,b) { return a.midpointTo(b); }
|
|
920
|
+
angle(a,b) { return a.angleTo(b); }
|
|
921
|
+
axisDistance(a,b) { return this.axes.reduce((s,ax)=>s+Math.abs(ax.project(b.x-a.x,b.y-a.y)),0); }
|
|
922
|
+
getLengthOf(points) { let t=0; for(let i=1;i<points.length;i++) t+=points[i-1].distanceTo(points[i]); return t; }
|
|
923
|
+
|
|
924
|
+
bounds(mnX,mnY,mxX,mxY) { return new MatrixBounds(this,mnX,mnY,mxX,mxY); }
|
|
925
|
+
boundsFromPoints(pts) { return MatrixBounds.fromPoints(this,pts); }
|
|
926
|
+
spaceBounds() { return new MatrixBounds(this,0,0,this.width,this.height); }
|
|
927
|
+
|
|
928
|
+
grid(rows,cols,ox=0,oy=0,cw=1,ch=1) { return new MatrixGrid(this,rows,cols,ox,oy,cw,ch); }
|
|
929
|
+
translation(a,b) { return new MatrixTranslation(this,a,b); }
|
|
930
|
+
transform(cfg) { return new MatrixTransform(this,cfg); }
|
|
931
|
+
translateTransform(dx,dy) { return MatrixTransform.translate(this,dx,dy); }
|
|
932
|
+
rotateTransform(deg,cx,cy) { return MatrixTransform.rotate(this,deg,cx,cy); }
|
|
933
|
+
scaleTransform(sx,sy,cx,cy) { return MatrixTransform.scale(this,sx,sy,cx,cy); }
|
|
934
|
+
shearTransform(shx,shy) { return MatrixTransform.shear(this,shx,shy); }
|
|
935
|
+
chain(transforms) { return new MatrixTransformChain(this,transforms); }
|
|
936
|
+
path(points) { return new MatrixPath(this,points); }
|
|
937
|
+
connector(points) { return new MatrixConnector(this,points); }
|
|
938
|
+
graph() { return new MatrixGraph(this); }
|
|
939
|
+
vector(dx,dy) { return new MatrixVector(dx,dy); }
|
|
940
|
+
lerp(a,b,t) { return this.interp.lerp(a,b,t); }
|
|
941
|
+
smoothstep(a,b,t) { return this.interp.smoothstep(a,b,t); }
|
|
942
|
+
bezierQuadratic(p0,cp,p2,t) { return this.interp.bezierQuadratic(p0,cp,p2,t); }
|
|
943
|
+
bezierCubic(p0,c1,c2,p3,t) { return this.interp.bezierCubic(p0,c1,c2,p3,t); }
|
|
944
|
+
project(p,ax) { return this.projection.ontoAxis(p,ax); }
|
|
945
|
+
isometric(x,y,z,tw,th) { return this.projection.isometric(x,y,z,tw,th); }
|
|
946
|
+
snap(p,sx,sy) { return p.snap(sx,sy); }
|
|
947
|
+
normalize(v) { return v.normalize?v.normalize():new MatrixVector(v.x,v.y).normalize(); }
|
|
948
|
+
clone(p) { return p.clone(); }
|
|
949
|
+
clonePath(p) { return p.clone(); }
|
|
950
|
+
toString() { return `Matrix(${this.width}×${this.height} axes=${this.axes.length} pts=${this._points.length})`; }
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
module.exports = {
|
|
954
|
+
kitdef: {
|
|
955
|
+
Matrix, MatrixAxis, MatrixPoint, MatrixVector,
|
|
956
|
+
MatrixBounds, MatrixTranslation, MatrixTransform, MatrixTransformChain,
|
|
957
|
+
MatrixConnector, MatrixPath, MatrixGrid, MatrixGraph,
|
|
958
|
+
MatrixProjection, MatrixInterpolation, MatrixDebug, MatrixRenderer, MatrixCanvas,
|
|
959
|
+
}
|
|
960
|
+
};
|