@voqalize/avatar 0.2.2 → 0.4.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 +21 -661
- package/LICENSE-CC-BY-4.0 +396 -0
- package/README.md +191 -662
- package/assets/README.md +48 -0
- package/assets/tanya.glb +0 -0
- package/assets/tara.glb +0 -0
- package/assets/tushar.glb +0 -0
- package/client/Avatar.tsx +57 -0
- package/client/AvatarClient.ts +818 -0
- package/client/arjun.ts +26 -0
- package/client/createAvatar.ts +177 -0
- package/client/createCanvasAvatar.ts +72 -0
- package/client/index.ts +44 -0
- package/client/internal.ts +108 -0
- package/client/interviewer-female.ts +4 -0
- package/client/interviewer-male.ts +4 -0
- package/client/ishita.ts +26 -0
- package/client/kabir.ts +26 -0
- package/client/meera.ts +26 -0
- package/client/naina.ts +26 -0
- package/client/playout.ts +95 -0
- package/client/professional-female-a.ts +4 -0
- package/client/professional-female-b.ts +4 -0
- package/client/professional-male-a.ts +4 -0
- package/client/professional-male-b.ts +4 -0
- package/client/react.ts +13 -0
- package/client/supports.ts +28 -0
- package/client/three/assets.ts +24 -0
- package/client/three/budgets.ts +27 -0
- package/client/three/holds.ts +64 -0
- package/client/three/internal.ts +54 -0
- package/client/three/motion-limits.json +116 -0
- package/client/three/sequences.ts +281 -0
- package/client/three/tanya.ts +69 -0
- package/client/three/tara-rig.ts +1562 -0
- package/client/three/tara.ts +76 -0
- package/client/three/tushar.ts +65 -0
- package/client/types.ts +235 -0
- package/client/useAvatar.ts +74 -0
- package/client/vikram.ts +26 -0
- package/dist/Avatar.d.ts +44 -0
- package/dist/Avatar.d.ts.map +1 -0
- package/{client/dist → dist}/Avatar.js +2 -2
- package/dist/Avatar.js.map +1 -0
- package/dist/AvatarClient.d.ts +274 -0
- package/dist/AvatarClient.d.ts.map +1 -0
- package/dist/AvatarClient.js +712 -0
- package/dist/AvatarClient.js.map +1 -0
- package/dist/arjun.d.ts +7 -0
- package/dist/arjun.d.ts.map +1 -0
- package/dist/arjun.js +20 -0
- package/dist/arjun.js.map +1 -0
- package/dist/createAvatar.d.ts +135 -0
- package/dist/createAvatar.d.ts.map +1 -0
- package/dist/createAvatar.js +75 -0
- package/dist/createAvatar.js.map +1 -0
- package/dist/createCanvasAvatar.d.ts +22 -0
- package/dist/createCanvasAvatar.d.ts.map +1 -0
- package/dist/createCanvasAvatar.js +47 -0
- package/dist/createCanvasAvatar.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/internal.d.ts +34 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +41 -0
- package/dist/internal.js.map +1 -0
- package/dist/interviewer-female.d.ts +4 -0
- package/dist/interviewer-female.d.ts.map +1 -0
- package/dist/interviewer-female.js +3 -0
- package/dist/interviewer-female.js.map +1 -0
- package/dist/interviewer-male.d.ts +4 -0
- package/dist/interviewer-male.d.ts.map +1 -0
- package/dist/interviewer-male.js +3 -0
- package/dist/interviewer-male.js.map +1 -0
- package/dist/ishita.d.ts +7 -0
- package/dist/ishita.d.ts.map +1 -0
- package/dist/ishita.js +20 -0
- package/dist/ishita.js.map +1 -0
- package/dist/kabir.d.ts +7 -0
- package/dist/kabir.d.ts.map +1 -0
- package/dist/kabir.js +20 -0
- package/dist/kabir.js.map +1 -0
- package/dist/meera.d.ts +7 -0
- package/dist/meera.d.ts.map +1 -0
- package/dist/meera.js +20 -0
- package/dist/meera.js.map +1 -0
- package/dist/naina.d.ts +7 -0
- package/dist/naina.d.ts.map +1 -0
- package/dist/naina.js +20 -0
- package/dist/naina.js.map +1 -0
- package/dist/playout.d.ts +42 -0
- package/dist/playout.d.ts.map +1 -0
- package/dist/playout.js +85 -0
- package/dist/playout.js.map +1 -0
- package/dist/professional-female-a.d.ts +4 -0
- package/dist/professional-female-a.d.ts.map +1 -0
- package/dist/professional-female-a.js +3 -0
- package/dist/professional-female-a.js.map +1 -0
- package/dist/professional-female-b.d.ts +4 -0
- package/dist/professional-female-b.d.ts.map +1 -0
- package/dist/professional-female-b.js +3 -0
- package/dist/professional-female-b.js.map +1 -0
- package/dist/professional-male-a.d.ts +4 -0
- package/dist/professional-male-a.d.ts.map +1 -0
- package/dist/professional-male-a.js +3 -0
- package/dist/professional-male-a.js.map +1 -0
- package/dist/professional-male-b.d.ts +4 -0
- package/dist/professional-male-b.d.ts.map +1 -0
- package/dist/professional-male-b.js +3 -0
- package/dist/professional-male-b.js.map +1 -0
- package/dist/react.d.ts +13 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +13 -0
- package/dist/react.js.map +1 -0
- package/dist/supports.d.ts +20 -0
- package/dist/supports.d.ts.map +1 -0
- package/dist/supports.js +26 -0
- package/dist/supports.js.map +1 -0
- package/dist/three/assets.d.ts +24 -0
- package/dist/three/assets.d.ts.map +1 -0
- package/dist/three/assets.js +22 -0
- package/dist/three/assets.js.map +1 -0
- package/dist/three/budgets.d.ts +17 -0
- package/dist/three/budgets.d.ts.map +1 -0
- package/dist/three/budgets.js +21 -0
- package/dist/three/budgets.js.map +1 -0
- package/dist/three/holds.d.ts +33 -0
- package/dist/three/holds.d.ts.map +1 -0
- package/dist/three/holds.js +56 -0
- package/dist/three/holds.js.map +1 -0
- package/dist/three/internal.d.ts +52 -0
- package/dist/three/internal.d.ts.map +1 -0
- package/dist/three/internal.js +49 -0
- package/dist/three/internal.js.map +1 -0
- package/dist/three/motion-limits.json +116 -0
- package/dist/three/sequences.d.ts +71 -0
- package/dist/three/sequences.d.ts.map +1 -0
- package/dist/three/sequences.js +262 -0
- package/dist/three/sequences.js.map +1 -0
- package/dist/three/tanya.d.ts +35 -0
- package/dist/three/tanya.d.ts.map +1 -0
- package/dist/three/tanya.js +51 -0
- package/dist/three/tanya.js.map +1 -0
- package/dist/three/tara-rig.d.ts +403 -0
- package/dist/three/tara-rig.d.ts.map +1 -0
- package/dist/three/tara-rig.js +1500 -0
- package/dist/three/tara-rig.js.map +1 -0
- package/dist/three/tara.d.ts +35 -0
- package/dist/three/tara.d.ts.map +1 -0
- package/dist/three/tara.js +58 -0
- package/dist/three/tara.js.map +1 -0
- package/dist/three/tushar.d.ts +31 -0
- package/dist/three/tushar.d.ts.map +1 -0
- package/dist/three/tushar.js +47 -0
- package/dist/three/tushar.js.map +1 -0
- package/dist/types.d.ts +149 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +140 -0
- package/dist/types.js.map +1 -0
- package/dist/useAvatar.d.ts +39 -0
- package/dist/useAvatar.d.ts.map +1 -0
- package/dist/useAvatar.js +35 -0
- package/dist/useAvatar.js.map +1 -0
- package/dist/vikram.d.ts +7 -0
- package/dist/vikram.d.ts.map +1 -0
- package/dist/vikram.js +20 -0
- package/dist/vikram.js.map +1 -0
- package/package.json +110 -25
- package/src/avatar.d.ts +218 -120
- package/src/avatar.js +950 -213
- package/src/behavior.d.ts +42 -0
- package/src/behavior.js +114 -0
- package/src/camera.js +29 -0
- package/src/canvas/author/parts/eye.mjs +722 -0
- package/src/canvas/author/parts/hand.mjs +1156 -0
- package/src/canvas/author/parts/mouth.mjs +741 -0
- package/src/canvas/author/parts/nose.mjs +100 -0
- package/src/canvas/author/parts/skin-detail.mjs +67 -0
- package/src/canvas/author/path.mjs +283 -0
- package/src/canvas/author/rig.mjs +405 -0
- package/src/canvas/avatars/round/face.d.mts +3 -0
- package/src/canvas/avatars/round/face.mjs +1307 -0
- package/src/canvas/create-rig.d.ts +15 -0
- package/src/canvas/create-rig.js +100 -0
- package/src/canvas/data/img/professional-female-a-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-female-a-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-female-a-top-body.webp +0 -0
- package/src/canvas/data/img/professional-female-b-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-female-b-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-female-b-top-body.webp +0 -0
- package/src/canvas/data/img/professional-male-a-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-male-a-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-male-a-top-body.webp +0 -0
- package/src/canvas/data/img/professional-male-b-hair-back.webp +0 -0
- package/src/canvas/data/img/professional-male-b-hair-front.webp +0 -0
- package/src/canvas/data/img/professional-male-b-top-body.webp +0 -0
- package/src/canvas/data/img/round-m3-hair-back.webp +0 -0
- package/src/canvas/data/img/round-m3-hair-front.webp +0 -0
- package/src/canvas/data/img/round-m3-top-body.webp +0 -0
- package/src/canvas/data/img/round-w1-hair-back.webp +0 -0
- package/src/canvas/data/img/round-w1-hair-front.webp +0 -0
- package/src/canvas/data/img/round-w1-top-body.webp +0 -0
- package/src/canvas/data/interviewer-female.rig.json +1 -0
- package/src/canvas/data/interviewer-male.rig.json +1 -0
- package/src/canvas/data/professional-female-a.rig.json +1 -0
- package/src/canvas/data/professional-female-b.rig.json +1 -0
- package/src/canvas/data/professional-male-a.rig.json +1 -0
- package/src/canvas/data/professional-male-b.rig.json +1 -0
- package/src/canvas/src/live.js +508 -0
- package/src/canvas/src/render2d.js +218 -0
- package/src/canvas/src/rig.js +297 -0
- package/src/canvas/src/vocab.js +96 -0
- package/src/clips.js +18 -7
- package/src/conformance.js +119 -0
- package/src/emotions.js +8 -5
- package/src/face-core.js +27 -1
- package/src/face-myna.d.ts +7 -0
- package/src/face-myna.js +192 -131
- package/src/face-peep-control-plane.js +167 -0
- package/src/face-peep.d.ts +7 -0
- package/src/face-peep.js +274 -116
- package/src/face-wren.d.ts +7 -0
- package/src/face-wren.js +17 -19
- package/src/faces.d.ts +12 -0
- package/src/faces.js +53 -0
- package/src/gaze.js +434 -58
- package/src/hand.js +120 -91
- package/src/head.js +134 -0
- package/src/idle.js +227 -119
- package/src/interjections.js +200 -31
- package/src/params.js +8 -4
- package/src/perform.js +7 -9
- package/src/prosody.js +647 -0
- package/src/rig.d.ts +24 -0
- package/src/rig.js +32 -0
- package/src/speech-timing.js +23 -0
- package/src/visemes.js +110 -24
- package/client/dist/Avatar.d.ts +0 -27
- package/client/dist/Avatar.d.ts.map +0 -1
- package/client/dist/Avatar.js.map +0 -1
- package/client/dist/AvatarClient.d.ts +0 -139
- package/client/dist/AvatarClient.d.ts.map +0 -1
- package/client/dist/AvatarClient.js +0 -223
- package/client/dist/AvatarClient.js.map +0 -1
- package/client/dist/index.d.ts +0 -22
- package/client/dist/index.d.ts.map +0 -1
- package/client/dist/index.js +0 -22
- package/client/dist/index.js.map +0 -1
- package/client/dist/types.d.ts +0 -86
- package/client/dist/types.d.ts.map +0 -1
- package/client/dist/types.js +0 -31
- package/client/dist/types.js.map +0 -1
- package/client/dist/useAvatar.d.ts +0 -42
- package/client/dist/useAvatar.d.ts.map +0 -1
- package/client/dist/useAvatar.js +0 -51
- package/client/dist/useAvatar.js.map +0 -1
- package/client/src/Avatar.tsx +0 -33
- package/client/src/AvatarClient.ts +0 -270
- package/client/src/index.ts +0 -22
- package/client/src/types.ts +0 -104
- package/client/src/useAvatar.ts +0 -85
- package/docs/contract-avatar.md +0 -371
- package/docs/contract-protocol.md +0 -461
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AvatarStateName } from "./avatar.js";
|
|
2
|
+
|
|
3
|
+
/** The two renderer calls behavior resolution actually needs. */
|
|
4
|
+
export interface BehaviorAvatar {
|
|
5
|
+
setState(name: AvatarStateName): unknown;
|
|
6
|
+
/**
|
|
7
|
+
* One motion by name. The vocabulary is open — two core intents plus
|
|
8
|
+
* whatever this avatar publishes — so a name it does not have must be a
|
|
9
|
+
* no-op rather than a throw.
|
|
10
|
+
*/
|
|
11
|
+
action(id: string): unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The whole behaviour vocabulary — the nine. Not an alias list; see behavior.js.
|
|
16
|
+
*
|
|
17
|
+
* Every one of these is also an `AvatarStateName`, which is why there is no
|
|
18
|
+
* mapping table any more.
|
|
19
|
+
*/
|
|
20
|
+
export type BehaviorStateId =
|
|
21
|
+
| "IDLE" | "LISTENING" | "CANT_HEAR" | "THINKING" | "WORKING"
|
|
22
|
+
| "MUTED" | "SPEAKING" | "DEGRADED" | "OFFLINE";
|
|
23
|
+
|
|
24
|
+
export type BehaviorActionId = "ack" | "turn.interrupted";
|
|
25
|
+
|
|
26
|
+
export const BEHAVIOR_STATE_IDS: readonly BehaviorStateId[];
|
|
27
|
+
export const BEHAVIOR_ACTIONS: Readonly<Record<BehaviorActionId, { renderAction: string }>>;
|
|
28
|
+
export const BEHAVIOR_ACTION_IDS: readonly BehaviorActionId[];
|
|
29
|
+
export const WIRE_ACTION_TO_BEHAVIOR: Readonly<Record<string, BehaviorActionId>>;
|
|
30
|
+
|
|
31
|
+
export class BehaviorController {
|
|
32
|
+
constructor(avatar: BehaviorAvatar);
|
|
33
|
+
state: BehaviorStateId | null;
|
|
34
|
+
setState(id: BehaviorStateId, options?: { force?: boolean }): this;
|
|
35
|
+
action(id: BehaviorActionId): this;
|
|
36
|
+
/**
|
|
37
|
+
* One id off the wire: a core intent through the catalog, anything else
|
|
38
|
+
* straight to the renderer. Never throws — the vocabulary is open.
|
|
39
|
+
*/
|
|
40
|
+
wireAction(id: string): this;
|
|
41
|
+
destroy(): void;
|
|
42
|
+
}
|
package/src/behavior.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behavior catalog and controller.
|
|
3
|
+
*
|
|
4
|
+
* This is the layer between factual/wire inputs and the bundled SVG renderer.
|
|
5
|
+
* It is not a seam any avatar implementation sees: an implementation receives
|
|
6
|
+
* a PipecatClient and decides for itself what a state means.
|
|
7
|
+
*
|
|
8
|
+
* The vocabulary is the nine core states and two core actions. The states used
|
|
9
|
+
* to be seventeen — the core
|
|
10
|
+
* set plus every SVG render state (TYPING, SEARCHING_SCREEN, WANTS_IN, …)
|
|
11
|
+
* passed straight through, so that tooling could drive one of *those* from
|
|
12
|
+
* here. That made the mixer's private state list look like part of the
|
|
13
|
+
* behaviour vocabulary. Tooling that wants a render state calls
|
|
14
|
+
* `avatar.setState` on the mixer, which is whose state it is.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The durable states this layer resolves. Each one *is* the render state it
|
|
19
|
+
* asks the bundled SVG mixer for, so there is nothing here to map.
|
|
20
|
+
*
|
|
21
|
+
* There used to be a second column — a per-state `renderState`, so a renderer
|
|
22
|
+
* could draw `WORKING` as anything it liked and the divergence would land here
|
|
23
|
+
* rather than leak. In nine rows it had exactly one non-identity entry,
|
|
24
|
+
* `STRAINING -> CANT_HEAR`, and the wire redesign deleted that entry by
|
|
25
|
+
* renaming the state: a state names the bot's *situation*, never how it looks,
|
|
26
|
+
* and "straining" was a situation named after one drawing of it. A table whose
|
|
27
|
+
* every row reads `X: X` documents nothing and invites a renderer to plug into
|
|
28
|
+
* it, so it is a list now. A renderer that genuinely wants to draw one of these
|
|
29
|
+
* as something else calls `avatar.setState` with the render state it wants,
|
|
30
|
+
* which is whose state that is.
|
|
31
|
+
*/
|
|
32
|
+
export const BEHAVIOR_STATE_IDS = Object.freeze([
|
|
33
|
+
'IDLE', 'LISTENING', 'CANT_HEAR', 'THINKING', 'WORKING',
|
|
34
|
+
'MUTED', 'SPEAKING', 'DEGRADED', 'OFFLINE',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const STATE_IDS = new Set(BEHAVIOR_STATE_IDS);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Library action IDs: the two intents every renderer owes a server, in this
|
|
41
|
+
* layer's readable spelling.
|
|
42
|
+
*
|
|
43
|
+
* It used to be seven, with a `sequence` command alongside for a renderer's own
|
|
44
|
+
* motions. The seven were not the wrong *names* so much as the wrong idea: four
|
|
45
|
+
* of them were `GESTURE_*` — a greet, a goodbye, a wave — which are things a
|
|
46
|
+
* particular body does and not intents a server can hold every face to, and two
|
|
47
|
+
* were the receipt and the nod, which are two *shapes* of acknowledging. So the
|
|
48
|
+
* server now says only that an acknowledgement is due, the avatar picks which
|
|
49
|
+
* one it makes, and the gestures live in whichever catalogue can draw them,
|
|
50
|
+
* reachable by the same open `action` id a sequence used to need its own
|
|
51
|
+
* command for.
|
|
52
|
+
*/
|
|
53
|
+
export const BEHAVIOR_ACTIONS = Object.freeze({
|
|
54
|
+
ack: { renderAction: 'ACKNOWLEDGE' },
|
|
55
|
+
'turn.interrupted': { renderAction: 'RESPONSE_INTERRUPTED' },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export const BEHAVIOR_ACTION_IDS = Object.freeze(Object.keys(BEHAVIOR_ACTIONS));
|
|
59
|
+
|
|
60
|
+
/** The two core wire names map into the behavior catalog here and nowhere else. */
|
|
61
|
+
export const WIRE_ACTION_TO_BEHAVIOR = Object.freeze({
|
|
62
|
+
ACKNOWLEDGE: 'ack',
|
|
63
|
+
RESPONSE_INTERRUPTED: 'turn.interrupted',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Turns behavior intent into the legacy SVG mixer's calls. A future renderer
|
|
68
|
+
* receives the same intent through its own adapter; it does not need to know
|
|
69
|
+
* the legacy state/action names used below.
|
|
70
|
+
*/
|
|
71
|
+
export class BehaviorController {
|
|
72
|
+
constructor(avatar) {
|
|
73
|
+
if (!avatar || typeof avatar.setState !== 'function' || typeof avatar.action !== 'function') {
|
|
74
|
+
throw new TypeError('BehaviorController requires an avatar implementation');
|
|
75
|
+
}
|
|
76
|
+
this.avatar = avatar;
|
|
77
|
+
this.state = null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setState(id, { force = false } = {}) {
|
|
81
|
+
if (!STATE_IDS.has(id)) throw new Error(`unknown behavior state: ${id}`);
|
|
82
|
+
if (!force && this.state === id) return this;
|
|
83
|
+
this.state = id;
|
|
84
|
+
this.avatar.setState(id);
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
destroy() {}
|
|
89
|
+
|
|
90
|
+
action(id) {
|
|
91
|
+
const def = BEHAVIOR_ACTIONS[id];
|
|
92
|
+
if (!def) throw new Error(`unknown behavior action: ${id}`);
|
|
93
|
+
this.avatar.action(def.renderAction);
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* One id off the wire. A core one goes through the behavior catalog; any
|
|
99
|
+
* other goes straight to the renderer, and that asymmetry is the point.
|
|
100
|
+
* `BEHAVIOR_ACTIONS` exists so a portable intent has a readable name
|
|
101
|
+
* independent of how any one face renders it. An avatar's own id has no such
|
|
102
|
+
* independence — it *is* the rendering, named by the renderer that owns it —
|
|
103
|
+
* so giving it a behavior alias would claim a portability it does not have.
|
|
104
|
+
*
|
|
105
|
+
* Neither path throws. The wire's action vocabulary is open and an id this
|
|
106
|
+
* face cannot draw is the expected case, not somebody's broken build.
|
|
107
|
+
*/
|
|
108
|
+
wireAction(id) {
|
|
109
|
+
const behaviorId = WIRE_ACTION_TO_BEHAVIOR[id];
|
|
110
|
+
if (behaviorId) return this.action(behaviorId);
|
|
111
|
+
this.avatar.action(id);
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
}
|
package/src/camera.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The close webcam crop shared by every avatar renderer.
|
|
3
|
+
*
|
|
4
|
+
* The head gets most of the tile because lip sync and listening expressions
|
|
5
|
+
* are the information the avatar is there to carry. The remaining lower band
|
|
6
|
+
* leaves enough neck and shoulder for posture to read without turning the
|
|
7
|
+
* call tile into a half-body portrait.
|
|
8
|
+
*/
|
|
9
|
+
export const CALL_CAMERA = Object.freeze({
|
|
10
|
+
aspect: 4 / 3,
|
|
11
|
+
headroom: 0.06,
|
|
12
|
+
head: 0.70,
|
|
13
|
+
body: 0.24,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/** Derive a camera rectangle from three landmarks in the drawing's own units. */
|
|
17
|
+
export function viewBoxForHead({ centerX, crownY, chinY }) {
|
|
18
|
+
if (![centerX, crownY, chinY].every(Number.isFinite) || chinY <= crownY) {
|
|
19
|
+
throw new TypeError('viewBoxForHead: expected a finite centre and crown above chin');
|
|
20
|
+
}
|
|
21
|
+
const h = (chinY - crownY) / CALL_CAMERA.head;
|
|
22
|
+
const w = h * CALL_CAMERA.aspect;
|
|
23
|
+
return {
|
|
24
|
+
x: centerX - w / 2,
|
|
25
|
+
y: crownY - h * CALL_CAMERA.headroom,
|
|
26
|
+
w,
|
|
27
|
+
h,
|
|
28
|
+
};
|
|
29
|
+
}
|