@vectojs/core 0.1.0 → 0.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/dist/animation/drivers.d.ts +48 -0
- package/dist/animation/easing.d.ts +16 -0
- package/dist/{chunk-53DAQC3U.js → chunk-W3SFIVXO.js} +356 -60
- package/dist/{chunk-M2IZPGOL.mjs → chunk-Y2N7TGEH.mjs} +303 -7
- package/dist/components/GridTextEntity.d.ts +15 -0
- package/dist/components/SplineEntity.d.ts +144 -0
- package/dist/components/TextEntity.d.ts +35 -0
- package/dist/index.d.ts +26 -577
- package/dist/index.js +60 -78
- package/dist/index.mjs +15 -33
- package/dist/{layout.d.mts → layout/LayoutEngine.d.ts} +15 -70
- package/dist/layout/LayoutWorker.d.ts +23 -0
- package/dist/layout/LayoutWorkerManager.d.ts +22 -0
- package/dist/layout/LayoutWorkerSource.d.ts +1 -0
- package/dist/layout/index.d.ts +3 -0
- package/dist/layout/measure.d.ts +20 -0
- package/dist/math/SpatialHashGrid.d.ts +53 -0
- package/dist/math/SpringPhysics.d.ts +13 -0
- package/dist/renderer/CanvasRenderer.d.ts +81 -0
- package/dist/renderer/IRenderer.d.ts +178 -0
- package/dist/renderer/SVGRenderer.d.ts +69 -0
- package/dist/renderer/WebGLPointRenderer.d.ts +62 -0
- package/dist/renderer/WebGPUParticleSystemManager.d.ts +14 -0
- package/dist/renderer/colorParse.d.ts +17 -0
- package/dist/renderer/index.d.ts +6 -0
- package/dist/text/ArabicShaper.d.ts +10 -0
- package/dist/text/BidiResolver.d.ts +6 -0
- package/dist/{text.d.ts → text/MSDFFont.d.ts} +10 -82
- package/dist/text/MSDFTextEntity.d.ts +30 -0
- package/dist/text/SVGEntity.d.ts +22 -0
- package/dist/text/index.d.ts +5 -0
- package/dist/text.js +2 -2
- package/dist/text.mjs +1 -1
- package/dist/tree/ComputeParticleEntity.d.ts +118 -0
- package/dist/tree/DOMPortalEntity.d.ts +18 -0
- package/dist/{Entity-D-rfAFCf.d.mts → tree/Entity.d.ts} +59 -197
- package/dist/tree/Scene.d.ts +295 -0
- package/package.json +5 -5
- package/dist/Entity-D-rfAFCf.d.ts +0 -572
- package/dist/index-ByBDSmMK.d.mts +0 -365
- package/dist/index-C3Fd_XmG.d.ts +0 -365
- package/dist/index.d.mts +0 -577
- package/dist/layout.d.ts +0 -319
- package/dist/renderer.d.mts +0 -2
- package/dist/renderer.d.ts +0 -2
- package/dist/text.d.mts +0 -201
|
@@ -2,6 +2,120 @@ import {
|
|
|
2
2
|
LayoutWorkerManager
|
|
3
3
|
} from "./chunk-YA2J5ZH7.mjs";
|
|
4
4
|
|
|
5
|
+
// src/math/SpringPhysics.ts
|
|
6
|
+
var SpringPhysics = class {
|
|
7
|
+
value;
|
|
8
|
+
target;
|
|
9
|
+
velocity = 0;
|
|
10
|
+
stiffness = 180;
|
|
11
|
+
damping = 12;
|
|
12
|
+
mass = 1;
|
|
13
|
+
valEpsilon = 5e-3;
|
|
14
|
+
velEpsilon = 5e-3;
|
|
15
|
+
constructor(initial) {
|
|
16
|
+
this.value = initial;
|
|
17
|
+
this.target = initial;
|
|
18
|
+
}
|
|
19
|
+
update(dt) {
|
|
20
|
+
if (this.isAtRest()) {
|
|
21
|
+
this.value = this.target;
|
|
22
|
+
this.velocity = 0;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const forceSpring = -this.stiffness * (this.value - this.target);
|
|
26
|
+
const forceDamping = -this.damping * this.velocity;
|
|
27
|
+
const acceleration = (forceSpring + forceDamping) / this.mass;
|
|
28
|
+
this.velocity += acceleration * dt;
|
|
29
|
+
this.value += this.velocity * dt;
|
|
30
|
+
}
|
|
31
|
+
isAtRest() {
|
|
32
|
+
return Math.abs(this.value - this.target) < this.valEpsilon && Math.abs(this.velocity) < this.velEpsilon;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/animation/easing.ts
|
|
37
|
+
var c1 = 1.70158;
|
|
38
|
+
var c3 = c1 + 1;
|
|
39
|
+
var Easing = {
|
|
40
|
+
linear: (t) => t,
|
|
41
|
+
easeInQuad: (t) => t * t,
|
|
42
|
+
easeOutQuad: (t) => t * (2 - t),
|
|
43
|
+
easeInOutQuad: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
|
|
44
|
+
easeInCubic: (t) => t * t * t,
|
|
45
|
+
easeOutCubic: (t) => 1 - Math.pow(1 - t, 3),
|
|
46
|
+
easeInOutCubic: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
|
|
47
|
+
easeOutBack: (t) => 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2),
|
|
48
|
+
easeInOutBack: (t) => {
|
|
49
|
+
const c2 = c1 * 1.525;
|
|
50
|
+
return t < 0.5 ? Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2) / 2 : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// src/animation/drivers.ts
|
|
55
|
+
function isTweenConfig(c) {
|
|
56
|
+
return typeof c === "object" && "duration" in c;
|
|
57
|
+
}
|
|
58
|
+
var TweenDriver = class {
|
|
59
|
+
value;
|
|
60
|
+
from;
|
|
61
|
+
to;
|
|
62
|
+
elapsed = 0;
|
|
63
|
+
duration;
|
|
64
|
+
delay;
|
|
65
|
+
ease;
|
|
66
|
+
constructor(from, to, cfg) {
|
|
67
|
+
this.value = from;
|
|
68
|
+
this.from = from;
|
|
69
|
+
this.to = to;
|
|
70
|
+
this.duration = Math.max(1, cfg.duration);
|
|
71
|
+
this.delay = cfg.delay ?? 0;
|
|
72
|
+
this.ease = typeof cfg.easing === "function" ? cfg.easing : Easing[cfg.easing ?? "easeOutQuad"];
|
|
73
|
+
}
|
|
74
|
+
get target() {
|
|
75
|
+
return this.to;
|
|
76
|
+
}
|
|
77
|
+
retarget(to) {
|
|
78
|
+
this.from = this.value;
|
|
79
|
+
this.to = to;
|
|
80
|
+
this.elapsed = 0;
|
|
81
|
+
}
|
|
82
|
+
tick(dtMs) {
|
|
83
|
+
this.elapsed += dtMs;
|
|
84
|
+
const active = this.elapsed - this.delay;
|
|
85
|
+
if (active <= 0) return;
|
|
86
|
+
const p = Math.min(active / this.duration, 1);
|
|
87
|
+
this.value = this.from + (this.to - this.from) * this.ease(p);
|
|
88
|
+
}
|
|
89
|
+
isDone() {
|
|
90
|
+
return this.elapsed - this.delay >= this.duration;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var SpringDriver = class {
|
|
94
|
+
spring;
|
|
95
|
+
constructor(from, to, cfg) {
|
|
96
|
+
this.spring = new SpringPhysics(from);
|
|
97
|
+
if (cfg.stiffness !== void 0) this.spring.stiffness = cfg.stiffness;
|
|
98
|
+
if (cfg.damping !== void 0) this.spring.damping = cfg.damping;
|
|
99
|
+
if (cfg.mass !== void 0) this.spring.mass = cfg.mass;
|
|
100
|
+
this.spring.target = to;
|
|
101
|
+
}
|
|
102
|
+
get value() {
|
|
103
|
+
return this.spring.value;
|
|
104
|
+
}
|
|
105
|
+
get target() {
|
|
106
|
+
return this.spring.target;
|
|
107
|
+
}
|
|
108
|
+
retarget(to) {
|
|
109
|
+
this.spring.target = to;
|
|
110
|
+
}
|
|
111
|
+
tick(dtMs) {
|
|
112
|
+
this.spring.update(dtMs / 1e3);
|
|
113
|
+
}
|
|
114
|
+
isDone() {
|
|
115
|
+
return this.spring.isAtRest();
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
5
119
|
// src/tree/Entity.ts
|
|
6
120
|
var VectoJSEvent = class {
|
|
7
121
|
/** The event name. */
|
|
@@ -80,12 +194,60 @@ var Entity = class {
|
|
|
80
194
|
if (this._scene) return this._scene;
|
|
81
195
|
return this.parent ? this.parent.scene : null;
|
|
82
196
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
197
|
+
_x = 0;
|
|
198
|
+
_y = 0;
|
|
199
|
+
_scaleX = 1;
|
|
200
|
+
_scaleY = 1;
|
|
201
|
+
_rotation = 0;
|
|
202
|
+
_opacity = 1;
|
|
203
|
+
// Fast-path flag: false for the overwhelming majority of entities (incl. the
|
|
204
|
+
// Danmaku hot loop), so a bare `entity.x = v` is one boolean check + field write.
|
|
205
|
+
_hasTransitions = false;
|
|
206
|
+
_transitions = null;
|
|
207
|
+
_drivers = /* @__PURE__ */ new Map();
|
|
208
|
+
_mounted = false;
|
|
209
|
+
get x() {
|
|
210
|
+
return this._x;
|
|
211
|
+
}
|
|
212
|
+
set x(v) {
|
|
213
|
+
if (this._hasTransitions) this._animateProp("x", v);
|
|
214
|
+
else this._x = v;
|
|
215
|
+
}
|
|
216
|
+
get y() {
|
|
217
|
+
return this._y;
|
|
218
|
+
}
|
|
219
|
+
set y(v) {
|
|
220
|
+
if (this._hasTransitions) this._animateProp("y", v);
|
|
221
|
+
else this._y = v;
|
|
222
|
+
}
|
|
223
|
+
get scaleX() {
|
|
224
|
+
return this._scaleX;
|
|
225
|
+
}
|
|
226
|
+
set scaleX(v) {
|
|
227
|
+
if (this._hasTransitions) this._animateProp("scaleX", v);
|
|
228
|
+
else this._scaleX = v;
|
|
229
|
+
}
|
|
230
|
+
get scaleY() {
|
|
231
|
+
return this._scaleY;
|
|
232
|
+
}
|
|
233
|
+
set scaleY(v) {
|
|
234
|
+
if (this._hasTransitions) this._animateProp("scaleY", v);
|
|
235
|
+
else this._scaleY = v;
|
|
236
|
+
}
|
|
237
|
+
get rotation() {
|
|
238
|
+
return this._rotation;
|
|
239
|
+
}
|
|
240
|
+
set rotation(v) {
|
|
241
|
+
if (this._hasTransitions) this._animateProp("rotation", v);
|
|
242
|
+
else this._rotation = v;
|
|
243
|
+
}
|
|
244
|
+
get opacity() {
|
|
245
|
+
return this._opacity;
|
|
246
|
+
}
|
|
247
|
+
set opacity(v) {
|
|
248
|
+
if (this._hasTransitions) this._animateProp("opacity", v);
|
|
249
|
+
else this._opacity = v;
|
|
250
|
+
}
|
|
89
251
|
isDOMPortal = false;
|
|
90
252
|
_interactive = false;
|
|
91
253
|
get interactive() {
|
|
@@ -140,9 +302,20 @@ var Entity = class {
|
|
|
140
302
|
if (s) {
|
|
141
303
|
s.a11yNeedsReorder = true;
|
|
142
304
|
s.markDirty();
|
|
305
|
+
child._notifyMounted();
|
|
143
306
|
}
|
|
144
307
|
return this;
|
|
145
308
|
}
|
|
309
|
+
/** Called once when this entity becomes attached to a live Scene. Override to react. */
|
|
310
|
+
onMounted() {
|
|
311
|
+
}
|
|
312
|
+
/** Fire onMounted for this node and its descendants, guarded against double-fire. */
|
|
313
|
+
_notifyMounted() {
|
|
314
|
+
if (this._mounted) return;
|
|
315
|
+
this._mounted = true;
|
|
316
|
+
this.onMounted();
|
|
317
|
+
for (const c of this.children) c._notifyMounted();
|
|
318
|
+
}
|
|
146
319
|
/**
|
|
147
320
|
* Remove a child entity from this node.
|
|
148
321
|
*
|
|
@@ -195,6 +368,123 @@ var Entity = class {
|
|
|
195
368
|
});
|
|
196
369
|
return this;
|
|
197
370
|
}
|
|
371
|
+
/** Write a driver-computed value to a backing field without re-triggering the setter. */
|
|
372
|
+
_applyAnimated(prop, v) {
|
|
373
|
+
switch (prop) {
|
|
374
|
+
case "x":
|
|
375
|
+
this._x = v;
|
|
376
|
+
break;
|
|
377
|
+
case "y":
|
|
378
|
+
this._y = v;
|
|
379
|
+
break;
|
|
380
|
+
case "scaleX":
|
|
381
|
+
this._scaleX = v;
|
|
382
|
+
break;
|
|
383
|
+
case "scaleY":
|
|
384
|
+
this._scaleY = v;
|
|
385
|
+
break;
|
|
386
|
+
case "rotation":
|
|
387
|
+
this._rotation = v;
|
|
388
|
+
break;
|
|
389
|
+
case "opacity":
|
|
390
|
+
this._opacity = v;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
_currentOf(prop) {
|
|
395
|
+
switch (prop) {
|
|
396
|
+
case "x":
|
|
397
|
+
return this._x;
|
|
398
|
+
case "y":
|
|
399
|
+
return this._y;
|
|
400
|
+
case "scaleX":
|
|
401
|
+
return this._scaleX;
|
|
402
|
+
case "scaleY":
|
|
403
|
+
return this._scaleY;
|
|
404
|
+
case "rotation":
|
|
405
|
+
return this._rotation;
|
|
406
|
+
case "opacity":
|
|
407
|
+
return this._opacity;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Write a value immediately, bypassing any configured transition. For subclasses
|
|
412
|
+
* that need to seed a starting state (e.g. the presence helper's enter `from`).
|
|
413
|
+
*/
|
|
414
|
+
setImmediate(prop, v) {
|
|
415
|
+
this._drivers.delete(prop);
|
|
416
|
+
this._applyAnimated(prop, v);
|
|
417
|
+
}
|
|
418
|
+
_spawnDriver(prop, to, cfg) {
|
|
419
|
+
if (prop !== "opacity" && this.scene?.prefersReducedMotion) {
|
|
420
|
+
this._drivers.delete(prop);
|
|
421
|
+
this._applyAnimated(prop, to);
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const existing = this._drivers.get(prop);
|
|
425
|
+
if (existing) {
|
|
426
|
+
existing.retarget(to);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
const from = this._currentOf(prop);
|
|
430
|
+
const driver = isTweenConfig(cfg) ? new TweenDriver(from, to, cfg) : new SpringDriver(from, to, cfg === "spring" ? {} : cfg);
|
|
431
|
+
this._drivers.set(prop, driver);
|
|
432
|
+
this.scene?.markDirty();
|
|
433
|
+
}
|
|
434
|
+
/** Assignment path when a declarative transition is configured for `prop`. */
|
|
435
|
+
_animateProp(prop, to) {
|
|
436
|
+
const cfg = this._transitions?.get(prop);
|
|
437
|
+
if (!cfg) {
|
|
438
|
+
this._applyAnimated(prop, to);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
this._spawnDriver(prop, to, cfg);
|
|
442
|
+
}
|
|
443
|
+
/** Declare which properties animate, and how. Subsequent assignment animates them. */
|
|
444
|
+
setTransition(config) {
|
|
445
|
+
this._transitions ??= /* @__PURE__ */ new Map();
|
|
446
|
+
for (const [k, v] of Object.entries(config))
|
|
447
|
+
this._transitions.set(k, v);
|
|
448
|
+
this._hasTransitions = this._transitions.size > 0;
|
|
449
|
+
return this;
|
|
450
|
+
}
|
|
451
|
+
/** Imperative tween toward targets; resolves when all reach their end. */
|
|
452
|
+
animateTo(props, cfg) {
|
|
453
|
+
return this._driveTo(props, cfg);
|
|
454
|
+
}
|
|
455
|
+
/** Imperative spring toward targets; resolves when all reach rest. */
|
|
456
|
+
springTo(props, cfg = {}) {
|
|
457
|
+
return this._driveTo(props, cfg);
|
|
458
|
+
}
|
|
459
|
+
_driveTo(props, cfg) {
|
|
460
|
+
const entries = Object.entries(props);
|
|
461
|
+
return Promise.all(
|
|
462
|
+
entries.map(
|
|
463
|
+
(e) => new Promise((resolve) => {
|
|
464
|
+
this._spawnDriver(e[0], e[1], cfg);
|
|
465
|
+
const d = this._drivers.get(e[0]);
|
|
466
|
+
if (!d)
|
|
467
|
+
resolve();
|
|
468
|
+
else d.onDone = resolve;
|
|
469
|
+
})
|
|
470
|
+
)
|
|
471
|
+
).then(() => void 0);
|
|
472
|
+
}
|
|
473
|
+
/** Advance active property drivers one frame. Call from update(). */
|
|
474
|
+
tickDrivers(dt) {
|
|
475
|
+
if (this._drivers.size === 0) return;
|
|
476
|
+
for (const [prop, driver] of this._drivers) {
|
|
477
|
+
driver.tick(dt);
|
|
478
|
+
if (driver.isDone()) {
|
|
479
|
+
this._applyAnimated(prop, driver.target);
|
|
480
|
+
driver.onDone?.();
|
|
481
|
+
this._drivers.delete(prop);
|
|
482
|
+
} else {
|
|
483
|
+
this._applyAnimated(prop, driver.value);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
this.scene?.markDirty();
|
|
487
|
+
}
|
|
198
488
|
/**
|
|
199
489
|
* Advance the entity's internal state for one frame.
|
|
200
490
|
*
|
|
@@ -204,7 +494,8 @@ var Entity = class {
|
|
|
204
494
|
* @param dt - Elapsed time since the last frame in milliseconds.
|
|
205
495
|
* @param time - Absolute timestamp from `performance.now()`.
|
|
206
496
|
*/
|
|
207
|
-
update(
|
|
497
|
+
update(dt, time) {
|
|
498
|
+
this.tickDrivers(dt);
|
|
208
499
|
if (this.animations.length > 0) {
|
|
209
500
|
const anim = this.animations[0];
|
|
210
501
|
if (anim.startTime === -1) {
|
|
@@ -899,6 +1190,11 @@ var SVGEntity = class extends Entity {
|
|
|
899
1190
|
};
|
|
900
1191
|
|
|
901
1192
|
export {
|
|
1193
|
+
SpringPhysics,
|
|
1194
|
+
Easing,
|
|
1195
|
+
isTweenConfig,
|
|
1196
|
+
TweenDriver,
|
|
1197
|
+
SpringDriver,
|
|
902
1198
|
VectoJSEvent,
|
|
903
1199
|
Entity,
|
|
904
1200
|
MSDFFont,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Entity } from '../tree/Entity';
|
|
2
|
+
import { IRenderer } from '../renderer/IRenderer';
|
|
3
|
+
export declare class GridTextEntity extends Entity {
|
|
4
|
+
fontSize: number;
|
|
5
|
+
fillStyle: string;
|
|
6
|
+
grid: string[];
|
|
7
|
+
cols: number;
|
|
8
|
+
rows: number;
|
|
9
|
+
charWidth: number;
|
|
10
|
+
charHeight: number;
|
|
11
|
+
constructor(_atlas: any, fontSize?: number);
|
|
12
|
+
updateGrid(ascii: string[]): void;
|
|
13
|
+
isPointInside(_globalX: number, _globalY: number): boolean;
|
|
14
|
+
render(renderer: IRenderer): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Bounds, Entity } from '../tree/Entity';
|
|
2
|
+
import { IRenderer } from '../renderer/IRenderer';
|
|
3
|
+
/** One piecewise-cubic segment: x(t) and y(t) as `[a,b,c,d]` polynomial coefficients. */
|
|
4
|
+
export interface SplineSegment {
|
|
5
|
+
start_t: number;
|
|
6
|
+
end_t: number;
|
|
7
|
+
x_poly: number[];
|
|
8
|
+
y_poly: number[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Color of a spline equation: an `[r,g,b]` triple in `0..1`, a linear-gradient
|
|
12
|
+
* descriptor, or `null` (use the entity's default color).
|
|
13
|
+
*/
|
|
14
|
+
export type SplineColor = [number, number, number] | {
|
|
15
|
+
stops: [number, [number, number, number]][];
|
|
16
|
+
start_pos: [number, number];
|
|
17
|
+
end_pos: [number, number];
|
|
18
|
+
} | null;
|
|
19
|
+
/** A single curve (one stroke color) made of consecutive {@link SplineSegment}s. */
|
|
20
|
+
export interface SplineEquation {
|
|
21
|
+
color_rgb: SplineColor;
|
|
22
|
+
data: SplineSegment[];
|
|
23
|
+
}
|
|
24
|
+
/** The native vectomancy `Spline` document. */
|
|
25
|
+
export interface SplineDocument {
|
|
26
|
+
type: 'Spline' | 'Polyline';
|
|
27
|
+
equations?: SplineEquation[];
|
|
28
|
+
paths?: {
|
|
29
|
+
color_rgb: SplineColor;
|
|
30
|
+
data: {
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
}[];
|
|
34
|
+
}[];
|
|
35
|
+
bounding_box?: [number, number, number, number];
|
|
36
|
+
}
|
|
37
|
+
/** Construction options for {@link SplineEntity}. */
|
|
38
|
+
export interface SplineOptions {
|
|
39
|
+
/** Stroke width in local units. Default `2`. */
|
|
40
|
+
lineWidth?: number;
|
|
41
|
+
/** Bake to an OffscreenCanvas once and `drawImage` each frame. Default `true`. */
|
|
42
|
+
cache?: boolean;
|
|
43
|
+
/** Color used when an equation's `color_rgb` is `null`. Default `#e2e8f0`. */
|
|
44
|
+
defaultColor?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Hit-test strategy:
|
|
47
|
+
* - `'curve'` (default): precise — a point hits only within `lineWidth/2 +
|
|
48
|
+
* hitTolerance` of an actual curve.
|
|
49
|
+
* - `'aabb'`: coarse — anywhere in the bounding box hits.
|
|
50
|
+
*/
|
|
51
|
+
hitTest?: 'curve' | 'aabb';
|
|
52
|
+
/** Extra pick padding (local units) added to `lineWidth/2` in `'curve'` mode. Default `0`. */
|
|
53
|
+
hitTolerance?: number;
|
|
54
|
+
}
|
|
55
|
+
/** Cubic Bézier control points produced from a {@link SplineSegment}. */
|
|
56
|
+
export interface BezierControlPoints {
|
|
57
|
+
x0: number;
|
|
58
|
+
y0: number;
|
|
59
|
+
cp1x: number;
|
|
60
|
+
cp1y: number;
|
|
61
|
+
cp2x: number;
|
|
62
|
+
cp2y: number;
|
|
63
|
+
x3: number;
|
|
64
|
+
y3: number;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Convert one cubic-polynomial segment to Bézier control points.
|
|
68
|
+
*
|
|
69
|
+
* For a coefficient vector `[a,b,c,d]` describing `f(t)=a+bt+ct²+dt³` on `t∈[0,1]`,
|
|
70
|
+
* the equivalent Bézier control values are `a`, `a+b/3`, `a+2b/3+c/3`, `a+b+c+d`.
|
|
71
|
+
* Applied independently to the x and y polynomials.
|
|
72
|
+
*
|
|
73
|
+
* @param seg - The polynomial segment.
|
|
74
|
+
* @returns The cubic Bézier control points.
|
|
75
|
+
*/
|
|
76
|
+
export declare function polySegmentToBezier(seg: SplineSegment): BezierControlPoints;
|
|
77
|
+
/**
|
|
78
|
+
* Renders a native vectomancy `Spline` document (piecewise-cubic curves) to canvas.
|
|
79
|
+
*
|
|
80
|
+
* Bounds come from the document's `bounding_box` (or are computed from segment
|
|
81
|
+
* endpoints), so the entity participates in {@link Scene} viewport culling. By
|
|
82
|
+
* default the curves are baked once into an `OffscreenCanvas` and blitted each
|
|
83
|
+
* frame; without `OffscreenCanvas` it strokes the Bézier paths per frame.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* const doc = await loadSpline('/ast/logo.json');
|
|
87
|
+
* scene.add(new SplineEntity(doc).setPosition(100, 100));
|
|
88
|
+
*/
|
|
89
|
+
export declare class SplineEntity extends Entity {
|
|
90
|
+
doc: SplineDocument;
|
|
91
|
+
lineWidth: number;
|
|
92
|
+
defaultColor: string;
|
|
93
|
+
hitTolerance: number;
|
|
94
|
+
private cache;
|
|
95
|
+
private hitMode;
|
|
96
|
+
private bounds;
|
|
97
|
+
private offscreen;
|
|
98
|
+
private baked;
|
|
99
|
+
/** Lazily-flattened polylines (one Float32Array of [x,y,...] per segment) for hit-testing. */
|
|
100
|
+
private polylines;
|
|
101
|
+
/**
|
|
102
|
+
* When `true`, the renderer draws a rounded-rect outline of the entity's
|
|
103
|
+
* local bounds after painting the curves. Useful for drag feedback and
|
|
104
|
+
* debugging hit areas. Defaults to `false`.
|
|
105
|
+
*/
|
|
106
|
+
showBounds: boolean;
|
|
107
|
+
constructor(doc: SplineDocument, opts?: SplineOptions);
|
|
108
|
+
private computeBounds;
|
|
109
|
+
/** @inheritdoc */
|
|
110
|
+
getBounds(): Bounds;
|
|
111
|
+
/**
|
|
112
|
+
* AABB hit-test against the document bounds in world space.
|
|
113
|
+
*
|
|
114
|
+
* Curve-accurate hit-testing can be layered on later via {@link hitTestCurve};
|
|
115
|
+
* this method already calls it as a refinement when it is overridden.
|
|
116
|
+
*/
|
|
117
|
+
isPointInside(globalX: number, globalY: number): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Curve-accurate refinement of {@link isPointInside}: hit only when the local
|
|
120
|
+
* point lies within `lineWidth/2 + hitTolerance` of an actual curve.
|
|
121
|
+
*
|
|
122
|
+
* Returns `null` in `hitTest: 'aabb'` mode (keep the bounding-box result).
|
|
123
|
+
* Curves are flattened to polylines once and cached. Override for custom logic.
|
|
124
|
+
*
|
|
125
|
+
* @param localX - X in the entity's local space.
|
|
126
|
+
* @param localY - Y in the entity's local space.
|
|
127
|
+
* @returns `true`/`false`, or `null` to keep the AABB result.
|
|
128
|
+
*/
|
|
129
|
+
protected hitTestCurve(localX: number, localY: number): boolean | null;
|
|
130
|
+
/** Flatten every Bézier segment into a sampled polyline once, then cache. */
|
|
131
|
+
private getPolylines;
|
|
132
|
+
private resolveColor;
|
|
133
|
+
private strokeEquations;
|
|
134
|
+
/** Bake all equations into an OffscreenCanvas once (when available). */
|
|
135
|
+
private bake;
|
|
136
|
+
render(r: IRenderer): void;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Fetch and parse a vectomancy `Spline` JSON document (browser only).
|
|
140
|
+
*
|
|
141
|
+
* @param url - URL of the `.json` spline document.
|
|
142
|
+
* @returns The parsed {@link SplineDocument}.
|
|
143
|
+
*/
|
|
144
|
+
export declare function loadSpline(url: string): Promise<SplineDocument>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Entity } from '../tree/Entity';
|
|
2
|
+
import { IRenderer } from '../renderer/IRenderer';
|
|
3
|
+
export declare class TextEntity extends Entity {
|
|
4
|
+
text: string;
|
|
5
|
+
private atlas;
|
|
6
|
+
private layout;
|
|
7
|
+
private prepared;
|
|
8
|
+
private nodes;
|
|
9
|
+
fontSize: number;
|
|
10
|
+
fillStyle: string | any;
|
|
11
|
+
strokeStyle: string | any;
|
|
12
|
+
hoveredFillStyle: string | any;
|
|
13
|
+
lineWidth: number;
|
|
14
|
+
private isHovered;
|
|
15
|
+
constructor(text: string, atlas: any, maxWidth: number, fontSize?: number);
|
|
16
|
+
/**
|
|
17
|
+
* Replace the text content. Runs the **cold** measurement pass (re-segment +
|
|
18
|
+
* re-measure) since the glyphs changed, then re-lays out.
|
|
19
|
+
*
|
|
20
|
+
* @returns `this` for chaining.
|
|
21
|
+
*/
|
|
22
|
+
setText(text: string): this;
|
|
23
|
+
/**
|
|
24
|
+
* Change the wrap width and reflow. Cheap **hot** path only — reuses the
|
|
25
|
+
* cached {@link PreparedText}, doing no re-segmentation or re-measurement.
|
|
26
|
+
* Ideal for responsive resize.
|
|
27
|
+
*
|
|
28
|
+
* @returns `this` for chaining.
|
|
29
|
+
*/
|
|
30
|
+
setMaxWidth(maxWidth: number): this;
|
|
31
|
+
/** Hot pass: place the cached {@link PreparedText} and refresh the a11y box. */
|
|
32
|
+
private applyLayout;
|
|
33
|
+
isPointInside(globalX: number, globalY: number): boolean;
|
|
34
|
+
render(renderer: IRenderer): void;
|
|
35
|
+
}
|