@laplace.live/persona-sdk 1.20.0 → 1.21.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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/values/curve.d.ts +38 -5
- package/dist/values/curve.js +162 -4
- package/dist/values/effect-schema.d.ts +1 -0
- package/dist/values/effect-schema.js +16 -0
- package/dist/values/limits.d.ts +15 -0
- package/dist/values/limits.js +9 -0
- package/dist/values/time.d.ts +9 -0
- package/dist/values/time.js +11 -0
- package/dist/wire/schemas/settings.d.ts +5 -0
- package/dist/wire/schemas/settings.js +11 -3
- package/dist/wire/types.d.ts +30 -10
- package/dist/wire/types.js +9 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './values/mouse.ts';
|
|
|
22
22
|
export * from './values/scene-transition.ts';
|
|
23
23
|
export * from './values/stage-info.ts';
|
|
24
24
|
export * from './values/terms.ts';
|
|
25
|
+
export * from './values/time.ts';
|
|
25
26
|
export * from './values/volumetric-lighting.ts';
|
|
26
27
|
export * from './values/vrm-bindings.ts';
|
|
27
28
|
export * from './wire/envelope.ts';
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ export * from "./values/mouse.js";
|
|
|
24
24
|
export * from "./values/scene-transition.js";
|
|
25
25
|
export * from "./values/stage-info.js";
|
|
26
26
|
export * from "./values/terms.js";
|
|
27
|
+
export * from "./values/time.js";
|
|
27
28
|
export * from "./values/volumetric-lighting.js";
|
|
28
29
|
export * from "./values/vrm-bindings.js";
|
|
29
30
|
export * from "./wire/envelope.js";
|
package/dist/values/curve.d.ts
CHANGED
|
@@ -18,8 +18,8 @@ export interface Keyframe {
|
|
|
18
18
|
anchor: Point;
|
|
19
19
|
/**
|
|
20
20
|
* Bezier handles. `x` is a **fraction of the segment's x-span** (0 = this segment's left
|
|
21
|
-
* anchor, 1 = its right), `y` is absolute — nizima's
|
|
22
|
-
* anchor
|
|
21
|
+
* anchor, 1 = its right), `y` is absolute — nizima's format. Because `y` stays put when its
|
|
22
|
+
* anchor moves, an editor drag has to carry the handles itself (`dragAnchor`), or the point kinks.
|
|
23
23
|
*/
|
|
24
24
|
next?: Point;
|
|
25
25
|
previous?: Point;
|
|
@@ -61,8 +61,9 @@ export interface CurveHandle extends Point {
|
|
|
61
61
|
span: readonly [number, number];
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
* The keyframe's
|
|
65
|
-
*
|
|
64
|
+
* The keyframe's handles, in curve coordinates — its outgoing one when it governs a Bezier
|
|
65
|
+
* segment, its incoming one when the keyframe before it does. A sharp point's are retracted onto
|
|
66
|
+
* it and still reported; `extendedHandles` is what an editor draws.
|
|
66
67
|
*
|
|
67
68
|
* `handleX` rather than a second clamp-and-lerp: a handle has to read back where the evaluator
|
|
68
69
|
* looks for it. `y` is absolute and deliberately unclamped — `sanitizeCurve` keeps a control point
|
|
@@ -70,6 +71,8 @@ export interface CurveHandle extends Point {
|
|
|
70
71
|
* legitimate overshoot at the wrong height.
|
|
71
72
|
*/
|
|
72
73
|
export declare function handlesFor(keyframes: Keyframe[], index: number): CurveHandle[];
|
|
74
|
+
/** The keyframe's handles an editor draws and lets you grab: a retracted one has nothing to grab. */
|
|
75
|
+
export declare function extendedHandles(keyframes: Keyframe[], index: number): CurveHandle[];
|
|
73
76
|
/**
|
|
74
77
|
* How far an anchor may travel in x, as `[lo, hi]`.
|
|
75
78
|
*
|
|
@@ -81,6 +84,31 @@ export declare function handlesFor(keyframes: Keyframe[], index: number): CurveH
|
|
|
81
84
|
* One statement of the rule, so an editor's drag and its typed field cannot drift apart.
|
|
82
85
|
*/
|
|
83
86
|
export declare function anchorBounds(keyframes: Keyframe[], index: number): [number, number];
|
|
87
|
+
/**
|
|
88
|
+
* The anchor at `index` moved to `to`, held within `anchorBounds` and the plot, carrying its own
|
|
89
|
+
* handles as a pen tool does, so the curve leaves and arrives at it in the same directions: a
|
|
90
|
+
* smooth point stays smooth, a corner keeps its angle. The neighbours' handles keep their
|
|
91
|
+
* fractions, so the segments either side stretch rather than re-aim.
|
|
92
|
+
*/
|
|
93
|
+
export declare function dragAnchor(keyframes: Keyframe[], index: number, to: Point): Keyframe[];
|
|
94
|
+
/**
|
|
95
|
+
* The `side` handle of the anchor at `index` moved to `to`. On a smooth point the other handle
|
|
96
|
+
* turns to stay in line, keeping its length — a pen tool's default — unless `independent`, which
|
|
97
|
+
* moves this one alone and leaves a corner. `to` is held within the segment the handle shapes,
|
|
98
|
+
* where `handleX` reads it, and on the plot, where it can be found again; a stored overshoot still
|
|
99
|
+
* reads back. The turned handle is shortened to fit its own.
|
|
100
|
+
*/
|
|
101
|
+
export declare function dragHandle(keyframes: Keyframe[], index: number, side: HandleSide, to: Point, independent: boolean): Keyframe[];
|
|
102
|
+
/**
|
|
103
|
+
* A pen tool's convert-point click on the interior anchor at `index`: a smooth point turns sharp,
|
|
104
|
+
* anything else turns smooth. Beside a step there is nothing to be in line with, so a point there is
|
|
105
|
+
* smooth while its one handle is out. Sharp retracts the handles onto the anchor, so no curve bends
|
|
106
|
+
* at it. Smooth aims them through the neighbours — flat at a peak or valley, so the curve cannot
|
|
107
|
+
* overshoot the neighbour it turns at — a third of the way along each segment, and a linear side
|
|
108
|
+
* becomes Bezier with its far handle on the old line, so the neighbour's end keeps its direction.
|
|
109
|
+
* Step sides are left as they are. Returns **the input array itself** when it declines.
|
|
110
|
+
*/
|
|
111
|
+
export declare function toggleSmooth(keyframes: Keyframe[], index: number): Keyframe[];
|
|
84
112
|
/**
|
|
85
113
|
* Every Bezier segment carries both handles, seeded collinear unless it already has both.
|
|
86
114
|
*
|
|
@@ -146,13 +174,18 @@ export declare const INTERPOLATIONS: readonly ["linear", "step", "invertStep", "
|
|
|
146
174
|
*/
|
|
147
175
|
export declare function sanitizeCurve(v: unknown): Curve | null;
|
|
148
176
|
/**
|
|
149
|
-
* The straight 0..1 line — what a binding without a curve already does
|
|
177
|
+
* The straight 0..1 line — what a binding without a curve already does — as the Linear preset.
|
|
150
178
|
*
|
|
151
179
|
* No handles: a `linear` keyframe's are never read, and one that outlives the segment it was cut
|
|
152
180
|
* for is what bends a curve on a mode switch. `withBezierHandles` cuts fresh ones against whatever
|
|
153
181
|
* the anchors are by then, so Bezier leaves the line where it is and only a drag shapes it.
|
|
154
182
|
*/
|
|
155
183
|
export declare function identityCurve(): Curve;
|
|
184
|
+
/**
|
|
185
|
+
* The same straight line as one Bezier segment, handles on the line at the thirds — what a new
|
|
186
|
+
* curve starts as, so a point clicked into it splits smooth and a drag bends it rather than kinking.
|
|
187
|
+
*/
|
|
188
|
+
export declare function bezierIdentityCurve(): Curve;
|
|
156
189
|
/** A ready-made response shape that replaces the whole curve; `linear` is the identity. */
|
|
157
190
|
export type CurvePresetId = (typeof CURVE_PRESET_IDS)[number];
|
|
158
191
|
export declare const CURVE_PRESET_IDS: readonly ["linear", "step", "invertStep", "bezier", "easeIn", "easeOut", "sCurve", "threshold", "steps"];
|
package/dist/values/curve.js
CHANGED
|
@@ -92,8 +92,9 @@ export function evaluateCurve(curve, x) {
|
|
|
92
92
|
return left === undefined || right === undefined ? first.anchor.y : segmentAt(left, right, x);
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
95
|
-
* The keyframe's
|
|
96
|
-
*
|
|
95
|
+
* The keyframe's handles, in curve coordinates — its outgoing one when it governs a Bezier
|
|
96
|
+
* segment, its incoming one when the keyframe before it does. A sharp point's are retracted onto
|
|
97
|
+
* it and still reported; `extendedHandles` is what an editor draws.
|
|
97
98
|
*
|
|
98
99
|
* `handleX` rather than a second clamp-and-lerp: a handle has to read back where the evaluator
|
|
99
100
|
* looks for it. `y` is absolute and deliberately unclamped — `sanitizeCurve` keeps a control point
|
|
@@ -117,6 +118,15 @@ export function handlesFor(keyframes, index) {
|
|
|
117
118
|
}
|
|
118
119
|
return out;
|
|
119
120
|
}
|
|
121
|
+
/** Below this length a handle is retracted onto its anchor, as a sharp point's are. */
|
|
122
|
+
const RETRACTED = 1e-9;
|
|
123
|
+
/** The keyframe's handles an editor draws and lets you grab: a retracted one has nothing to grab. */
|
|
124
|
+
export function extendedHandles(keyframes, index) {
|
|
125
|
+
const anchor = keyframes[index]?.anchor;
|
|
126
|
+
if (anchor === undefined)
|
|
127
|
+
return [];
|
|
128
|
+
return handlesFor(keyframes, index).filter(h => Math.hypot(h.x - anchor.x, h.y - anchor.y) > RETRACTED);
|
|
129
|
+
}
|
|
120
130
|
/**
|
|
121
131
|
* How far an anchor may travel in x, as `[lo, hi]`.
|
|
122
132
|
*
|
|
@@ -133,7 +143,143 @@ export function anchorBounds(keyframes, index) {
|
|
|
133
143
|
return [0, 1];
|
|
134
144
|
if (index === 0 || index === keyframes.length - 1)
|
|
135
145
|
return [held.anchor.x, held.anchor.x];
|
|
136
|
-
|
|
146
|
+
const lo = keyframes[index - 1]?.anchor.x ?? 0;
|
|
147
|
+
const hi = keyframes[index + 1]?.anchor.x ?? 1;
|
|
148
|
+
// Clicks in one column stack anchors on the curve; neighbours too close for the margin meet halfway.
|
|
149
|
+
const edge = Math.min(EDGE_EPSILON, (hi - lo) / 2);
|
|
150
|
+
return [lo + edge, hi - edge];
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The handle at `anchor + s·offset` for the largest `s` in 0..1 inside the box, stored against
|
|
154
|
+
* `xs`, its segment's span: a handle out of room is shortened along its own direction, so a
|
|
155
|
+
* smooth point stays smooth.
|
|
156
|
+
*/
|
|
157
|
+
function fitHandle(anchor, offset, xs, ys) {
|
|
158
|
+
let s = 1;
|
|
159
|
+
if (offset.x > 0)
|
|
160
|
+
s = Math.min(s, (xs[1] - anchor.x) / offset.x);
|
|
161
|
+
if (offset.x < 0)
|
|
162
|
+
s = Math.min(s, (xs[0] - anchor.x) / offset.x);
|
|
163
|
+
if (offset.y > 0)
|
|
164
|
+
s = Math.min(s, (ys[1] - anchor.y) / offset.y);
|
|
165
|
+
if (offset.y < 0)
|
|
166
|
+
s = Math.min(s, (ys[0] - anchor.y) / offset.y);
|
|
167
|
+
s = Math.max(0, s);
|
|
168
|
+
return { x: handleFraction(xs[0], xs[1], anchor.x + offset.x * s), y: anchor.y + offset.y * s };
|
|
169
|
+
}
|
|
170
|
+
/** Where an authored handle may sit in y: on the plot, or no further out than a stored overshoot. */
|
|
171
|
+
function heightsFor(y) {
|
|
172
|
+
return [Math.min(0, y), Math.max(1, y)];
|
|
173
|
+
}
|
|
174
|
+
/** Two handles in line through their anchor, on opposite sides of it: a smooth point. */
|
|
175
|
+
function inLine(anchor, a, b) {
|
|
176
|
+
const u = { x: a.x - anchor.x, y: a.y - anchor.y };
|
|
177
|
+
const v = { x: b.x - anchor.x, y: b.y - anchor.y };
|
|
178
|
+
const lu = Math.hypot(u.x, u.y);
|
|
179
|
+
const lv = Math.hypot(v.x, v.y);
|
|
180
|
+
if (lu <= RETRACTED || lv <= RETRACTED)
|
|
181
|
+
return false;
|
|
182
|
+
return u.x * v.x + u.y * v.y < 0 && Math.abs(u.x * v.y - u.y * v.x) <= 1e-3 * lu * lv;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The anchor at `index` moved to `to`, held within `anchorBounds` and the plot, carrying its own
|
|
186
|
+
* handles as a pen tool does, so the curve leaves and arrives at it in the same directions: a
|
|
187
|
+
* smooth point stays smooth, a corner keeps its angle. The neighbours' handles keep their
|
|
188
|
+
* fractions, so the segments either side stretch rather than re-aim.
|
|
189
|
+
*/
|
|
190
|
+
export function dragAnchor(keyframes, index, to) {
|
|
191
|
+
const held = keyframes[index];
|
|
192
|
+
if (held === undefined)
|
|
193
|
+
return keyframes;
|
|
194
|
+
const [lo, hi] = anchorBounds(keyframes, index);
|
|
195
|
+
const at = { x: clamp(to.x, lo, hi), y: clamp(to.y, 0, 1) };
|
|
196
|
+
const moved = { ...held, anchor: at };
|
|
197
|
+
for (const h of handlesFor(keyframes, index)) {
|
|
198
|
+
const span = h.side === 'next' ? [at.x, h.span[1]] : [h.span[0], at.x];
|
|
199
|
+
moved[h.side] = fitHandle(at, { x: h.x - held.anchor.x, y: h.y - held.anchor.y }, span, heightsFor(h.y));
|
|
200
|
+
}
|
|
201
|
+
return keyframes.map((k, i) => (i === index ? moved : k));
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* The `side` handle of the anchor at `index` moved to `to`. On a smooth point the other handle
|
|
205
|
+
* turns to stay in line, keeping its length — a pen tool's default — unless `independent`, which
|
|
206
|
+
* moves this one alone and leaves a corner. `to` is held within the segment the handle shapes,
|
|
207
|
+
* where `handleX` reads it, and on the plot, where it can be found again; a stored overshoot still
|
|
208
|
+
* reads back. The turned handle is shortened to fit its own.
|
|
209
|
+
*/
|
|
210
|
+
export function dragHandle(keyframes, index, side, to, independent) {
|
|
211
|
+
const held = keyframes[index];
|
|
212
|
+
const handles = handlesFor(keyframes, index);
|
|
213
|
+
const dragged = handles.find(h => h.side === side);
|
|
214
|
+
if (held === undefined || dragged === undefined)
|
|
215
|
+
return keyframes;
|
|
216
|
+
const at = { x: clamp(to.x, dragged.span[0], dragged.span[1]), y: clamp(to.y, 0, 1) };
|
|
217
|
+
const moved = { ...held };
|
|
218
|
+
moved[side] = { x: handleFraction(dragged.span[0], dragged.span[1], at.x), y: at.y };
|
|
219
|
+
const other = handles.find(h => h.side !== side);
|
|
220
|
+
const a = held.anchor;
|
|
221
|
+
const reach = Math.hypot(at.x - a.x, at.y - a.y);
|
|
222
|
+
if (!independent && other !== undefined && reach > 0 && inLine(a, dragged, other)) {
|
|
223
|
+
const keep = Math.hypot(other.x - a.x, other.y - a.y) / reach;
|
|
224
|
+
const away = { x: (a.x - at.x) * keep, y: (a.y - at.y) * keep };
|
|
225
|
+
moved[other.side] = fitHandle(a, away, other.span, heightsFor(other.y));
|
|
226
|
+
}
|
|
227
|
+
return keyframes.map((k, i) => (i === index ? moved : k));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* A pen tool's convert-point click on the interior anchor at `index`: a smooth point turns sharp,
|
|
231
|
+
* anything else turns smooth. Beside a step there is nothing to be in line with, so a point there is
|
|
232
|
+
* smooth while its one handle is out. Sharp retracts the handles onto the anchor, so no curve bends
|
|
233
|
+
* at it. Smooth aims them through the neighbours — flat at a peak or valley, so the curve cannot
|
|
234
|
+
* overshoot the neighbour it turns at — a third of the way along each segment, and a linear side
|
|
235
|
+
* becomes Bezier with its far handle on the old line, so the neighbour's end keeps its direction.
|
|
236
|
+
* Step sides are left as they are. Returns **the input array itself** when it declines.
|
|
237
|
+
*/
|
|
238
|
+
export function toggleSmooth(keyframes, index) {
|
|
239
|
+
const before = keyframes[index - 1];
|
|
240
|
+
const held = keyframes[index];
|
|
241
|
+
const after = keyframes[index + 1];
|
|
242
|
+
if (before === undefined || held === undefined || after === undefined)
|
|
243
|
+
return keyframes;
|
|
244
|
+
// A step side has no curve to aim; a linear one turns Bezier below.
|
|
245
|
+
const smoothIn = before.interpolation === 'linear' || before.interpolation === 'bezier';
|
|
246
|
+
const smoothOut = held.interpolation === 'linear' || held.interpolation === 'bezier';
|
|
247
|
+
if (!smoothIn && !smoothOut)
|
|
248
|
+
return keyframes;
|
|
249
|
+
const a = held.anchor;
|
|
250
|
+
const handles = handlesFor(keyframes, index);
|
|
251
|
+
const incoming = handles.find(h => h.side === 'previous');
|
|
252
|
+
const outgoing = handles.find(h => h.side === 'next');
|
|
253
|
+
const out = [...keyframes];
|
|
254
|
+
const isSmooth = smoothIn && smoothOut
|
|
255
|
+
? incoming !== undefined && outgoing !== undefined && inLine(a, incoming, outgoing)
|
|
256
|
+
: extendedHandles(keyframes, index).length > 0;
|
|
257
|
+
if (isSmooth) {
|
|
258
|
+
const sharp = { ...held };
|
|
259
|
+
if (incoming !== undefined)
|
|
260
|
+
sharp.previous = { x: 1, y: a.y };
|
|
261
|
+
if (outgoing !== undefined)
|
|
262
|
+
sharp.next = { x: 0, y: a.y };
|
|
263
|
+
out[index] = sharp;
|
|
264
|
+
return out;
|
|
265
|
+
}
|
|
266
|
+
const l = before.anchor;
|
|
267
|
+
const r = after.anchor;
|
|
268
|
+
const slope = (a.y - l.y) * (r.y - a.y) > 0 ? (r.y - l.y) / (r.x - l.x) : 0;
|
|
269
|
+
const smooth = { ...held };
|
|
270
|
+
if (before.interpolation === 'linear') {
|
|
271
|
+
out[index - 1] = { ...before, interpolation: 'bezier', next: { x: 1 / 3, y: lerp(l.y, a.y, 1 / 3) } };
|
|
272
|
+
}
|
|
273
|
+
if (smoothIn)
|
|
274
|
+
smooth.previous = fitHandle(a, { x: (l.x - a.x) / 3, y: (slope * (l.x - a.x)) / 3 }, [l.x, a.x], [0, 1]);
|
|
275
|
+
if (held.interpolation === 'linear') {
|
|
276
|
+
smooth.interpolation = 'bezier';
|
|
277
|
+
out[index + 1] = { ...after, previous: { x: 2 / 3, y: lerp(a.y, r.y, 2 / 3) } };
|
|
278
|
+
}
|
|
279
|
+
if (smoothOut)
|
|
280
|
+
smooth.next = fitHandle(a, { x: (r.x - a.x) / 3, y: (slope * (r.x - a.x)) / 3 }, [a.x, r.x], [0, 1]);
|
|
281
|
+
out[index] = smooth;
|
|
282
|
+
return out;
|
|
137
283
|
}
|
|
138
284
|
/**
|
|
139
285
|
* Every Bezier segment carries both handles, seeded collinear unless it already has both.
|
|
@@ -308,7 +454,7 @@ export function sanitizeCurve(v) {
|
|
|
308
454
|
return { keyframes: withBezierHandles(keyframes) };
|
|
309
455
|
}
|
|
310
456
|
/**
|
|
311
|
-
* The straight 0..1 line — what a binding without a curve already does
|
|
457
|
+
* The straight 0..1 line — what a binding without a curve already does — as the Linear preset.
|
|
312
458
|
*
|
|
313
459
|
* No handles: a `linear` keyframe's are never read, and one that outlives the segment it was cut
|
|
314
460
|
* for is what bends a curve on a mode switch. `withBezierHandles` cuts fresh ones against whatever
|
|
@@ -322,6 +468,18 @@ export function identityCurve() {
|
|
|
322
468
|
],
|
|
323
469
|
};
|
|
324
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* The same straight line as one Bezier segment, handles on the line at the thirds — what a new
|
|
473
|
+
* curve starts as, so a point clicked into it splits smooth and a drag bends it rather than kinking.
|
|
474
|
+
*/
|
|
475
|
+
export function bezierIdentityCurve() {
|
|
476
|
+
return {
|
|
477
|
+
keyframes: withBezierHandles([
|
|
478
|
+
{ anchor: { x: 0, y: 0 }, interpolation: 'bezier' },
|
|
479
|
+
{ anchor: { x: 1, y: 1 }, interpolation: 'linear' },
|
|
480
|
+
]),
|
|
481
|
+
};
|
|
482
|
+
}
|
|
325
483
|
export const CURVE_PRESET_IDS = [
|
|
326
484
|
'linear',
|
|
327
485
|
'step',
|
|
@@ -11,6 +11,7 @@ export declare const EFFECT_SCOPES: {
|
|
|
11
11
|
readonly blur: readonly ["scene", "layer"];
|
|
12
12
|
readonly bloom: readonly ["scene", "layer"];
|
|
13
13
|
readonly diffusion: readonly ["scene", "layer"];
|
|
14
|
+
readonly sketch: readonly ["scene", "layer"];
|
|
14
15
|
readonly dof: readonly ["scene"];
|
|
15
16
|
readonly chromaticAberration: readonly ["scene"];
|
|
16
17
|
readonly grain: readonly ["scene"];
|
|
@@ -13,6 +13,7 @@ export const EFFECT_SCOPES = {
|
|
|
13
13
|
blur: ['scene', 'layer'],
|
|
14
14
|
bloom: ['scene', 'layer'],
|
|
15
15
|
diffusion: ['scene', 'layer'],
|
|
16
|
+
sketch: ['scene', 'layer'],
|
|
16
17
|
dof: ['scene'],
|
|
17
18
|
chromaticAberration: ['scene'],
|
|
18
19
|
grain: ['scene'],
|
|
@@ -165,6 +166,15 @@ export const EFFECT_SPECS = {
|
|
|
165
166
|
radius: { default: 0.85, min: 0, max: 1, step: 0.01 },
|
|
166
167
|
threshold: { default: 0.35, min: 0, max: 1, step: 0.01 },
|
|
167
168
|
},
|
|
169
|
+
sketch: {
|
|
170
|
+
lines: { default: 1, min: 0, max: 2, step: 0.01 },
|
|
171
|
+
lineWidth: { default: 1, min: 0.5, max: 4, step: 0.1, digits: 1, unit: 'px' },
|
|
172
|
+
// 1 is the source's density; above it the grain packs toward solid graphite.
|
|
173
|
+
shading: { default: 1, min: 0, max: 2, step: 0.01 },
|
|
174
|
+
softness: { default: 1, min: 0, max: 1, step: 0.01 },
|
|
175
|
+
saturation: { default: 0, min: 0, max: 1, step: 0.01 },
|
|
176
|
+
opacity: { default: 1, min: 0, max: 1, step: 0.01 },
|
|
177
|
+
},
|
|
168
178
|
dof: {
|
|
169
179
|
bokehScale: { default: 2, min: 0, max: 8, step: 0.01 },
|
|
170
180
|
/** World metres of acceptably-sharp depth around the focus plane. */
|
|
@@ -303,6 +313,10 @@ export const EFFECT_COLOR_SPECS = {
|
|
|
303
313
|
outline: {
|
|
304
314
|
color: { default: '#000000' },
|
|
305
315
|
},
|
|
316
|
+
// The mean of the source demo's kraft paper photo.
|
|
317
|
+
sketch: {
|
|
318
|
+
paperColor: { default: '#b69b83' },
|
|
319
|
+
},
|
|
306
320
|
dropShadow: {
|
|
307
321
|
color: { default: '#000000' },
|
|
308
322
|
},
|
|
@@ -315,6 +329,7 @@ export const EFFECT_BOOLEAN_SPECS = {
|
|
|
315
329
|
transparentBackground: { default: false },
|
|
316
330
|
},
|
|
317
331
|
blur: { highQuality: { default: false } },
|
|
332
|
+
sketch: { paperBackground: { default: false } },
|
|
318
333
|
bloom: {
|
|
319
334
|
selectColors: { default: false },
|
|
320
335
|
invertColors: { default: false },
|
|
@@ -435,6 +450,7 @@ export const ALL_EFFECT_STACK_ORDER = [
|
|
|
435
450
|
'chromaticAberration',
|
|
436
451
|
'bloom',
|
|
437
452
|
'diffusion',
|
|
453
|
+
'sketch',
|
|
438
454
|
'color',
|
|
439
455
|
'levels',
|
|
440
456
|
'colorWheels',
|
package/dist/values/limits.d.ts
CHANGED
|
@@ -212,6 +212,19 @@ export declare function pinnableParents(models: readonly {
|
|
|
212
212
|
name: string;
|
|
213
213
|
ridesThis: boolean;
|
|
214
214
|
}[];
|
|
215
|
+
/**
|
|
216
|
+
* Whether pinning puts an item inside its Live2D host's ArtMesh stack, where `attach.depth` places
|
|
217
|
+
* it: a Live2D item, or a 2D object as one quad. A 3D object only ever paints in front.
|
|
218
|
+
*/
|
|
219
|
+
export declare function pinEntersStack(item: {
|
|
220
|
+
kind: 'model';
|
|
221
|
+
ref: {
|
|
222
|
+
kind: ModelFormat;
|
|
223
|
+
};
|
|
224
|
+
} | {
|
|
225
|
+
kind: 'object';
|
|
226
|
+
space: ObjectSpace;
|
|
227
|
+
}): boolean;
|
|
215
228
|
/**
|
|
216
229
|
* The depth control's stops, back to front: behind the model, above each of its ArtMeshes in
|
|
217
230
|
* paint order, then in front of it all. One continuous run, so depth is a slider over the host's
|
|
@@ -254,5 +267,7 @@ export declare const OUTLINER_DEFAULT_WIDTH = 288;
|
|
|
254
267
|
* split must fit inside it, or no layout satisfies both floors and the group clips instead.
|
|
255
268
|
*/
|
|
256
269
|
export declare const TWO_COLUMN_WIDTH = 672;
|
|
270
|
+
/** Colors a color picker keeps saved — two rows of its swatch grid; saving more waits for a removal. */
|
|
271
|
+
export declare const SAVED_COLORS_MAX = 12;
|
|
257
272
|
/** 3D-stage resolution multipliers offered by both performance pickers. */
|
|
258
273
|
export declare const RENDER_SCALE_PRESETS: readonly [1, 0.85, 0.75, 0.66, 0.5];
|
package/dist/values/limits.js
CHANGED
|
@@ -343,6 +343,13 @@ export function pinnableParents(models, items, instanceId) {
|
|
|
343
343
|
}
|
|
344
344
|
return out;
|
|
345
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Whether pinning puts an item inside its Live2D host's ArtMesh stack, where `attach.depth` places
|
|
348
|
+
* it: a Live2D item, or a 2D object as one quad. A 3D object only ever paints in front.
|
|
349
|
+
*/
|
|
350
|
+
export function pinEntersStack(item) {
|
|
351
|
+
return item.kind === 'model' ? item.ref.kind === 'live2d' : item.space === '2d';
|
|
352
|
+
}
|
|
346
353
|
/**
|
|
347
354
|
* The depth control's stops, back to front: behind the model, above each of its ArtMeshes in
|
|
348
355
|
* paint order, then in front of it all. One continuous run, so depth is a slider over the host's
|
|
@@ -411,5 +418,7 @@ export const OUTLINER_DEFAULT_WIDTH = 288;
|
|
|
411
418
|
* split must fit inside it, or no layout satisfies both floors and the group clips instead.
|
|
412
419
|
*/
|
|
413
420
|
export const TWO_COLUMN_WIDTH = 672;
|
|
421
|
+
/** Colors a color picker keeps saved — two rows of its swatch grid; saving more waits for a removal. */
|
|
422
|
+
export const SAVED_COLORS_MAX = 12;
|
|
414
423
|
/** 3D-stage resolution multipliers offered by both performance pickers. */
|
|
415
424
|
export const RENDER_SCALE_PRESETS = [1, 0.85, 0.75, 0.66, 0.5];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Desktop local wall-clock time, normalized over a repeating 24-hour or 12-hour cycle. */
|
|
2
|
+
export declare const TIME_INPUT_RANGES: {
|
|
3
|
+
readonly Time24h: readonly [0, 1];
|
|
4
|
+
readonly Time12h: readonly [0, 1];
|
|
5
|
+
};
|
|
6
|
+
export type TimeInputName = keyof typeof TIME_INPUT_RANGES;
|
|
7
|
+
export type TimeInputs = Readonly<Record<TimeInputName, number>>;
|
|
8
|
+
export declare const TIME_INPUT_NAMES: readonly TimeInputName[];
|
|
9
|
+
export declare function isTimeInputName(v: string): v is TimeInputName;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { keysOf } from "./guards.js";
|
|
2
|
+
/** Desktop local wall-clock time, normalized over a repeating 24-hour or 12-hour cycle. */
|
|
3
|
+
export const TIME_INPUT_RANGES = {
|
|
4
|
+
Time24h: [0, 1],
|
|
5
|
+
Time12h: [0, 1],
|
|
6
|
+
};
|
|
7
|
+
export const TIME_INPUT_NAMES = keysOf(TIME_INPUT_RANGES);
|
|
8
|
+
const TIME_INPUT_SET = new Set(TIME_INPUT_NAMES);
|
|
9
|
+
export function isTimeInputName(v) {
|
|
10
|
+
return TIME_INPUT_SET.has(v);
|
|
11
|
+
}
|
|
@@ -180,9 +180,14 @@ export declare const SettingsSchema: z.ZodObject<{
|
|
|
180
180
|
width: z.ZodNumber;
|
|
181
181
|
height: z.ZodNumber;
|
|
182
182
|
}, z.core.$strip>>;
|
|
183
|
+
stageReferenceSize: z.ZodOptional<z.ZodObject<{
|
|
184
|
+
width: z.ZodNumber;
|
|
185
|
+
height: z.ZodNumber;
|
|
186
|
+
}, z.core.$strip>>;
|
|
183
187
|
}, z.core.$strip>;
|
|
184
188
|
ui: z.ZodObject<{
|
|
185
189
|
trayVisible: z.ZodBoolean;
|
|
190
|
+
streamMode: z.ZodOptional<z.ZodBoolean>;
|
|
186
191
|
}, z.core.$strip>;
|
|
187
192
|
performance: z.ZodObject<{
|
|
188
193
|
showFps: z.ZodBoolean;
|
|
@@ -99,11 +99,15 @@ export const SettingsSchema = z.object({
|
|
|
99
99
|
window: z
|
|
100
100
|
.object({
|
|
101
101
|
alwaysOnTop: z.boolean().describe('Keep the control-panel window above other windows.'),
|
|
102
|
-
stageSize: StageSizeSchema.optional().describe('Read-only stage content size; absent on older hosts.'),
|
|
102
|
+
stageSize: StageSizeSchema.optional().describe('Read-only stage window content size; absent on older hosts.'),
|
|
103
|
+
stageReferenceSize: StageSizeSchema.optional().describe('Read-only initial stage size calibrating placement units and model fit; not the output aspect.'),
|
|
103
104
|
})
|
|
104
105
|
.describe('Public window configuration and read-only stage dimensions.'),
|
|
105
106
|
ui: z
|
|
106
|
-
.object({
|
|
107
|
+
.object({
|
|
108
|
+
trayVisible: z.boolean().describe('Show the tray or menu bar icon.'),
|
|
109
|
+
streamMode: z.boolean().optional().describe('Read-only Stream Mode state; absent on older hosts.'),
|
|
110
|
+
})
|
|
107
111
|
.describe('Public interface preferences.'),
|
|
108
112
|
performance: PerformanceSettingsSchema.partial({ renderScale: true }).describe('Renderer settings; renderScale is absent on older hosts.'),
|
|
109
113
|
tracking: TrackingSettingsSchema.describe('Network face master switch and all configured tracking sources.'),
|
|
@@ -118,6 +122,10 @@ export const SettingsPatchSchema = z.object({
|
|
|
118
122
|
.partial()
|
|
119
123
|
.optional()
|
|
120
124
|
.describe('Control-panel window fields to update.'),
|
|
121
|
-
ui: SettingsSchema.shape.ui
|
|
125
|
+
ui: SettingsSchema.shape.ui
|
|
126
|
+
.pick({ trayVisible: true })
|
|
127
|
+
.partial()
|
|
128
|
+
.optional()
|
|
129
|
+
.describe('Public interface fields to update.'),
|
|
122
130
|
performance: PerformanceSettingsSchema.partial().optional().describe('Renderer fields to update.'),
|
|
123
131
|
});
|
package/dist/wire/types.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { type BaseControllerInputName, type ControllerInputName, type Controller
|
|
|
8
8
|
import { type HandInputName } from '../values/hands.ts';
|
|
9
9
|
import { type LipSyncMode, type VoiceInputName } from '../values/lipsync.ts';
|
|
10
10
|
import { type MouseInputName } from '../values/mouse.ts';
|
|
11
|
+
import { type TimeInputName } from '../values/time.ts';
|
|
11
12
|
export type ModelFormat = 'live2d' | 'vrm';
|
|
12
13
|
/** Where an item came from: shipped with the app, or added by the user. */
|
|
13
14
|
export type ContentOrigin = 'bundled' | 'user';
|
|
@@ -80,7 +81,7 @@ export interface CatalogItem extends Omit<ContentRef, 'origin'> {
|
|
|
80
81
|
/** Creator or content homepage (https). */
|
|
81
82
|
url?: string;
|
|
82
83
|
}
|
|
83
|
-
/** Screen-space placement:
|
|
84
|
+
/** Screen-space placement: calibrated stage units from the viewport centre, rotation in radians. */
|
|
84
85
|
export interface ScreenPlacement {
|
|
85
86
|
x: number;
|
|
86
87
|
y: number;
|
|
@@ -207,8 +208,6 @@ export interface SceneModelItem {
|
|
|
207
208
|
}
|
|
208
209
|
export declare const OBJECT_SPACES: readonly ["2d", "3d"];
|
|
209
210
|
export type ObjectSpace = (typeof OBJECT_SPACES)[number];
|
|
210
|
-
/** Where a webpage overlay renders relative to the stage. */
|
|
211
|
-
export type WebLayer = 'behind' | 'front';
|
|
212
211
|
export type CaptureKind = 'display' | 'window';
|
|
213
212
|
/** What an object renders. Mirrors VTube Studio's items and Warudo's screen/prop assets. */
|
|
214
213
|
export type ObjectContent = {
|
|
@@ -236,8 +235,6 @@ export type ObjectContent = {
|
|
|
236
235
|
transparent: boolean;
|
|
237
236
|
/** Stylesheet injected into every page the object loads; empty for none. */
|
|
238
237
|
css: string;
|
|
239
|
-
/** 2D only: composited behind or in front of the whole stage, not interleaved with other layers. */
|
|
240
|
-
layer: WebLayer;
|
|
241
238
|
/** Close the page while the object is hidden and reload it on show, as an OBS browser source can. */
|
|
242
239
|
shutdownWhenHidden: boolean;
|
|
243
240
|
} | {
|
|
@@ -661,6 +658,28 @@ export interface EffectBlur {
|
|
|
661
658
|
mode: 'gaussian' | 'bokeh';
|
|
662
659
|
highQuality: boolean;
|
|
663
660
|
}
|
|
661
|
+
/**
|
|
662
|
+
* Pencil sketch on paper (Jaume Sanchez's NPR sketch shader): graphite grain shades the darker
|
|
663
|
+
* tones and pencil lines trace color and coverage edges, over a procedural paper grain.
|
|
664
|
+
*/
|
|
665
|
+
export interface EffectSketch {
|
|
666
|
+
enabled: boolean;
|
|
667
|
+
/** Paper tint (hex) under the drawing. */
|
|
668
|
+
paperColor: string;
|
|
669
|
+
/** Scene only: paper also fills transparent pixels. Layers always keep their coverage. */
|
|
670
|
+
paperBackground: boolean;
|
|
671
|
+
/** Pencil-line darkness, 0–2. */
|
|
672
|
+
lines: number;
|
|
673
|
+
/** Pencil-line width in CSS pixels, 0.5–4, so it holds across display densities. */
|
|
674
|
+
lineWidth: number;
|
|
675
|
+
/** Graphite grain in the darker tones, 0–2: 0 leaves only the lines, 1 is the source's density. */
|
|
676
|
+
shading: number;
|
|
677
|
+
/** How strongly a wide blur of the surrounding tones lightens the shading, 0–1. */
|
|
678
|
+
softness: number;
|
|
679
|
+
/** 0 draws in graphite, 1 keeps the image's colors at the same lightness. */
|
|
680
|
+
saturation: number;
|
|
681
|
+
opacity: number;
|
|
682
|
+
}
|
|
664
683
|
/** Lens fringing that grows toward frame edges (Unity PPv2's curve, the one VTube Studio wraps). */
|
|
665
684
|
export interface SceneChromaticAberration {
|
|
666
685
|
enabled: boolean;
|
|
@@ -877,6 +896,7 @@ export interface EffectValues {
|
|
|
877
896
|
gradient: EffectGradient;
|
|
878
897
|
flare: SceneFlare;
|
|
879
898
|
blur: EffectBlur;
|
|
899
|
+
sketch: EffectSketch;
|
|
880
900
|
chromaticAberration: SceneChromaticAberration;
|
|
881
901
|
grain: SceneFilmGrain;
|
|
882
902
|
dof: SceneDepthOfField;
|
|
@@ -1041,7 +1061,7 @@ export interface ScenePatch {
|
|
|
1041
1061
|
* App-level features a client gates on (never version-sniff): `hello` and
|
|
1042
1062
|
* `app.info` report them — the per-app mirror of {@link InstanceRuntime.capabilities}.
|
|
1043
1063
|
*/
|
|
1044
|
-
export declare const APP_CAPABILITIES: readonly ["storage", "speech", "automations", "controllers", "model-editing", "asset-inspection", "layer-effects", "scene-transitions", "area-lights", "spot-lights", "camera-follow-lights", "shadow-filters", "environment-map-model", "spawn", "tracking-lost", "motion-stop", "stage-capture", "model-movement"];
|
|
1064
|
+
export declare const APP_CAPABILITIES: readonly ["storage", "speech", "automations", "controllers", "model-editing", "asset-inspection", "layer-effects", "scene-transitions", "area-lights", "spot-lights", "camera-follow-lights", "shadow-filters", "environment-map-model", "spawn", "tracking-lost", "motion-stop", "stage-capture", "model-movement", "object-pin-depth"];
|
|
1045
1065
|
export type AppCapability = (typeof APP_CAPABILITIES)[number];
|
|
1046
1066
|
export declare function isAppCapability(v: unknown): v is AppCapability;
|
|
1047
1067
|
/** What a loaded model instance can do; absent capabilities answer `unsupported-for-format`. */
|
|
@@ -1302,12 +1322,12 @@ declare const VTS_INPUT_RANGES: {
|
|
|
1302
1322
|
};
|
|
1303
1323
|
type VtsInputName = keyof typeof VTS_INPUT_RANGES;
|
|
1304
1324
|
/**
|
|
1305
|
-
* Default input vocabulary: face, hand and
|
|
1325
|
+
* Default input vocabulary: face, hand, cursor and time inputs, raw ARKit channels and controller
|
|
1306
1326
|
* profile 1. Additional controller profile ids are accepted by isInputName without appearing
|
|
1307
1327
|
* in this list.
|
|
1308
1328
|
*/
|
|
1309
|
-
export declare const INPUT_NAMES: readonly (VtsInputName | HandInputName | VoiceInputName | MouseInputName | ArkitInputName | BaseControllerInputName)[];
|
|
1310
|
-
export type InputName = VtsInputName | HandInputName | VoiceInputName | MouseInputName | ArkitInputName | ControllerInputName;
|
|
1329
|
+
export declare const INPUT_NAMES: readonly (VtsInputName | HandInputName | VoiceInputName | MouseInputName | TimeInputName | ArkitInputName | BaseControllerInputName)[];
|
|
1330
|
+
export type InputName = VtsInputName | HandInputName | VoiceInputName | MouseInputName | TimeInputName | ArkitInputName | ControllerInputName;
|
|
1311
1331
|
/** Whether an untrusted string names a tracking input — the guard every wire boundary needs. */
|
|
1312
1332
|
export declare function isInputName(v: string): v is InputName;
|
|
1313
1333
|
/**
|
|
@@ -1335,7 +1355,7 @@ export declare function arkitTwinOf(input: InputName): BindingInput;
|
|
|
1335
1355
|
* Default inputs' natural spans. Use getInputRange for a dynamically numbered controller input.
|
|
1336
1356
|
* Head angles are degrees; the rest are unitless.
|
|
1337
1357
|
*/
|
|
1338
|
-
export declare const INPUT_RANGES: Record<VtsInputName | HandInputName | VoiceInputName | MouseInputName | ArkitInputName | BaseControllerInputName, readonly [number, number]>;
|
|
1358
|
+
export declare const INPUT_RANGES: Record<VtsInputName | HandInputName | VoiceInputName | MouseInputName | TimeInputName | ArkitInputName | BaseControllerInputName, readonly [number, number]>;
|
|
1339
1359
|
/** Natural span of any valid input, including dynamically numbered controller profiles. */
|
|
1340
1360
|
export declare function getInputRange(name: InputName): readonly [number, number];
|
|
1341
1361
|
/**
|
package/dist/wire/types.js
CHANGED
|
@@ -7,6 +7,7 @@ import { isOneOf, keysOf } from "../values/guards.js";
|
|
|
7
7
|
import { HAND_INPUT_NAMES, HAND_INPUT_RANGES } from "../values/hands.js";
|
|
8
8
|
import { VOICE_INPUT_NAMES, VOICE_INPUT_RANGES } from "../values/lipsync.js";
|
|
9
9
|
import { MOUSE_INPUT_NAMES, MOUSE_INPUT_RANGES } from "../values/mouse.js";
|
|
10
|
+
import { TIME_INPUT_NAMES, TIME_INPUT_RANGES } from "../values/time.js";
|
|
10
11
|
/**
|
|
11
12
|
* How a registered file is labelled. Wider than an object's content kinds: `.hdr` is only
|
|
12
13
|
* ever an environment map, and a `.vmd` splits by content — `cameraMotion` for one that
|
|
@@ -55,6 +56,11 @@ export const APP_CAPABILITIES = [
|
|
|
55
56
|
'stage-capture',
|
|
56
57
|
/** `model.getMovement` / `model.setMovement` and the `model.movement` event. */
|
|
57
58
|
'model-movement',
|
|
59
|
+
/**
|
|
60
|
+
* 2D images, videos and webpages all paint in the stage's layer stack (a webpage has no `layer`), and
|
|
61
|
+
* one pinned to a Live2D model takes `attach.depth` in that model's stack, as a Live2D item does.
|
|
62
|
+
*/
|
|
63
|
+
'object-pin-depth',
|
|
58
64
|
];
|
|
59
65
|
export function isAppCapability(v) {
|
|
60
66
|
return isOneOf(v, APP_CAPABILITIES);
|
|
@@ -195,7 +201,7 @@ const VTS_INPUT_RANGES = {
|
|
|
195
201
|
/** VTS's input vocabulary, plus `JawOpen` — the derived half of {@link INPUT_NAMES}. */
|
|
196
202
|
const VTS_INPUT_NAMES = keysOf(VTS_INPUT_RANGES);
|
|
197
203
|
/**
|
|
198
|
-
* Default input vocabulary: face, hand and
|
|
204
|
+
* Default input vocabulary: face, hand, cursor and time inputs, raw ARKit channels and controller
|
|
199
205
|
* profile 1. Additional controller profile ids are accepted by isInputName without appearing
|
|
200
206
|
* in this list.
|
|
201
207
|
*/
|
|
@@ -204,6 +210,7 @@ export const INPUT_NAMES = [
|
|
|
204
210
|
...VOICE_INPUT_NAMES,
|
|
205
211
|
...HAND_INPUT_NAMES,
|
|
206
212
|
...MOUSE_INPUT_NAMES,
|
|
213
|
+
...TIME_INPUT_NAMES,
|
|
207
214
|
...ARKIT_INPUT_NAMES,
|
|
208
215
|
...BASE_CONTROLLER_INPUT_NAMES,
|
|
209
216
|
];
|
|
@@ -244,6 +251,7 @@ export const INPUT_RANGES = {
|
|
|
244
251
|
...VOICE_INPUT_RANGES,
|
|
245
252
|
...HAND_INPUT_RANGES,
|
|
246
253
|
...MOUSE_INPUT_RANGES,
|
|
254
|
+
...TIME_INPUT_RANGES,
|
|
247
255
|
...BASE_CONTROLLER_INPUT_RANGES,
|
|
248
256
|
// `fromEntries` widens the key type back to `string`; the annotation above is what keeps
|
|
249
257
|
// this exhaustive, and it fails to typecheck if a name ever lacks a range.
|