Avatar-Bewegungen mit nur 115 Bytes pro Frame
Posy ist ein offenes Protokoll, das Körper-, Finger- und Gesichtsbewegungen von Avataren in Echtzeit zwischen mehreren Teilnehmern über einen WebRTC-Datenkanal oder einen WebSocket übertragen kann.
- Header + bone_mask16
- 13 Knochenrotationen52
- Position + Hüfthöhe8
- Finger, beide Hände24
- 13 Emotes + Blick15
Spec lesen
Demo öffnen
Frame bauen
Apache-2.0 · Kanal-Label avatar-pose · Protokoll posy/1 · Spec und Demo sind nur auf Englisch
Ist es was für deine App?
Optimal, wenn
- Avatarbewegungen vieler Nutzer übertragen werden
- Bandbreite und Fehlertoleranz ein Thema sind
- Neben Fullbody & Face weitere Extras benötigt werden
- Bewegungsdaten schnell online übertragen werden sollen
Eher nichts, wenn
- Motion-Capture verlustfrei aufgezeichnet werden soll (nimm VMC)
- Das Ganze eh nur im Heimnetzwerk bleibt (geht, bringt aber wenig)
Funktionsweise
Jeder Bewegungs-Frame geht vom Sender zum Server und von dort zu den Empfängern. Der Server gibt Takt, Uhrzeit und Berechtigungen vor. Er braucht kaum Power, kann also locker auch auf einem lokalen PC oder einem kleinen gehosteten VPS laufen.
- Der Sender deklariert, dann sendet er. Er deklariert die Datentypen, die sein Tracker liefert und sein Avatar zeigen kann, und sendet nur diese. Ein verlorener Frame ist weg und wird nicht neu gesendet.
- Der Server serviert. Zumindest solange die Länge der Frames dem entspricht, was dem Sender gewährt wurde. Wird ihm was Falsches untergeschoben, verwirft er den ganzen Frame, ohne die Rotationen überhaupt anzufassen.
- Der Empfänger rendert den ganzen Bumms. Jeder Empfänger verwendet die Daten der Frames, um die Avatarbewegungen darzustellen, interpoliert zwischen den Frames und überbrückt kurze Lücken.
- Posy avatar space (§3)
- Ist identisch zum normalisierten Rig der VRM-1.0-Runtime. Fertig. Wir haben das Rad nicht neu erfunden.
- Humanoides steht in §3, der Rest in §2.6
- Katzenohren, Flügel, Schwänze und weitere Gliedmaßen sind optional deklarierte Extras, alles, was die Avatar-Datei so hergibt.
Einen Frame bauen
Links kann man Knochen und Blöcke an- und ausschalten, rechts sieht man den resultierenden Frame.
Quaternion in 4 Bytes (§5.3)
Wir sparen bei jedem Knochen, indem wir die größte der vier Komponenten weglassen und die anderen drei auf je 10 Bit quantisieren.
- normalisiertes Quaternion (x, y, z, w)
–Die markierte Komponente (?) wird weggelassen und beim Dekodieren neu berechnet.
- gepacktes u32
–- Bit-Layout: Index (2) a (10) b (10) c (10)
–- Fehler nach Kodieren und Dekodieren
–Im Worst Case ≈ 0,25°. Bei 0 / 0 / 0 stehen hier ≈ 0,14°, weil die Identität genau auf einer Rundungsgrenze liegt. Sieht man beides nicht.
Bandbreite
Jeder User mit Tracking lädt jeweils einen Stream hoch und der Server schickt ihn an alle Teilnehmer. Dem User kann es also egal sein, wie viele Leute dabei sind, solange er nicht selbst den Server hostet. Der Server hingegen muss alles an alle weiterleiten, da kann sich die Bandbreite schnell summieren.
–
| Raum | – |
| Ein Zuschauer empfängt | – |
| Ein Sender empfängtdie Bewegungen aller anderen, ohne die eigenen | – |
| Der Server empfängt | – |
| Pro Sender Upload bei Full/Normal/Minimal | – / – / – kbit/s |
Tabellen der Spec
Der vollständige und normative Text ist spec/Posy.md, auf Englisch.
Eine 64-Bit-Maske. Ist Bit n gesetzt, ist das Quaternion von Knochen n vorhanden; die Quaternionen folgen in aufsteigender Bit-Reihenfolge. Die Farben zeigen den deklarierten Typ, zu dem ein Bit gehört. Setzt ein Frame ein Bit, dessen Typ dem Sender nicht gewährt wurde, oder eines der Bits 55–63, lehnt der Server ihn ab.
Ein Byte pro Slot, 0–255 für 0,0–1,0. Beide Blöcke enden mit zwei i8-Blick-Bytes, Yaw und Pitch, ±127 für ±45°: die Blickrichtung, vom Kopf aus gemessen. Die Perfect-Sync-Reihenfolge sind die 52 ARKit-Namen, nach Code-Unit sortiert. Eine Zunge und jedes Emote darüber hinaus ist ein deklarierter Extra-Wert.
24 Bytes: linke Hand (12 B), dann rechte Hand (12 B). Pro Hand:
| Offset | Typ | Feld |
|---|
Curl (0 = gestreckt … 255 = ganz gebeugt) beugt alle drei Gelenke eines Fingers: Grundgelenk 90°, Mittelgelenk 110°, Endgelenk 70°, um Z, zur Handfläche hin.
Splay (−127…+127 für −30°…+30°) dreht das Grundgelenk um Y; positiv heißt: zur Daumenseite.
Der Daumen wird von seinen drei Bytes gemeinsam gesteuert: Splay ±90° in der Ebene der Handfläche, Opposition 0–60° zur Handfläche hin mit einer gleich großen Rollbewegung, Curl 60° und 80° an den beiden äußeren Gelenken. Ein Empfänger misst einmal, wie der Daumen seines Avatars in Ruhe steht, und dreht das Ergebnis entsprechend. Die Definition steht in Spec §5.5; die Pose-Vektoren f01–f09 legen sie fest.
Ein Frame enthält diesen Block oder Fingerknochen pro Gelenk (Bits 25–54), nie beides. Ohne beides zeigt ein Empfänger die Ruhehand.
Um Fehler kümmert sich der Server. Ein Empfänger prüft die Länge eines Frames und sonst nichts: er sieht nur Frames, die der Server durchgelassen hat.
| Code | Bedeutung | Was der Server tut |
|---|
Posy implementieren
- Die Spec lesen und implementieren. §3 (Avatar-Raum), §4 (Knochen) und §5 (Pakete) von
spec/Posy.mdsind das, wo jede Implementierung übereinstimmen muss. §2 beschreibt den Session Layer. Hier kannst du eigentlich schon aufhören, der Rest ist nice to have. - Referenz-Codec nutzen. Das
posy-protocolauf npm (reference/js) ist in TypeScript geschrieben, hat keine Laufzeit-Abhängigkeiten und ist per se nur eine Implementierungshilfe. - Teste deine Implementierung mit den
testvectors/frames. Sie prüfen Bytes: jede.binwird mit ihrer.jsonverglichen.testvectors/posescheckt hauptsächlich, ob irgendwas aus Versehen invertiert wurde. - Schau dir die Loopback-Demo an. Sie simuliert einen verlustbehafteten Kanal, ergo scheiß Internet.
npm install posy-protocol