@lemoncat7/dsh-theme-xiaohei 0.3.0-alpha.23 → 0.3.2
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/README.md +39 -17
- package/THIRD_PARTY_NOTICES.md +41 -0
- package/lib/appearance.js +26 -10
- package/lib/blink.js +3 -3
- package/lib/bloub-heixiu/ambient.d.ts +3 -0
- package/lib/bloub-heixiu/ambient.js +12 -0
- package/lib/bloub-heixiu/character.d.ts +33 -0
- package/lib/bloub-heixiu/character.js +124 -0
- package/lib/bloub-heixiu/effects.d.ts +4 -0
- package/lib/bloub-heixiu/effects.js +91 -0
- package/lib/bloub-heixiu/flight.d.ts +15 -0
- package/lib/bloub-heixiu/flight.js +66 -0
- package/lib/bloub-heixiu/follow.d.ts +13 -0
- package/lib/bloub-heixiu/follow.js +23 -0
- package/lib/bloub-heixiu/layout.d.ts +18 -0
- package/lib/bloub-heixiu/layout.js +12 -0
- package/lib/bloub-heixiu/renderer.d.ts +16 -0
- package/lib/bloub-heixiu/renderer.js +94 -0
- package/lib/bloub-heixiu/runtime.d.ts +6 -0
- package/lib/bloub-heixiu/runtime.js +395 -0
- package/lib/bloub-heixiu/signals.d.ts +17 -0
- package/lib/bloub-heixiu/signals.js +53 -0
- package/lib/bloub-heixiu/styles.d.ts +2 -0
- package/lib/bloub-heixiu/styles.js +98 -0
- package/lib/chrome/accessibility.d.ts +1 -1
- package/lib/chrome/accessibility.js +5 -13
- package/lib/chrome/composer-avatar.d.ts +10 -0
- package/lib/chrome/composer-avatar.js +599 -0
- package/lib/chrome/composer-send-heixiu.d.ts +3 -0
- package/lib/chrome/composer-send-heixiu.js +143 -0
- package/lib/chrome/composer.d.ts +2 -2
- package/lib/chrome/composer.js +84 -95
- package/lib/chrome/controls.d.ts +8 -0
- package/lib/chrome/controls.js +348 -0
- package/lib/chrome/conversation-messages.d.ts +1 -1
- package/lib/chrome/conversation-messages.js +207 -9
- package/lib/chrome/conversation.d.ts +1 -1
- package/lib/chrome/conversation.js +6 -0
- package/lib/chrome/identity.js +4 -18
- package/lib/chrome/portal.d.ts +1 -1
- package/lib/chrome/portal.js +1 -1
- package/lib/chrome/sidebar.d.ts +1 -1
- package/lib/chrome/sidebar.js +134 -153
- package/lib/chrome/tokens.d.ts +1 -1
- package/lib/chrome/tokens.js +121 -68
- package/lib/chrome/workspace.d.ts +2 -2
- package/lib/chrome/workspace.js +35 -254
- package/lib/chrome.js +4 -0
- package/lib/client.d.ts +5 -12
- package/lib/client.js +6262 -4455
- package/lib/composer-avatar.d.ts +18 -0
- package/lib/composer-avatar.js +441 -0
- package/lib/composer-send-heixiu.d.ts +8 -0
- package/lib/composer-send-heixiu.js +133 -0
- package/lib/gaze.js +3 -3
- package/lib/generated-backgrounds.d.ts +14 -0
- package/lib/generated-backgrounds.js +14 -0
- package/lib/generated-character-motion.d.ts +4 -0
- package/lib/generated-character-motion.js +2 -0
- package/lib/generated-identity.d.ts +1 -0
- package/lib/generated-identity.js +1 -0
- package/lib/generated-keyart.d.ts +18 -3
- package/lib/generated-keyart.js +18 -3
- package/lib/host-contract.d.ts +9 -0
- package/lib/host-contract.js +13 -0
- package/lib/host-dom.d.ts +11 -0
- package/lib/host-dom.js +60 -0
- package/lib/loading-heixiu.js +3 -3
- package/lib/metallic-brand-mark.d.ts +10 -0
- package/lib/metallic-brand-mark.js +323 -0
- package/lib/plugin.d.ts +1 -1
- package/lib/plugin.js +31 -16
- package/lib/portal.js +11 -10
- package/lib/reactions.js +3 -3
- package/lib/scene/avatar-model.d.ts +6 -0
- package/lib/scene/avatar-model.js +646 -0
- package/lib/scene/avatar-motion.d.ts +3 -0
- package/lib/scene/avatar-motion.js +799 -0
- package/lib/scene/character-idle.d.ts +17 -0
- package/lib/scene/character-idle.js +65 -0
- package/lib/scene/character-layout.d.ts +24 -0
- package/lib/scene/character-layout.js +51 -0
- package/lib/scene/character-motion-clock.d.ts +18 -0
- package/lib/scene/character-motion-clock.js +57 -0
- package/lib/scene/character-motion-types.d.ts +12 -0
- package/lib/scene/character-motion-types.js +2 -0
- package/lib/scene/character-motion.d.ts +6 -0
- package/lib/scene/character-motion.js +126 -0
- package/lib/scene/character-poses.d.ts +26 -0
- package/lib/scene/character-poses.js +11 -0
- package/lib/scene/character-rig-renderer.d.ts +4 -0
- package/lib/scene/character-rig-renderer.js +85 -0
- package/lib/scene/character-rig.d.ts +20 -0
- package/lib/scene/character-rig.js +53 -0
- package/lib/scene/heixiu-feedback.d.ts +1 -1
- package/lib/scene/heixiu-feedback.js +0 -17
- package/lib/scene/pointer-bridge.d.ts +21 -0
- package/lib/scene/pointer-bridge.js +155 -0
- package/lib/scene/runtime.d.ts +12 -0
- package/lib/scene/runtime.js +87 -0
- package/lib/scene/styles.d.ts +7 -0
- package/lib/scene/styles.js +75 -0
- package/lib/scene/sylva-performance.d.ts +11 -0
- package/lib/scene/sylva-performance.js +58 -0
- package/lib/scene/wallpaper-character-styles.d.ts +3 -0
- package/lib/scene/wallpaper-character-styles.js +53 -0
- package/lib/scene/wallpaper-character.d.ts +4 -0
- package/lib/scene/wallpaper-character.js +213 -0
- package/lib/scene.d.ts +7 -13
- package/lib/scene.js +7 -1023
- package/lib/sidebar-glass.js +99 -36
- package/lib/sidebar-heixiu-roaming.js +5 -5
- package/lib/sidebar-reveal.d.ts +8 -0
- package/lib/sidebar-reveal.js +51 -0
- package/lib/state.d.ts +14 -2
- package/lib/state.js +12 -4
- package/lib/theme.js +2 -2
- package/lib/vendor/bloub/decor.d.ts +114 -0
- package/lib/vendor/bloub/decor.js +198 -0
- package/lib/vendor/bloub/engine.d.ts +213 -0
- package/lib/vendor/bloub/engine.js +452 -0
- package/lib/vendor/bloub/expressions.d.ts +35 -0
- package/lib/vendor/bloub/expressions.js +143 -0
- package/lib/vendor/bloub/eyefit.d.ts +75 -0
- package/lib/vendor/bloub/eyefit.js +393 -0
- package/lib/vendor/bloub/face.d.ts +63 -0
- package/lib/vendor/bloub/face.js +106 -0
- package/lib/vendor/bloub/math.d.ts +22 -0
- package/lib/vendor/bloub/math.js +36 -0
- package/lib/vendor/bloub/profiles.d.ts +8 -0
- package/lib/vendor/bloub/profiles.js +20 -0
- package/lib/vendor/bloub/repere.d.ts +30 -0
- package/lib/vendor/bloub/repere.js +32 -0
- package/lib/vendor/bloub/shape.d.ts +80 -0
- package/lib/vendor/bloub/shape.js +268 -0
- package/lib/vendor/bloub/skins.d.ts +36 -0
- package/lib/vendor/bloub/skins.js +74 -0
- package/lib/vendor/bloub/states.d.ts +87 -0
- package/lib/vendor/bloub/states.js +468 -0
- package/package.json +23 -6
- package/lib/appearance.d.ts.map +0 -1
- package/lib/appearance.js.map +0 -1
- package/lib/blink.d.ts.map +0 -1
- package/lib/blink.js.map +0 -1
- package/lib/boot-appearance.d.ts.map +0 -1
- package/lib/boot-appearance.js.map +0 -1
- package/lib/boot-loader.d.ts.map +0 -1
- package/lib/boot-loader.js.map +0 -1
- package/lib/chrome/accessibility.d.ts.map +0 -1
- package/lib/chrome/accessibility.js.map +0 -1
- package/lib/chrome/blink.d.ts.map +0 -1
- package/lib/chrome/blink.js.map +0 -1
- package/lib/chrome/composer.d.ts.map +0 -1
- package/lib/chrome/composer.js.map +0 -1
- package/lib/chrome/conversation-messages.d.ts.map +0 -1
- package/lib/chrome/conversation-messages.js.map +0 -1
- package/lib/chrome/conversation.d.ts.map +0 -1
- package/lib/chrome/conversation.js.map +0 -1
- package/lib/chrome/frames.d.ts.map +0 -1
- package/lib/chrome/frames.js.map +0 -1
- package/lib/chrome/gaze.d.ts.map +0 -1
- package/lib/chrome/gaze.js.map +0 -1
- package/lib/chrome/identity.d.ts.map +0 -1
- package/lib/chrome/identity.js.map +0 -1
- package/lib/chrome/overlays.d.ts.map +0 -1
- package/lib/chrome/overlays.js.map +0 -1
- package/lib/chrome/portal.d.ts.map +0 -1
- package/lib/chrome/portal.js.map +0 -1
- package/lib/chrome/reactions.d.ts.map +0 -1
- package/lib/chrome/reactions.js.map +0 -1
- package/lib/chrome/sidebar.d.ts.map +0 -1
- package/lib/chrome/sidebar.js.map +0 -1
- package/lib/chrome/tokens.d.ts.map +0 -1
- package/lib/chrome/tokens.js.map +0 -1
- package/lib/chrome/workspace.d.ts.map +0 -1
- package/lib/chrome/workspace.js.map +0 -1
- package/lib/chrome.d.ts.map +0 -1
- package/lib/chrome.js.map +0 -1
- package/lib/client.d.ts.map +0 -1
- package/lib/client.js.map +0 -7
- package/lib/gaze.d.ts.map +0 -1
- package/lib/gaze.js.map +0 -1
- package/lib/generated-identity.d.ts.map +0 -1
- package/lib/generated-identity.js.map +0 -1
- package/lib/generated-keyart.d.ts.map +0 -1
- package/lib/generated-keyart.js.map +0 -1
- package/lib/generated-loading-assets.d.ts.map +0 -1
- package/lib/generated-loading-assets.js.map +0 -1
- package/lib/generated-sidebar-assets.d.ts +0 -4
- package/lib/generated-sidebar-assets.d.ts.map +0 -1
- package/lib/generated-sidebar-assets.js +0 -4
- package/lib/generated-sidebar-assets.js.map +0 -1
- package/lib/heixiu-interactions.d.ts.map +0 -1
- package/lib/heixiu-interactions.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/loading-heixiu.d.ts.map +0 -1
- package/lib/loading-heixiu.js.map +0 -1
- package/lib/plugin.d.ts.map +0 -1
- package/lib/plugin.js.map +0 -1
- package/lib/portal.d.ts.map +0 -1
- package/lib/portal.js.map +0 -1
- package/lib/reactions.d.ts.map +0 -1
- package/lib/reactions.js.map +0 -1
- package/lib/scene/heixiu-feedback.d.ts.map +0 -1
- package/lib/scene/heixiu-feedback.js.map +0 -1
- package/lib/scene/heixiu-interactions.d.ts.map +0 -1
- package/lib/scene/heixiu-interactions.js.map +0 -1
- package/lib/scene/state-transitions.d.ts.map +0 -1
- package/lib/scene/state-transitions.js.map +0 -1
- package/lib/scene.d.ts.map +0 -1
- package/lib/scene.js.map +0 -1
- package/lib/sidebar-glass.d.ts.map +0 -1
- package/lib/sidebar-glass.js.map +0 -1
- package/lib/sidebar-heixiu-escape.d.ts.map +0 -1
- package/lib/sidebar-heixiu-escape.js.map +0 -1
- package/lib/sidebar-heixiu-roaming.d.ts.map +0 -1
- package/lib/sidebar-heixiu-roaming.js.map +0 -1
- package/lib/state.d.ts.map +0 -1
- package/lib/state.js.map +0 -1
- package/lib/theme.d.ts.map +0 -1
- package/lib/theme.js.map +0 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ou poser le visage sur une forme du personnalisateur.
|
|
3
|
+
*
|
|
4
|
+
* Les yeux vivent sur une sphere, et `radiusAtAngle` les recolle au contour reel au
|
|
5
|
+
* prorata du rayon local. Ce prorata place bien leur CENTRE, mais l'oeil a une taille :
|
|
6
|
+
* la marge qui lui reste devant le bord est multipliee par le meme facteur, donc une
|
|
7
|
+
* silhouette etroite dans sa direction le pousse contre le bord jusqu'a ce que le
|
|
8
|
+
* masque l'ouvre vers l'exterieur. La gelule apparaissait comme une encoche dans le
|
|
9
|
+
* corps sur `capsule`, `triangle`, `nuage` et `goutte`.
|
|
10
|
+
*
|
|
11
|
+
* Ce module resout le probleme UNE FOIS, au chargement, et rend une table de decalages.
|
|
12
|
+
* Ce choix est l'essentiel du correctif, bien plus que la geometrie qui suit :
|
|
13
|
+
*
|
|
14
|
+
* Resolue dans la boucle de rendu, la correction reagit a tout ce qui bouge a soixante
|
|
15
|
+
* images par seconde — la derive du regard, le pointeur, l'expression en cours de
|
|
16
|
+
* morph, le bord le plus proche qui change, l'oeil le plus contraint qui change. Sept
|
|
17
|
+
* variantes ont ete ecrites ainsi et toutes produisaient un artefact de mouvement
|
|
18
|
+
* visible : tremblement permanent, saut de direction de 26 unites quand le bord de
|
|
19
|
+
* reference basculait, grossissement brusque quand la taille entrait dans le calcul.
|
|
20
|
+
* Le defaut n'etait dans aucune de leurs geometries, il etait dans le fait de resoudre
|
|
21
|
+
* par image.
|
|
22
|
+
*
|
|
23
|
+
* Le reste du moteur ne travaille pas comme ca : les poses sont DECLAREES et il ne fait
|
|
24
|
+
* que les interpoler avec des courbes connues. Un decalage tabule rentre dans ce moule.
|
|
25
|
+
* Il ne bouge pas quand le regard derive ni quand le pointeur bouge, et sur un changement
|
|
26
|
+
* de forme ou d'expression il ne fait qu'aller d'une entree de table a l'autre, sur la
|
|
27
|
+
* courbe de ce morph. Le tremblement devient impossible par construction, au lieu d'etre
|
|
28
|
+
* repousse : interpoler entre deux constantes est monotone, alors que re-resoudre le
|
|
29
|
+
* probleme sur un regard en cours d'interpolation ne l'est pas.
|
|
30
|
+
*
|
|
31
|
+
* Corollaire agreable : le solveur n'a plus aucune contrainte de continuite, puisqu'il ne
|
|
32
|
+
* tourne pas pendant l'animation. Il peut donc sonder tout un faisceau de directions et
|
|
33
|
+
* couvrir le pire cas de la derive du regard, ce qu'une version par image ne pouvait pas
|
|
34
|
+
* se permettre.
|
|
35
|
+
*
|
|
36
|
+
* La table est une constante de module, batie a l'import a partir de donnees pures :
|
|
37
|
+
* meme nature que le calendrier de clignements de `face.ts`, deterministe et sans etat,
|
|
38
|
+
* donc sans effet sur la purete de `engine.sample(t)`.
|
|
39
|
+
*/
|
|
40
|
+
import { type StateId } from './states.js';
|
|
41
|
+
/**
|
|
42
|
+
* Table des decalages, batie a l'import : une entree par (forme, etat a corps de base,
|
|
43
|
+
* expression). Seuls `idle` et `swirl` portent le visage de repos, donc seuls eux se
|
|
44
|
+
* declinent par expression — les trois autres etats a corps de base ont un visage releve
|
|
45
|
+
* sur la video et une seule entree.
|
|
46
|
+
*
|
|
47
|
+
* Clef par REFERENCE du tableau de rayons, ce qui est deja la convention du moteur : ses
|
|
48
|
+
* gardes `radii === this.shape` et `expression === this.expr` reposent sur la meme
|
|
49
|
+
* stabilite. Un profil inconnu, ou `null`, ne corrige rien — l'API accepte n'importe quel
|
|
50
|
+
* tableau et le moteur n'a pas a dependre de la prudence de ses appelants.
|
|
51
|
+
*/
|
|
52
|
+
declare function batir(): Map<number[], Map<string, {
|
|
53
|
+
x: number;
|
|
54
|
+
y: number;
|
|
55
|
+
}>>;
|
|
56
|
+
/**
|
|
57
|
+
* Decalage a appliquer aux deux yeux pour cette forme sur cet etat, en unites de rayon
|
|
58
|
+
* de boule — le moteur le remet a son echelle.
|
|
59
|
+
*
|
|
60
|
+
* Vaut zero des que la forme n'est pas au catalogue, ce qui couvre `null` et le cercle :
|
|
61
|
+
* sur le cercle les deux profils sont le meme, donc la marge est deja celle exigee et la
|
|
62
|
+
* descente sort au premier tour. La forme relevee sur la video ne bouge donc pas, sans
|
|
63
|
+
* cas particulier.
|
|
64
|
+
*/
|
|
65
|
+
export declare function decalageDesYeux(radii: number[] | null, state: StateId, expr: string | null): {
|
|
66
|
+
x: number;
|
|
67
|
+
y: number;
|
|
68
|
+
};
|
|
69
|
+
/** Pour les tests : de quoi verifier la table sans refaire la geometrie. */
|
|
70
|
+
/** Pour les tests : de quoi chronometrer la construction de la table. */
|
|
71
|
+
export declare const POUR_TESTS: {
|
|
72
|
+
batir: typeof batir;
|
|
73
|
+
};
|
|
74
|
+
export {};
|
|
75
|
+
//# sourceMappingURL=eyefit.d.ts.map
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
// Adapted from bloub, Copyright (c) 2026 Jérémy Perret (MIT).
|
|
2
|
+
// See THIRD_PARTY_NOTICES.md for source revision and local changes.
|
|
3
|
+
/**
|
|
4
|
+
* Ou poser le visage sur une forme du personnalisateur.
|
|
5
|
+
*
|
|
6
|
+
* Les yeux vivent sur une sphere, et `radiusAtAngle` les recolle au contour reel au
|
|
7
|
+
* prorata du rayon local. Ce prorata place bien leur CENTRE, mais l'oeil a une taille :
|
|
8
|
+
* la marge qui lui reste devant le bord est multipliee par le meme facteur, donc une
|
|
9
|
+
* silhouette etroite dans sa direction le pousse contre le bord jusqu'a ce que le
|
|
10
|
+
* masque l'ouvre vers l'exterieur. La gelule apparaissait comme une encoche dans le
|
|
11
|
+
* corps sur `capsule`, `triangle`, `nuage` et `goutte`.
|
|
12
|
+
*
|
|
13
|
+
* Ce module resout le probleme UNE FOIS, au chargement, et rend une table de decalages.
|
|
14
|
+
* Ce choix est l'essentiel du correctif, bien plus que la geometrie qui suit :
|
|
15
|
+
*
|
|
16
|
+
* Resolue dans la boucle de rendu, la correction reagit a tout ce qui bouge a soixante
|
|
17
|
+
* images par seconde — la derive du regard, le pointeur, l'expression en cours de
|
|
18
|
+
* morph, le bord le plus proche qui change, l'oeil le plus contraint qui change. Sept
|
|
19
|
+
* variantes ont ete ecrites ainsi et toutes produisaient un artefact de mouvement
|
|
20
|
+
* visible : tremblement permanent, saut de direction de 26 unites quand le bord de
|
|
21
|
+
* reference basculait, grossissement brusque quand la taille entrait dans le calcul.
|
|
22
|
+
* Le defaut n'etait dans aucune de leurs geometries, il etait dans le fait de resoudre
|
|
23
|
+
* par image.
|
|
24
|
+
*
|
|
25
|
+
* Le reste du moteur ne travaille pas comme ca : les poses sont DECLAREES et il ne fait
|
|
26
|
+
* que les interpoler avec des courbes connues. Un decalage tabule rentre dans ce moule.
|
|
27
|
+
* Il ne bouge pas quand le regard derive ni quand le pointeur bouge, et sur un changement
|
|
28
|
+
* de forme ou d'expression il ne fait qu'aller d'une entree de table a l'autre, sur la
|
|
29
|
+
* courbe de ce morph. Le tremblement devient impossible par construction, au lieu d'etre
|
|
30
|
+
* repousse : interpoler entre deux constantes est monotone, alors que re-resoudre le
|
|
31
|
+
* probleme sur un regard en cours d'interpolation ne l'est pas.
|
|
32
|
+
*
|
|
33
|
+
* Corollaire agreable : le solveur n'a plus aucune contrainte de continuite, puisqu'il ne
|
|
34
|
+
* tourne pas pendant l'animation. Il peut donc sonder tout un faisceau de directions et
|
|
35
|
+
* couvrir le pire cas de la derive du regard, ce qu'une version par image ne pouvait pas
|
|
36
|
+
* se permettre.
|
|
37
|
+
*
|
|
38
|
+
* La table est une constante de module, batie a l'import a partir de donnees pures :
|
|
39
|
+
* meme nature que le calendrier de clignements de `face.ts`, deterministe et sans etat,
|
|
40
|
+
* donc sans effet sur la purete de `engine.sample(t)`.
|
|
41
|
+
*/
|
|
42
|
+
import { EXPRESSIONS } from './expressions.js';
|
|
43
|
+
import { eyePoses } from './face.js';
|
|
44
|
+
import { radiusAtAngle, toPoints } from './shape.js';
|
|
45
|
+
import { SHAPES } from './skins.js';
|
|
46
|
+
import { STATES } from './states.js';
|
|
47
|
+
/** Rayon de reference du solveur. Le decalage rendu est en unites de ce rayon. */
|
|
48
|
+
const R = 100;
|
|
49
|
+
/**
|
|
50
|
+
* Amplitudes maximales de la vie au repos, lues sur `liveliness` : `loopNoise` est
|
|
51
|
+
* borne a 1 en valeur absolue, donc ces sommes sont des bornes exactes et non des
|
|
52
|
+
* estimations.
|
|
53
|
+
*
|
|
54
|
+
* Il faut les couvrir, sinon la correction est juste sur la pose nominale et fausse une
|
|
55
|
+
* seconde plus tard : 7 degres de lacet deplacent l'oeil d'une douzaine d'unites sur une
|
|
56
|
+
* boule de rayon 100. C'est precisement ce qui faisait deborder `capsule` + `effraye`
|
|
57
|
+
* alors qu'une mesure a un seul instant le declarait bon.
|
|
58
|
+
*/
|
|
59
|
+
const DERIVE_YAW = 5.5 + 1.6;
|
|
60
|
+
const DERIVE_PITCH = 4.2 + 1.3;
|
|
61
|
+
/** Flottement du centre, en unites de rayon de boule. */
|
|
62
|
+
const DERIVE_X = 0.006;
|
|
63
|
+
const DERIVE_Y = 0.007;
|
|
64
|
+
/**
|
|
65
|
+
* Empreintes des deux yeux d'un visage, posees sur un profil.
|
|
66
|
+
*
|
|
67
|
+
* Une gelule est exactement un segment epaissi d'un disque de rayon `r`. Son image par
|
|
68
|
+
* la matrice tangente est donc un segment epaissi d'une ELLIPSE, et un disque du rayon
|
|
69
|
+
* de son grand axe la couvre : d'ou la plus grande valeur singuliere. La mesure reste
|
|
70
|
+
* ainsi conservatrice au sens strict, une marge positive garantissant que la gelule est
|
|
71
|
+
* dedans.
|
|
72
|
+
*
|
|
73
|
+
* Le clignement n'y est pas : un oeil ferme n'a pas besoin qu'on lui fasse de la place.
|
|
74
|
+
*/
|
|
75
|
+
function empreintes(visage, sil, radii) {
|
|
76
|
+
const out = [];
|
|
77
|
+
const poses = eyePoses(visage.gaze, R, visage.split);
|
|
78
|
+
for (let i = 0; i < 2; i++) {
|
|
79
|
+
const e = poses[i];
|
|
80
|
+
if (e.depth <= 0.02)
|
|
81
|
+
continue;
|
|
82
|
+
const cfg = visage.eyes[i];
|
|
83
|
+
const phi = ((cfg.tilt ?? 0) * Math.PI) / 180;
|
|
84
|
+
const cp = Math.cos(phi);
|
|
85
|
+
const sp = Math.sin(phi);
|
|
86
|
+
const ax = e.a * cp + e.c * sp;
|
|
87
|
+
const ay = e.b * cp + e.d * sp;
|
|
88
|
+
const cx = -e.a * sp + e.c * cp;
|
|
89
|
+
const cy = -e.b * sp + e.d * cp;
|
|
90
|
+
const hw = Math.max(cfg.w * R, 0.01) / 2;
|
|
91
|
+
const hh = Math.max(cfg.h * R, 0.01) / 2;
|
|
92
|
+
const r = Math.min(hw, hh);
|
|
93
|
+
// l'axe est celui de la plus grande dimension
|
|
94
|
+
const long = hh > hw;
|
|
95
|
+
const demi = long ? hh - r : hw - r;
|
|
96
|
+
// le prorata du rayon local, exactement comme le fait le moteur
|
|
97
|
+
const fit = radiusAtAngle(radii, Math.atan2(e.y, e.x) - sil.rot);
|
|
98
|
+
out.push({
|
|
99
|
+
x: e.x * fit,
|
|
100
|
+
y: e.y * fit,
|
|
101
|
+
ax: (long ? cx : ax) * demi,
|
|
102
|
+
ay: (long ? cy : ay) * demi,
|
|
103
|
+
r,
|
|
104
|
+
m: [ax, ay, cx, cy]
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Approche la plus courte entre un contour et un segment : la distance, et le vecteur
|
|
111
|
+
* qui va du contour vers le segment — le sens qui degage.
|
|
112
|
+
*
|
|
113
|
+
* Les deux sortent de la MEME passe. Les calculer separement doublait le seul vrai cout
|
|
114
|
+
* de ce module, qui est ce balayage.
|
|
115
|
+
*/
|
|
116
|
+
function approche(pts, x0, y0, x1, y1) {
|
|
117
|
+
const sx = x1 - x0;
|
|
118
|
+
const sy = y1 - y0;
|
|
119
|
+
const len2 = sx * sx + sy * sy;
|
|
120
|
+
let best = Infinity;
|
|
121
|
+
let vx = 0;
|
|
122
|
+
let vy = 0;
|
|
123
|
+
for (let i = 0; i < pts.length; i++) {
|
|
124
|
+
const p = pts[i];
|
|
125
|
+
let t = len2 > 0 ? ((p.x - x0) * sx + (p.y - y0) * sy) / len2 : 0;
|
|
126
|
+
t = t < 0 ? 0 : t > 1 ? 1 : t;
|
|
127
|
+
const ex = x0 + t * sx - p.x;
|
|
128
|
+
const ey = y0 + t * sy - p.y;
|
|
129
|
+
const d2 = ex * ex + ey * ey;
|
|
130
|
+
if (d2 < best) {
|
|
131
|
+
best = d2;
|
|
132
|
+
vx = ex;
|
|
133
|
+
vy = ey;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const d = Math.sqrt(best);
|
|
137
|
+
return { d, ux: d > 1e-9 ? vx / d : 0, uy: d > 1e-9 ? vy / d : 0 };
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Flottement du centre au repos, en unites de viewBox. Il est ajoute au rayon de la
|
|
141
|
+
* gelule : moins d'une unite, donc l'absorber ainsi coute moins cher que de multiplier les
|
|
142
|
+
* epreuves par ses quatre coins.
|
|
143
|
+
*/
|
|
144
|
+
const FLOTTEMENT = Math.hypot(DERIVE_X, DERIVE_Y) * R;
|
|
145
|
+
/** Marge de la gelule la plus serree, et le sens qui la degage. */
|
|
146
|
+
function pire(pts, emps, tx, ty) {
|
|
147
|
+
let marge = Infinity;
|
|
148
|
+
let ux = 0;
|
|
149
|
+
let uy = 0;
|
|
150
|
+
for (const e of emps) {
|
|
151
|
+
const x = e.x + tx;
|
|
152
|
+
const y = e.y + ty;
|
|
153
|
+
const a = approche(pts, x - e.ax, y - e.ay, x + e.ax, y + e.ay);
|
|
154
|
+
// fonction d'appui de l'ellipse dans la direction de l'approche
|
|
155
|
+
const [m0, m1, m2, m3] = e.m;
|
|
156
|
+
const rayon = e.r * Math.hypot(m0 * a.ux + m1 * a.uy, m2 * a.ux + m3 * a.uy) + FLOTTEMENT;
|
|
157
|
+
if (a.d - rayon < marge) {
|
|
158
|
+
marge = a.d - rayon;
|
|
159
|
+
ux = a.ux;
|
|
160
|
+
uy = a.uy;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { marge, ux, uy };
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Directions sondees et pas de la dichotomie. Le produit des deux est le cout de
|
|
167
|
+
* construction de la table, seul chiffre a surveiller ici.
|
|
168
|
+
*/
|
|
169
|
+
const DIRECTIONS = 12;
|
|
170
|
+
const DICHOTOMIE = 8;
|
|
171
|
+
/**
|
|
172
|
+
* Le decalage a poser sur les deux yeux pour cette forme, cet etat et cette expression.
|
|
173
|
+
*
|
|
174
|
+
* Une TRANSLATION commune aux deux yeux, donc une isometrie : ecart entre les yeux,
|
|
175
|
+
* tailles et inclinaisons sont conserves au pixel. Le visage est seulement pose un peu
|
|
176
|
+
* plus bas sur un corps qui n'a pas de place en haut, ce qui est le geste qu'on ferait a
|
|
177
|
+
* la main. Les variantes qui bornaient chaque oeil separement ecartaient la paire, et
|
|
178
|
+
* celles qui mettaient le visage a l'echelle rapetissaient les yeux — visiblement.
|
|
179
|
+
*
|
|
180
|
+
* La marge visee est celle du profil D'ORIGINE, pas un degagement strict : sur le cercle
|
|
181
|
+
* l'oeil exterieur frole deja le bord, 17,3 unites pour une boule de rayon 100, et c'est
|
|
182
|
+
* voulu, c'est ce qui donne le volume. Elle est plafonnee par ce que la forme offre en son
|
|
183
|
+
* centre, sinon la demande est intenable sur un corps plat.
|
|
184
|
+
*
|
|
185
|
+
* RECHERCHE DIRECTIONNELLE et non descente. On cherche la translation de plus petite
|
|
186
|
+
* norme qui tient, donc on sonde une couronne de directions et on dichotomie la distance
|
|
187
|
+
* le long de chacune. Une descente de gradient a ete ecrite d'abord et elle ne converge
|
|
188
|
+
* pas : degager la paire d'un bord la rapproche de l'autre, si bien qu'elle tatonne et ne
|
|
189
|
+
* fait que garder son meilleur essai — passer ses tours de 40 a 18 suffisait a faire
|
|
190
|
+
* reapparaitre 34 debordements. Ici le resultat ne depend pas d'une convergence : chaque
|
|
191
|
+
* direction est resolue exactement, au pas de dichotomie pres.
|
|
192
|
+
*/
|
|
193
|
+
function resous(epreuves) {
|
|
194
|
+
if (!epreuves.length)
|
|
195
|
+
return { x: 0, y: 0 };
|
|
196
|
+
/** La marge la plus serree sur toutes les epreuves, pour une translation donnee. */
|
|
197
|
+
const marge = (tx, ty) => {
|
|
198
|
+
let m = Infinity;
|
|
199
|
+
for (const ep of epreuves)
|
|
200
|
+
m = Math.min(m, pire(ep.contour, ep.empreintes, tx, ty).marge);
|
|
201
|
+
return m;
|
|
202
|
+
};
|
|
203
|
+
// Marge exigee : la plus serree que le profil d'origine tolere, sur toutes les
|
|
204
|
+
// epreuves. Puis plafonnee par le plus degage que la forme puisse offrir a la paire,
|
|
205
|
+
// son centre.
|
|
206
|
+
let requis = Infinity;
|
|
207
|
+
for (const ep of epreuves) {
|
|
208
|
+
requis = Math.min(requis, pire(ep.calContour, ep.reference, 0, 0).marge);
|
|
209
|
+
}
|
|
210
|
+
/*
|
|
211
|
+
* La course doit pouvoir atteindre le centre du corps : `wide` a des gelules de 87
|
|
212
|
+
* unites de long, et sur un triangle elles ne tiennent que vers le milieu, a une
|
|
213
|
+
* cinquantaine d'unites de leur place nominale. Une course fixe les laissait dehors.
|
|
214
|
+
*/
|
|
215
|
+
let mx = 0;
|
|
216
|
+
let my = 0;
|
|
217
|
+
const emps = epreuves[0].empreintes;
|
|
218
|
+
for (const e of emps) {
|
|
219
|
+
mx -= e.x / emps.length;
|
|
220
|
+
my -= e.y / emps.length;
|
|
221
|
+
}
|
|
222
|
+
const course = Math.max(0.35 * R, Math.hypot(mx, my) * 1.25);
|
|
223
|
+
// Plafond de la demande : ce que la forme offre en son centre, toujours atteignable.
|
|
224
|
+
requis = Math.min(requis, marge(mx, my));
|
|
225
|
+
/*
|
|
226
|
+
* Deja bon : le cas du cercle, et de toute forme assez large. La gelule doit RENTRER
|
|
227
|
+
* en plus de n'etre pas plus serree que sur le profil d'origine — sans cette seconde
|
|
228
|
+
* condition, une forme ou rien ne rentre satisfait la premiere de facon degeneree et
|
|
229
|
+
* on abandonnait. `wide` a des gelules de 87 unites de long, `notify` de 50 de
|
|
230
|
+
* diametre : sur un triangle ou une goutte elles debordent quoi qu'on fasse, et il
|
|
231
|
+
* faut alors viser le moins pire, pas renoncer.
|
|
232
|
+
*/
|
|
233
|
+
const depart = marge(0, 0);
|
|
234
|
+
if (depart >= requis && depart >= 0)
|
|
235
|
+
return { x: 0, y: 0 };
|
|
236
|
+
const cible = Math.max(requis, 0);
|
|
237
|
+
let meilleurX = 0;
|
|
238
|
+
let meilleurY = 0;
|
|
239
|
+
let meilleureNorme = Infinity;
|
|
240
|
+
// repli quand rien ne rentre : la translation qui degage le plus, sondee au passage
|
|
241
|
+
let secoursX = 0;
|
|
242
|
+
let secoursY = 0;
|
|
243
|
+
let secours = depart;
|
|
244
|
+
for (let d = 0; d < DIRECTIONS; d++) {
|
|
245
|
+
const a = (d / DIRECTIONS) * Math.PI * 2;
|
|
246
|
+
const ux = Math.cos(a);
|
|
247
|
+
const uy = Math.sin(a);
|
|
248
|
+
if (marge(ux * course, uy * course) < cible) {
|
|
249
|
+
// cette direction ne mene nulle part ; on garde quand meme le meilleur degagement
|
|
250
|
+
// pas de solution par la, mais peut-etre un meilleur degagement
|
|
251
|
+
for (const k of [0.3, 0.6, 1]) {
|
|
252
|
+
const m = marge(ux * course * k, uy * course * k);
|
|
253
|
+
if (m > secours) {
|
|
254
|
+
secours = m;
|
|
255
|
+
secoursX = ux * course * k;
|
|
256
|
+
secoursY = uy * course * k;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
// la plus courte distance qui tient, le long de cette direction
|
|
262
|
+
let bas = 0;
|
|
263
|
+
let haut = course;
|
|
264
|
+
for (let i = 0; i < DICHOTOMIE; i++) {
|
|
265
|
+
const mid = (bas + haut) / 2;
|
|
266
|
+
if (marge(ux * mid, uy * mid) >= cible)
|
|
267
|
+
haut = mid;
|
|
268
|
+
else
|
|
269
|
+
bas = mid;
|
|
270
|
+
}
|
|
271
|
+
if (haut < meilleureNorme) {
|
|
272
|
+
meilleureNorme = haut;
|
|
273
|
+
meilleurX = ux * haut;
|
|
274
|
+
meilleurY = uy * haut;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const x = meilleureNorme === Infinity ? secoursX : meilleurX;
|
|
278
|
+
const y = meilleureNorme === Infinity ? secoursY : meilleurY;
|
|
279
|
+
// rendu en unites de RAYON DE BOULE : le moteur le remet a son echelle
|
|
280
|
+
return { x: +(x / R).toFixed(6), y: +(y / R).toFixed(6) };
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Le visage a couvrir : celui de l'expression si l'etat l'accepte, le sien sinon.
|
|
284
|
+
*
|
|
285
|
+
* UNE entree de table par expression, et non un pire cas commun a toutes. Un pire cas
|
|
286
|
+
* paraissait plus sur — un decalage constant ne peut pas bouger quand l'expression change
|
|
287
|
+
* — mais il est intenable : sur une capsule, `neutre` a les yeux hauts et demande a
|
|
288
|
+
* descendre quand `effraye` les a bas et demande a monter. Aucune translation unique ne
|
|
289
|
+
* satisfait les deux, et la mesure le confirme (4 debordements de 4,8 unites).
|
|
290
|
+
*
|
|
291
|
+
* Une entree par expression n'est pas moins fluide pour autant : le moteur interpole
|
|
292
|
+
* entre DEUX CONSTANTES, ce qui est monotone par construction. Ce qui tremblait, c'etait
|
|
293
|
+
* de re-resoudre le probleme sur un regard en cours d'interpolation.
|
|
294
|
+
*/
|
|
295
|
+
function visageDe(def, pose, expr) {
|
|
296
|
+
if (def.baseFace && expr)
|
|
297
|
+
return { gaze: expr.gaze, split: expr.split, eyes: expr.eyes };
|
|
298
|
+
return { gaze: pose.gaze, split: pose.split, eyes: pose.eyes };
|
|
299
|
+
}
|
|
300
|
+
/** Les dates a echantillonner dans un etat : une seule si sa pose ne bouge pas. */
|
|
301
|
+
function dates(def) {
|
|
302
|
+
/** Tout ce dont le solveur se sert : si rien ne bouge, une date suffit. */
|
|
303
|
+
const signature = (p) => JSON.stringify([p.gaze, p.split, p.eyes, p.sil.rot, p.sil.cx, p.sil.cy, p.sil.sx, p.sil.sy]);
|
|
304
|
+
if (signature(def.pose(0)) === signature(def.pose(def.duration)))
|
|
305
|
+
return [0];
|
|
306
|
+
const n = 3;
|
|
307
|
+
return Array.from({ length: n }, (_, i) => (i / (n - 1)) * def.duration);
|
|
308
|
+
}
|
|
309
|
+
/** Le decalage d'une forme sur un etat et une expression, derive comprise. */
|
|
310
|
+
function decalagePour(def, radii, expr) {
|
|
311
|
+
const epreuves = [];
|
|
312
|
+
for (const t of dates(def)) {
|
|
313
|
+
const pose = def.pose(t);
|
|
314
|
+
const contour = toPoints({ ...pose.sil, radii }, R);
|
|
315
|
+
const calContour = toPoints(pose.sil, R);
|
|
316
|
+
const v = visageDe(def, pose, expr);
|
|
317
|
+
// Les quatre coins de la derive bornent la pose nominale, qui est leur centre : la
|
|
318
|
+
// tester en plus ne changerait aucune marge et coute une epreuve sur cinq.
|
|
319
|
+
const coins = [];
|
|
320
|
+
for (const dy of [-DERIVE_YAW, DERIVE_YAW]) {
|
|
321
|
+
for (const dp of [-DERIVE_PITCH, DERIVE_PITCH]) {
|
|
322
|
+
coins.push({
|
|
323
|
+
...v,
|
|
324
|
+
gaze: { yaw: v.gaze.yaw + dy, pitch: v.gaze.pitch + dp, roll: v.gaze.roll }
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
for (const c of coins) {
|
|
329
|
+
epreuves.push({
|
|
330
|
+
empreintes: empreintes(c, pose.sil, radii),
|
|
331
|
+
reference: empreintes(c, pose.sil, pose.sil.radii),
|
|
332
|
+
contour,
|
|
333
|
+
calContour
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return resous(epreuves);
|
|
338
|
+
}
|
|
339
|
+
/** Zero, la valeur commune a tout ce qui n'a rien a corriger. */
|
|
340
|
+
const NUL = { x: 0, y: 0 };
|
|
341
|
+
/** Clef d'une entree : l'etat, et l'expression quand l'etat l'accepte. */
|
|
342
|
+
const clef = (state, expr) => `${state}|${expr ?? ''}`;
|
|
343
|
+
/**
|
|
344
|
+
* Table des decalages, batie a l'import : une entree par (forme, etat a corps de base,
|
|
345
|
+
* expression). Seuls `idle` et `swirl` portent le visage de repos, donc seuls eux se
|
|
346
|
+
* declinent par expression — les trois autres etats a corps de base ont un visage releve
|
|
347
|
+
* sur la video et une seule entree.
|
|
348
|
+
*
|
|
349
|
+
* Clef par REFERENCE du tableau de rayons, ce qui est deja la convention du moteur : ses
|
|
350
|
+
* gardes `radii === this.shape` et `expression === this.expr` reposent sur la meme
|
|
351
|
+
* stabilite. Un profil inconnu, ou `null`, ne corrige rien — l'API accepte n'importe quel
|
|
352
|
+
* tableau et le moteur n'a pas a dependre de la prudence de ses appelants.
|
|
353
|
+
*/
|
|
354
|
+
function batir() {
|
|
355
|
+
return new Map(SHAPES.map((forme) => {
|
|
356
|
+
const par = new Map();
|
|
357
|
+
for (const def of STATES) {
|
|
358
|
+
if (!def.baseBody)
|
|
359
|
+
continue;
|
|
360
|
+
const expressions = def.baseFace ? [null, ...EXPRESSIONS] : [null];
|
|
361
|
+
for (const expr of expressions) {
|
|
362
|
+
par.set(clef(def.id, expr?.id ?? null), decalagePour(def, forme.radii, expr));
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return [forme.radii, par];
|
|
366
|
+
}));
|
|
367
|
+
}
|
|
368
|
+
// The theme uses the authored body, not the customiser. Avoid building its
|
|
369
|
+
// fitting catalogue at startup; preserve the solver for a future custom shape.
|
|
370
|
+
let DECALAGES;
|
|
371
|
+
const CATALOG_PROFILES = new Set(SHAPES.map(shape => shape.radii));
|
|
372
|
+
/**
|
|
373
|
+
* Decalage a appliquer aux deux yeux pour cette forme sur cet etat, en unites de rayon
|
|
374
|
+
* de boule — le moteur le remet a son echelle.
|
|
375
|
+
*
|
|
376
|
+
* Vaut zero des que la forme n'est pas au catalogue, ce qui couvre `null` et le cercle :
|
|
377
|
+
* sur le cercle les deux profils sont le meme, donc la marge est deja celle exigee et la
|
|
378
|
+
* descente sort au premier tour. La forme relevee sur la video ne bouge donc pas, sans
|
|
379
|
+
* cas particulier.
|
|
380
|
+
*/
|
|
381
|
+
export function decalageDesYeux(radii, state, expr) {
|
|
382
|
+
if (!radii || !CATALOG_PROFILES.has(radii))
|
|
383
|
+
return NUL;
|
|
384
|
+
const par = (DECALAGES ??= batir()).get(radii);
|
|
385
|
+
if (!par)
|
|
386
|
+
return NUL;
|
|
387
|
+
// un etat sans visage de repos n'a qu'une entree, quelle que soit l'expression
|
|
388
|
+
return par.get(clef(state, expr)) ?? par.get(clef(state, null)) ?? NUL;
|
|
389
|
+
}
|
|
390
|
+
/** Pour les tests : de quoi verifier la table sans refaire la geometrie. */
|
|
391
|
+
/** Pour les tests : de quoi chronometrer la construction de la table. */
|
|
392
|
+
export const POUR_TESTS = { batir };
|
|
393
|
+
//# sourceMappingURL=eyefit.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Demi-ecart des yeux sur la sphere, en degres (separation totale ~31deg). */
|
|
2
|
+
export declare const EYE_SPLIT = 15.46;
|
|
3
|
+
/** Taille de l'oeil au repos, en unites de rayon de boule. */
|
|
4
|
+
export declare const EYE_W = 0.186;
|
|
5
|
+
export declare const EYE_H = 0.412;
|
|
6
|
+
/** Orientation de tete au repos, ajustee sur les frames de reference. */
|
|
7
|
+
export declare const REST_GAZE: HeadGaze;
|
|
8
|
+
export interface EyePose {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
/** matrice tangente 2x2 : [a b c d] au sens SVG matrix(a,b,c,d,e,f) */
|
|
12
|
+
a: number;
|
|
13
|
+
b: number;
|
|
14
|
+
c: number;
|
|
15
|
+
d: number;
|
|
16
|
+
/** composante z de la normale : > 0 = face visible */
|
|
17
|
+
depth: number;
|
|
18
|
+
}
|
|
19
|
+
export interface HeadGaze {
|
|
20
|
+
/** lacet, degres, positif = regarde a droite */
|
|
21
|
+
yaw: number;
|
|
22
|
+
/** tangage, degres, positif = regarde en haut */
|
|
23
|
+
pitch: number;
|
|
24
|
+
/** roulis, degres, inclinaison de la tete */
|
|
25
|
+
roll: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Repere de la tete puis des deux yeux.
|
|
29
|
+
* Repere ecran : x a droite, y vers le bas, z vers le spectateur.
|
|
30
|
+
* L'indice 0 est l'oeil interieur, l'indice 1 l'oeil exterieur.
|
|
31
|
+
*/
|
|
32
|
+
export declare function eyePoses(gaze: HeadGaze, scale: number, split?: number): [EyePose, EyePose];
|
|
33
|
+
/**
|
|
34
|
+
* Vie au repos : derive lente du regard, saccades, clignements.
|
|
35
|
+
*
|
|
36
|
+
* Fonction pure du temps (aucun etat interne), donc pause, reprise et saut a
|
|
37
|
+
* une date arbitraire donnent toujours la meme image. Les valeurs sont des
|
|
38
|
+
* ECARTS a ajouter a la pose de l'etat courant.
|
|
39
|
+
*/
|
|
40
|
+
export interface Liveliness {
|
|
41
|
+
dYaw: number;
|
|
42
|
+
dPitch: number;
|
|
43
|
+
dRoll: number;
|
|
44
|
+
/** 1 = oeil ouvert, 0 = ferme (ecrasement vertical en repere ecran) */
|
|
45
|
+
lid: number;
|
|
46
|
+
driftX: number;
|
|
47
|
+
driftY: number;
|
|
48
|
+
breath: number;
|
|
49
|
+
}
|
|
50
|
+
export interface LivelinessOptions {
|
|
51
|
+
wander?: number;
|
|
52
|
+
blink?: boolean;
|
|
53
|
+
float?: boolean;
|
|
54
|
+
}
|
|
55
|
+
export declare function liveliness(t: number, opt?: LivelinessOptions): Liveliness;
|
|
56
|
+
/**
|
|
57
|
+
* Le clignement est un ecrasement VERTICAL en repere ecran autour du centre de
|
|
58
|
+
* l'oeil (mesure : la largeur de bbox est conservee, la hauteur tombe a ~0.35),
|
|
59
|
+
* pas un retrecissement le long de l'axe incline de la gelule. On le compose
|
|
60
|
+
* donc apres la matrice tangente, en n'affectant que les sorties en y.
|
|
61
|
+
*/
|
|
62
|
+
export declare function blinkScale(lid: number): number;
|
|
63
|
+
//# sourceMappingURL=face.d.ts.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Adapted from bloub, Copyright (c) 2026 Jérémy Perret (MIT).
|
|
2
|
+
// See THIRD_PARTY_NOTICES.md for source revision and local changes.
|
|
3
|
+
import { clamp, createRng, loopNoise } from './math.js';
|
|
4
|
+
/** Demi-ecart des yeux sur la sphere, en degres (separation totale ~31deg). */
|
|
5
|
+
export const EYE_SPLIT = 15.46;
|
|
6
|
+
/** Taille de l'oeil au repos, en unites de rayon de boule. */
|
|
7
|
+
export const EYE_W = 0.186;
|
|
8
|
+
export const EYE_H = 0.412;
|
|
9
|
+
/** Orientation de tete au repos, ajustee sur les frames de reference. */
|
|
10
|
+
export const REST_GAZE = { yaw: 28.49, pitch: 28.62, roll: -13 };
|
|
11
|
+
const deg = (d) => (d * Math.PI) / 180;
|
|
12
|
+
/** Fait tourner deux vecteurs d'un repere orthonorme dans leur plan commun. */
|
|
13
|
+
function spin(u, v, angle) {
|
|
14
|
+
const c = Math.cos(angle);
|
|
15
|
+
const s = Math.sin(angle);
|
|
16
|
+
return [
|
|
17
|
+
[u[0] * c + v[0] * s, u[1] * c + v[1] * s, u[2] * c + v[2] * s],
|
|
18
|
+
[v[0] * c - u[0] * s, v[1] * c - u[1] * s, v[2] * c - u[2] * s]
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Repere de la tete puis des deux yeux.
|
|
23
|
+
* Repere ecran : x a droite, y vers le bas, z vers le spectateur.
|
|
24
|
+
* L'indice 0 est l'oeil interieur, l'indice 1 l'oeil exterieur.
|
|
25
|
+
*/
|
|
26
|
+
export function eyePoses(gaze, scale, split = EYE_SPLIT) {
|
|
27
|
+
let f = [0, 0, 1];
|
|
28
|
+
let right = [1, 0, 0];
|
|
29
|
+
let down = [0, 1, 0];
|
|
30
|
+
[f, right] = spin(f, right, deg(gaze.yaw));
|
|
31
|
+
[down, f] = spin(down, f, deg(gaze.pitch));
|
|
32
|
+
[right, down] = spin(right, down, deg(gaze.roll));
|
|
33
|
+
const build = (side) => {
|
|
34
|
+
const [ef, er] = spin(f, right, deg(split * side));
|
|
35
|
+
return {
|
|
36
|
+
x: ef[0] * scale,
|
|
37
|
+
y: ef[1] * scale,
|
|
38
|
+
a: er[0],
|
|
39
|
+
b: er[1],
|
|
40
|
+
c: down[0],
|
|
41
|
+
d: down[1],
|
|
42
|
+
depth: ef[2]
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
return [build(-1), build(1)];
|
|
46
|
+
}
|
|
47
|
+
const BLINK_RNG = createRng(0x5eed);
|
|
48
|
+
/** Calendrier de clignements pre-tire : deterministe et sans etat. */
|
|
49
|
+
const BLINKS = (() => {
|
|
50
|
+
const out = [];
|
|
51
|
+
let t = 1.4;
|
|
52
|
+
while (t < 900) {
|
|
53
|
+
out.push(t);
|
|
54
|
+
// 1.9 a 4.6 s entre deux clignements, plus un double clignement parfois
|
|
55
|
+
t += 1.9 + BLINK_RNG() * 2.7;
|
|
56
|
+
if (BLINK_RNG() < 0.18) {
|
|
57
|
+
out.push(t);
|
|
58
|
+
t += 0.24;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
})();
|
|
63
|
+
/** Mesure : 1 a 2 frames a 10 fps. */
|
|
64
|
+
const BLINK_DUR = 0.18;
|
|
65
|
+
function blinkLid(t) {
|
|
66
|
+
// A desktop companion can remain mounted longer than the 15-minute demo.
|
|
67
|
+
t = ((t % 900) + 900) % 900;
|
|
68
|
+
for (let i = 0; i < BLINKS.length; i++) {
|
|
69
|
+
const start = BLINKS[i];
|
|
70
|
+
if (t < start)
|
|
71
|
+
break;
|
|
72
|
+
const k = (t - start) / BLINK_DUR;
|
|
73
|
+
if (k >= 0 && k <= 1) {
|
|
74
|
+
// fermeture rapide, reouverture un peu plus lente
|
|
75
|
+
return k < 0.45 ? 1 - k / 0.45 : (k - 0.45) / 0.55;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
export function liveliness(t, opt = {}) {
|
|
81
|
+
const { wander = 1, blink = true, float = true } = opt;
|
|
82
|
+
// Periodes premieres entre elles : la derive ne se repete jamais a l'oeil.
|
|
83
|
+
return {
|
|
84
|
+
dYaw: (loopNoise(t, 11.3, 0.4) * 5.5 + loopNoise(t, 3.7, 2.1) * 1.6) * wander,
|
|
85
|
+
dPitch: (loopNoise(t, 9.1, 1.3) * 4.2 + loopNoise(t, 4.3, 0.7) * 1.3) * wander,
|
|
86
|
+
dRoll: loopNoise(t, 13.7, 3.2) * 2.2 * wander,
|
|
87
|
+
lid: blink ? blinkLid(t) : 1,
|
|
88
|
+
// Au repos la video est quasiment immobile (centre stable a +-0.003, rayon
|
|
89
|
+
// constant) : toute la vie passe par le regard et les clignements. On garde
|
|
90
|
+
// juste de quoi ne pas figer completement l'image.
|
|
91
|
+
driftX: float ? loopNoise(t, 7.9, 1.9) * 0.006 : 0,
|
|
92
|
+
driftY: float ? loopNoise(t, 5.3, 0.3) * 0.007 : 0,
|
|
93
|
+
// La largeur est constante, seule la hauteur respire tres legerement.
|
|
94
|
+
breath: float ? 1 + Math.sin((t / 3.4) * Math.PI * 2) * 0.005 : 1
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Le clignement est un ecrasement VERTICAL en repere ecran autour du centre de
|
|
99
|
+
* l'oeil (mesure : la largeur de bbox est conservee, la hauteur tombe a ~0.35),
|
|
100
|
+
* pas un retrecissement le long de l'axe incline de la gelule. On le compose
|
|
101
|
+
* donc apres la matrice tangente, en n'affectant que les sorties en y.
|
|
102
|
+
*/
|
|
103
|
+
export function blinkScale(lid) {
|
|
104
|
+
return 0.06 + 0.94 * clamp(lid);
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=face.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export declare const TAU: number;
|
|
2
|
+
export declare const clamp: (v: number, lo?: number, hi?: number) => number;
|
|
3
|
+
export declare const lerp: (a: number, b: number, t: number) => number;
|
|
4
|
+
export type Easing = (t: number) => number;
|
|
5
|
+
/**
|
|
6
|
+
* Mesure sur la video : les transitions sont des ease-out exponentiels, sans
|
|
7
|
+
* depassement du corps. Les seuls effets de ressort sont locaux (le pop de la
|
|
8
|
+
* pastille de notification, l'ouverture des yeux) et sont ecrits directement
|
|
9
|
+
* dans l'etat concerne.
|
|
10
|
+
*/
|
|
11
|
+
export declare const easings: {
|
|
12
|
+
easeOutCubic: (t: number) => number;
|
|
13
|
+
easeInOutCubic: (t: number) => number;
|
|
14
|
+
easeOutQuint: (t: number) => number;
|
|
15
|
+
};
|
|
16
|
+
/** Bruit 1D periodique : boucle sans couture sur `period`, utile pour la derive du regard. */
|
|
17
|
+
export declare function loopNoise(t: number, period: number, seed?: number): number;
|
|
18
|
+
/** PRNG deterministe (mulberry32) : meme sequence a chaque lecture. */
|
|
19
|
+
export declare function createRng(seed: number): () => number;
|
|
20
|
+
/** Arrondi court : divise par ~2 le poids des chaines de path generees a 60 fps. */
|
|
21
|
+
export declare const r2: (v: number) => number;
|
|
22
|
+
//# sourceMappingURL=math.d.ts.map
|