@chatlychat/world 0.0.0-stage → 0.1.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +21 -0
  3. package/README.md +120 -2
  4. package/dist/agent.d.ts +59 -0
  5. package/dist/agent.d.ts.map +1 -0
  6. package/dist/agent.js +232 -0
  7. package/dist/agent.js.map +1 -0
  8. package/dist/bubble.d.ts +36 -0
  9. package/dist/bubble.d.ts.map +1 -0
  10. package/dist/bubble.js +108 -0
  11. package/dist/bubble.js.map +1 -0
  12. package/dist/capabilities.d.ts +77 -0
  13. package/dist/capabilities.d.ts.map +1 -0
  14. package/dist/capabilities.js +118 -0
  15. package/dist/capabilities.js.map +1 -0
  16. package/dist/cards.d.ts +51 -0
  17. package/dist/cards.d.ts.map +1 -0
  18. package/dist/cards.js +136 -0
  19. package/dist/cards.js.map +1 -0
  20. package/dist/composer.d.ts +25 -0
  21. package/dist/composer.d.ts.map +1 -0
  22. package/dist/composer.js +53 -0
  23. package/dist/composer.js.map +1 -0
  24. package/dist/examples/tsconfig.tsbuildinfo +1 -0
  25. package/dist/handlers.d.ts +26 -0
  26. package/dist/handlers.d.ts.map +1 -0
  27. package/dist/handlers.js +41 -0
  28. package/dist/handlers.js.map +1 -0
  29. package/dist/index.d.ts +26 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +24 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/input.d.ts +46 -0
  34. package/dist/input.d.ts.map +1 -0
  35. package/dist/input.js +89 -0
  36. package/dist/input.js.map +1 -0
  37. package/dist/layout.d.ts +24 -0
  38. package/dist/layout.d.ts.map +1 -0
  39. package/dist/layout.js +69 -0
  40. package/dist/layout.js.map +1 -0
  41. package/dist/model.d.ts +25 -0
  42. package/dist/model.d.ts.map +1 -0
  43. package/dist/model.js +60 -0
  44. package/dist/model.js.map +1 -0
  45. package/dist/motion.d.ts +27 -0
  46. package/dist/motion.d.ts.map +1 -0
  47. package/dist/motion.js +65 -0
  48. package/dist/motion.js.map +1 -0
  49. package/dist/panel.d.ts +77 -0
  50. package/dist/panel.d.ts.map +1 -0
  51. package/dist/panel.js +214 -0
  52. package/dist/panel.js.map +1 -0
  53. package/dist/selection.d.ts +14 -0
  54. package/dist/selection.d.ts.map +1 -0
  55. package/dist/selection.js +21 -0
  56. package/dist/selection.js.map +1 -0
  57. package/dist/text.d.ts +26 -0
  58. package/dist/text.d.ts.map +1 -0
  59. package/dist/text.js +100 -0
  60. package/dist/text.js.map +1 -0
  61. package/dist/theme-apply.d.ts +19 -0
  62. package/dist/theme-apply.d.ts.map +1 -0
  63. package/dist/theme-apply.js +88 -0
  64. package/dist/theme-apply.js.map +1 -0
  65. package/dist/types.d.ts +102 -0
  66. package/dist/types.d.ts.map +1 -0
  67. package/dist/types.js +41 -0
  68. package/dist/types.js.map +1 -0
  69. package/dist/visemes.d.ts +28 -0
  70. package/dist/visemes.d.ts.map +1 -0
  71. package/dist/visemes.js +42 -0
  72. package/dist/visemes.js.map +1 -0
  73. package/dist/world-api.d.ts +39 -0
  74. package/dist/world-api.d.ts.map +1 -0
  75. package/dist/world-api.js +2 -0
  76. package/dist/world-api.js.map +1 -0
  77. package/dist/world.d.ts +5 -0
  78. package/dist/world.d.ts.map +1 -0
  79. package/dist/world.js +573 -0
  80. package/dist/world.js.map +1 -0
  81. package/package.json +67 -3
package/dist/model.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * glTF loading and the `anchor` a model asks for.
3
+ *
4
+ * Chatly references a model and never fetches, validates or sizes one, so
5
+ * this is where the host's world does: three's own GLTFLoader (imported
6
+ * lazily, so a conversation with no models never pays for it), the block's
7
+ * `scale` as a multiplier on the file's own size, and `anchor` as the named
8
+ * point of the model that sits at the placement's origin.
9
+ */
10
+ import { Box3, Group, Vector3 } from 'three';
11
+ export const MODEL_ANCHORS = ['center', 'base', 'top', 'back'];
12
+ export const gltfLoader = () => {
13
+ let loader = null;
14
+ return async (url) => {
15
+ if (loader === null) {
16
+ const { GLTFLoader } = await import('three/examples/jsm/loaders/GLTFLoader.js');
17
+ loader = new GLTFLoader();
18
+ }
19
+ return loader.loadAsync(url);
20
+ };
21
+ };
22
+ /** Load each URL once; every caller gets its own clone, because a scene can have one parent. */
23
+ export const cachedLoader = (load) => {
24
+ const cache = new Map();
25
+ return async (url) => {
26
+ let p = cache.get(url);
27
+ if (p === undefined) {
28
+ p = load(url);
29
+ cache.set(url, p);
30
+ // A failure is not cached: the next card gets a fresh attempt.
31
+ p.catch(() => cache.delete(url));
32
+ }
33
+ const m = await p;
34
+ return { scene: m.scene.clone(true), animations: m.animations };
35
+ };
36
+ };
37
+ /** The translation that moves `anchor` of `box` to the origin. */
38
+ export function anchorOffset(box, anchor) {
39
+ const c = box.getCenter(new Vector3());
40
+ const p = anchor === 'base'
41
+ ? new Vector3(c.x, box.min.y, c.z)
42
+ : anchor === 'top'
43
+ ? new Vector3(c.x, box.max.y, c.z)
44
+ : anchor === 'back'
45
+ ? new Vector3(c.x, c.y, box.min.z)
46
+ : c;
47
+ return p.negate();
48
+ }
49
+ /** Wrap a loaded scene so its scale and anchor are applied and the wrapper sits at the origin. */
50
+ export function fitModel(model, scale = 1, anchor = 'center') {
51
+ const inner = new Group();
52
+ inner.add(model);
53
+ inner.scale.setScalar(scale);
54
+ inner.updateMatrixWorld(true);
55
+ model.position.copy(anchorOffset(new Box3().setFromObject(inner), anchor).divideScalar(scale));
56
+ const outer = new Group();
57
+ outer.add(inner);
58
+ return outer;
59
+ }
60
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Entrance and pick animation, and the one switch that stops all of it.
3
+ *
4
+ * Reduced motion is a viewer's accessibility setting, so it is enforced HERE
5
+ * rather than asked of each caller: with it on, `run` applies the final frame
6
+ * synchronously and schedules nothing. A renderer that animates through this
7
+ * cannot forget to honour it.
8
+ */
9
+ import type { Object3D } from 'three';
10
+ export type Easing = (t: number) => number;
11
+ export declare const easeOut: Easing;
12
+ export declare class Motion {
13
+ private readonly tweens;
14
+ reduced: boolean;
15
+ constructor(reduced: boolean);
16
+ /** `apply(0..1)`; always ends with `apply(1)`. */
17
+ run(durationMs: number, apply: (t: number) => void): void;
18
+ update(dtMs: number): void;
19
+ get running(): number;
20
+ clear(): void;
21
+ }
22
+ /** Set opacity on every material under `obj`. Materials are made transparent first. */
23
+ export declare function setOpacity(obj: Object3D, k: number): void;
24
+ export type Entrance = 'fade' | 'scale' | 'slide' | 'none';
25
+ /** The theme's `motionEntrance` applied to a freshly built object. */
26
+ export declare function enter(motion: Motion, obj: Object3D, kind: Entrance, durationMs?: number): void;
27
+ //# sourceMappingURL=motion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"motion.d.ts","sourceRoot":"","sources":["../src/motion.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtC,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;AAC3C,eAAO,MAAM,OAAO,EAAE,MAAgC,CAAC;AAQvD,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoB;IACpC,OAAO,EAAE,OAAO,CAAC;gBAEL,OAAO,EAAE,OAAO;IAInC,kDAAkD;IAC3C,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IASzD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAWjC,IAAW,OAAO,IAAI,MAAM,CAE3B;IAEM,KAAK,IAAI,IAAI;CAGrB;AAED,uFAAuF;AACvF,wBAAgB,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAUzD;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;AAE3D,sEAAsE;AACtE,wBAAgB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,SAAM,GAAG,IAAI,CAe3F"}
package/dist/motion.js ADDED
@@ -0,0 +1,65 @@
1
+ export const easeOut = (t) => 1 - (1 - t) ** 3;
2
+ export class Motion {
3
+ tweens = new Set();
4
+ reduced;
5
+ constructor(reduced) {
6
+ this.reduced = reduced;
7
+ }
8
+ /** `apply(0..1)`; always ends with `apply(1)`. */
9
+ run(durationMs, apply) {
10
+ if (this.reduced || durationMs <= 0) {
11
+ apply(1);
12
+ return;
13
+ }
14
+ apply(0);
15
+ this.tweens.add({ elapsed: 0, duration: durationMs, apply });
16
+ }
17
+ update(dtMs) {
18
+ for (const tw of [...this.tweens]) {
19
+ tw.elapsed += dtMs;
20
+ const t = Math.min(1, tw.elapsed / tw.duration);
21
+ tw.apply(easeOut(t));
22
+ if (t >= 1) {
23
+ this.tweens.delete(tw);
24
+ }
25
+ }
26
+ }
27
+ get running() {
28
+ return this.tweens.size;
29
+ }
30
+ clear() {
31
+ this.tweens.clear();
32
+ }
33
+ }
34
+ /** Set opacity on every material under `obj`. Materials are made transparent first. */
35
+ export function setOpacity(obj, k) {
36
+ obj.traverse((o) => {
37
+ const mats = o.material;
38
+ for (const m of Array.isArray(mats) ? mats : mats === undefined ? [] : [mats]) {
39
+ const mat = m;
40
+ mat.userData.base ??= mat.opacity;
41
+ mat.transparent = true;
42
+ mat.opacity = mat.userData.base * k;
43
+ }
44
+ });
45
+ }
46
+ /** The theme's `motionEntrance` applied to a freshly built object. */
47
+ export function enter(motion, obj, kind, durationMs = 250) {
48
+ if (kind === 'none') {
49
+ return;
50
+ }
51
+ const y = obj.position.y;
52
+ const s = obj.scale.x;
53
+ motion.run(durationMs, (t) => {
54
+ if (kind === 'fade') {
55
+ setOpacity(obj, t);
56
+ }
57
+ else if (kind === 'scale') {
58
+ obj.scale.setScalar(Math.max(0.001, s * t));
59
+ }
60
+ else {
61
+ obj.position.y = y - 0.1 * (1 - t);
62
+ }
63
+ });
64
+ }
65
+ //# sourceMappingURL=motion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"motion.js","sourceRoot":"","sources":["../src/motion.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The world-space panel and its composer, and how they follow the viewer.
3
+ *
4
+ * A panel is NEVER head-locked. A surface welded to the head is nauseating
5
+ * within seconds, so a `locked` placement is honoured as `lazy` — the host
6
+ * abilities the world resolves against simply do not offer it. The three
7
+ * modes are:
8
+ *
9
+ * - `fixed`: placed once, where the viewer was looking, and left there;
10
+ * - `lazy`: left alone until the viewer turns or walks past a dead zone, then
11
+ * eased into place (snapped, with reduced motion);
12
+ * - `wrist`: carried on a host-supplied controller or hand.
13
+ */
14
+ import { Group, Vector3, type Camera, type Object3D } from 'three';
15
+ import type { PanelFollow } from './types.js';
16
+ import type { ComposerRenderProps, PanelRenderProps, ResolvedWorldPlacement } from '@chatlychat/core';
17
+ /** ─────────────────────────── follow math (pure) ─────────────────────────── */
18
+ export interface FollowPose {
19
+ readonly x: number;
20
+ readonly y: number;
21
+ readonly z: number;
22
+ readonly yaw: number;
23
+ }
24
+ export interface FollowState extends FollowPose {
25
+ readonly chasing: boolean;
26
+ }
27
+ export declare const DEAD_YAW: number;
28
+ export declare const DEAD_METRES = 0.5;
29
+ export declare const wrapAngle: (a: number) => number;
30
+ /**
31
+ * One step of lazy follow, with hysteresis: the panel stays put until the
32
+ * viewer leaves the dead zone, then chases until it has all but arrived, so it
33
+ * never creeps on every small head movement.
34
+ */
35
+ export declare function lazyStep(cur: FollowState, want: FollowPose, dtMs: number, reduced: boolean): FollowState;
36
+ /** The follow mode a resolved placement means. `locked` never survives resolution, but be sure. */
37
+ export declare function followMode(placement: ResolvedWorldPlacement, override?: PanelFollow): PanelFollow;
38
+ export interface RigContext {
39
+ readonly camera: Camera;
40
+ readonly reduced: boolean;
41
+ readonly agent: Object3D | null;
42
+ readonly place: Object3D | Vector3 | null;
43
+ readonly wrist: Object3D | null;
44
+ }
45
+ /** Holds the panel + composer group and moves it per the follow mode. */
46
+ export declare class PanelRig {
47
+ private readonly parent;
48
+ private readonly placement;
49
+ private mode;
50
+ readonly group: Group;
51
+ private state;
52
+ private placed;
53
+ private wristed;
54
+ constructor(parent: Object3D, placement: ResolvedWorldPlacement, mode: PanelFollow);
55
+ get follow(): PanelFollow;
56
+ /** Where the placement wants the panel, in parent-local space. */
57
+ private want;
58
+ update(dtMs: number, ctx: RigContext): void;
59
+ }
60
+ export declare const PANEL_LABELS: {
61
+ readonly you: "You";
62
+ readonly agent: "Agent";
63
+ readonly typing: "is typing…";
64
+ };
65
+ export declare function defaultPanel(props: PanelRenderProps): Object3D;
66
+ /** What the default composer reads and calls. The world owns one; an override uses `props.send`. */
67
+ export interface ComposerState {
68
+ readonly draft: string;
69
+ readonly submit: () => void;
70
+ readonly requestInput: () => void;
71
+ }
72
+ export declare const COMPOSER_LABELS: {
73
+ readonly placeholder: "Reply…";
74
+ readonly send: "Send";
75
+ };
76
+ export declare function defaultComposer(props: ComposerRenderProps, state: ComposerState, width: number): Object3D;
77
+ //# sourceMappingURL=panel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../src/panel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EACL,KAAK,EAIL,OAAO,EACP,KAAK,MAAM,EACX,KAAK,QAAQ,EACd,MAAM,OAAO,CAAC;AAOf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EACV,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,kBAAkB,CAAC;AAE1B,iFAAiF;AAEjF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,eAAO,MAAM,QAAQ,QAAuB,CAAC;AAC7C,eAAO,MAAM,WAAW,MAAM,CAAC;AAK/B,eAAO,MAAM,SAAS,MAAO,MAAM,KAAG,MAA8C,CAAC;AAErF;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,GACf,WAAW,CAiBb;AAED,mGAAmG;AACnG,wBAAgB,UAAU,CAAC,SAAS,EAAE,sBAAsB,EAAE,QAAQ,CAAC,EAAE,WAAW,GAAG,WAAW,CAEjG;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;CACjC;AAID,yEAAyE;AACzE,qBAAa,QAAQ;IAOjB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,IAAI;IARd,SAAgB,KAAK,QAAe;IACpC,OAAO,CAAC,KAAK,CAA6D;IAC1E,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,OAAO,CAAyB;gBAGrB,MAAM,EAAE,QAAQ,EAChB,SAAS,EAAE,sBAAsB,EAC1C,IAAI,EAAE,WAAW;IAM3B,IAAW,MAAM,IAAI,WAAW,CAE/B;IAED,kEAAkE;IAClE,OAAO,CAAC,IAAI;IAqCL,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI;CA6BnD;AAKD,eAAO,MAAM,YAAY;;;;CAAgE,CAAC;AAE1F,wBAAgB,YAAY,CAAC,KAAK,EAAE,gBAAgB,GAAG,QAAQ,CA0B9D;AAED,oGAAoG;AACpG,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,IAAI,CAAC;CACnC;AAED,eAAO,MAAM,eAAe;;;CAAmD,CAAC;AAIhF,wBAAgB,eAAe,CAC7B,KAAK,EAAE,mBAAmB,EAC1B,KAAK,EAAE,aAAa,EACpB,KAAK,EAAE,MAAM,GACZ,QAAQ,CAiDV"}
package/dist/panel.js ADDED
@@ -0,0 +1,214 @@
1
+ /**
2
+ * The world-space panel and its composer, and how they follow the viewer.
3
+ *
4
+ * A panel is NEVER head-locked. A surface welded to the head is nauseating
5
+ * within seconds, so a `locked` placement is honoured as `lazy` — the host
6
+ * abilities the world resolves against simply do not offer it. The three
7
+ * modes are:
8
+ *
9
+ * - `fixed`: placed once, where the viewer was looking, and left there;
10
+ * - `lazy`: left alone until the viewer turns or walks past a dead zone, then
11
+ * eased into place (snapped, with reduced motion);
12
+ * - `wrist`: carried on a host-supplied controller or hand.
13
+ */
14
+ import { Group, Mesh, MeshBasicMaterial, PlaneGeometry, Vector3, } from 'three';
15
+ import { interactive } from './input.js';
16
+ import { createTextMesh } from './text.js';
17
+ import { panelBacking } from './theme-apply.js';
18
+ import { WORLD_CONTRACT } from './types.js';
19
+ export const DEAD_YAW = (35 * Math.PI) / 180;
20
+ export const DEAD_METRES = 0.5;
21
+ const STOP_YAW = (2 * Math.PI) / 180;
22
+ const STOP_METRES = 0.03;
23
+ const RATE_PER_S = 4;
24
+ export const wrapAngle = (a) => Math.atan2(Math.sin(a), Math.cos(a));
25
+ /**
26
+ * One step of lazy follow, with hysteresis: the panel stays put until the
27
+ * viewer leaves the dead zone, then chases until it has all but arrived, so it
28
+ * never creeps on every small head movement.
29
+ */
30
+ export function lazyStep(cur, want, dtMs, reduced) {
31
+ const dx = want.x - cur.x;
32
+ const dy = want.y - cur.y;
33
+ const dz = want.z - cur.z;
34
+ const dist = Math.hypot(dx, dy, dz);
35
+ const dyaw = wrapAngle(want.yaw - cur.yaw);
36
+ const chasing = cur.chasing || Math.abs(dyaw) > DEAD_YAW || dist > DEAD_METRES;
37
+ if (!chasing) {
38
+ return cur;
39
+ }
40
+ if (reduced) {
41
+ return { ...want, chasing: false };
42
+ }
43
+ const k = 1 - Math.exp((-RATE_PER_S * dtMs) / 1000);
44
+ const next = { x: cur.x + dx * k, y: cur.y + dy * k, z: cur.z + dz * k, yaw: cur.yaw + dyaw * k };
45
+ const arrived = dist * (1 - k) < STOP_METRES && Math.abs(dyaw) * (1 - k) < STOP_YAW;
46
+ return { ...next, chasing: !arrived };
47
+ }
48
+ /** The follow mode a resolved placement means. `locked` never survives resolution, but be sure. */
49
+ export function followMode(placement, override) {
50
+ return override ?? (placement.follow === 'none' ? 'fixed' : 'lazy');
51
+ }
52
+ const WRIST_WIDTH = 0.3;
53
+ /** Holds the panel + composer group and moves it per the follow mode. */
54
+ export class PanelRig {
55
+ parent;
56
+ placement;
57
+ mode;
58
+ group = new Group();
59
+ state = { x: 0, y: 0, z: 0, yaw: 0, chasing: false };
60
+ placed = false;
61
+ wristed = null;
62
+ constructor(parent, placement, mode) {
63
+ this.parent = parent;
64
+ this.placement = placement;
65
+ this.mode = mode;
66
+ this.group.name = 'cl3-panel-rig';
67
+ parent.add(this.group);
68
+ }
69
+ get follow() {
70
+ return this.mode;
71
+ }
72
+ /** Where the placement wants the panel, in parent-local space. */
73
+ want(ctx) {
74
+ const p = this.placement;
75
+ const cam = ctx.camera.getWorldPosition(new Vector3());
76
+ const dir = ctx.camera.getWorldDirection(new Vector3());
77
+ const fwd = new Vector3(dir.x, 0, dir.z);
78
+ if (fwd.lengthSq() < 1e-6) {
79
+ fwd.set(0, 0, -1);
80
+ }
81
+ fwd.normalize();
82
+ let pos;
83
+ if (p.anchor === 'agent' && ctx.agent !== null) {
84
+ const a = ctx.agent.getWorldPosition(new Vector3());
85
+ const toCam = new Vector3(cam.x - a.x, 0, cam.z - a.z).normalize();
86
+ pos = a.add(toCam.multiplyScalar(p.distanceMetres)).setY(a.y + 1.4 + p.heightMetres);
87
+ }
88
+ else if (p.anchor === 'place' && ctx.place !== null) {
89
+ const o = ctx.place instanceof Vector3
90
+ ? ctx.place.clone()
91
+ : ctx.place.getWorldPosition(new Vector3());
92
+ pos = o.setY(o.y + 1.2 + p.heightMetres);
93
+ }
94
+ else if (p.anchor === 'world') {
95
+ pos = this.parent.getWorldPosition(new Vector3());
96
+ pos.y += 1.4 + p.heightMetres;
97
+ pos.z -= p.distanceMetres;
98
+ }
99
+ else {
100
+ pos = cam.clone().addScaledVector(fwd, p.distanceMetres);
101
+ pos.y = cam.y + p.heightMetres;
102
+ }
103
+ this.parent.worldToLocal(pos);
104
+ const camLocal = this.parent.worldToLocal(cam.clone());
105
+ const yaw = p.billboard === 'none' && this.placed
106
+ ? this.state.yaw
107
+ : Math.atan2(camLocal.x - pos.x, camLocal.z - pos.z);
108
+ return { x: pos.x, y: pos.y, z: pos.z, yaw };
109
+ }
110
+ update(dtMs, ctx) {
111
+ if (this.mode === 'wrist' && ctx.wrist !== null) {
112
+ if (this.wristed !== ctx.wrist) {
113
+ ctx.wrist.add(this.group);
114
+ this.wristed = ctx.wrist;
115
+ this.group.position.set(0, 0.12, -0.05);
116
+ this.group.rotation.set(-Math.PI / 3, 0, 0);
117
+ this.group.scale.setScalar(WRIST_WIDTH / this.placement.widthMetres);
118
+ }
119
+ return;
120
+ }
121
+ if (this.wristed !== null) {
122
+ // The wrist went away: come back into the scene rather than vanish with it.
123
+ this.parent.add(this.group);
124
+ this.group.scale.setScalar(1);
125
+ this.group.rotation.set(0, 0, 0);
126
+ this.wristed = null;
127
+ this.placed = false;
128
+ }
129
+ const want = this.want(ctx);
130
+ if (!this.placed) {
131
+ this.state = { ...want, chasing: false };
132
+ this.placed = true;
133
+ }
134
+ else if (this.mode === 'lazy') {
135
+ this.state = lazyStep(this.state, want, dtMs, ctx.reduced);
136
+ }
137
+ this.group.position.set(this.state.x, this.state.y, this.state.z);
138
+ this.group.rotation.set(0, this.state.yaw, 0);
139
+ }
140
+ }
141
+ /** ─────────────────────────── default renderers ─────────────────────────── */
142
+ const MAX_LINES = 6;
143
+ export const PANEL_LABELS = { you: 'You', agent: 'Agent', typing: 'is typing…' };
144
+ export function defaultPanel(props) {
145
+ const { theme, placement } = props;
146
+ const width = placement.widthMetres;
147
+ const lines = props.messages
148
+ .filter((m) => m.deletedAt === null && !m.hiddenFromContact && m.kind !== 'system_event' && m.body !== '')
149
+ .slice(-MAX_LINES)
150
+ .map((m) => `${m.authorType === 'contact' ? PANEL_LABELS.you : PANEL_LABELS.agent}: ${m.body}`);
151
+ if (props.typing !== null) {
152
+ lines.push(`${props.typing.displayName ?? PANEL_LABELS.agent} ${PANEL_LABELS.typing}`);
153
+ }
154
+ const g = new Group();
155
+ g.name = WORLD_CONTRACT.objects.panel;
156
+ const text = createTextMesh(lines.join('\n'), theme, { maxWidthMetres: width - 0.04 });
157
+ const [, th] = text.userData['size'];
158
+ const height = th + 0.04;
159
+ const back = panelBacking(theme, width, height);
160
+ if (back !== null) {
161
+ g.add(back);
162
+ }
163
+ text.position.z = 0.002;
164
+ g.add(text);
165
+ g.userData['size'] = [width, height];
166
+ return g;
167
+ }
168
+ export const COMPOSER_LABELS = { placeholder: 'Reply…', send: 'Send' };
169
+ const FIELD_H = 0.07;
170
+ const SEND_W = 0.12;
171
+ export function defaultComposer(props, state, width) {
172
+ const { theme } = props;
173
+ const g = new Group();
174
+ g.name = WORLD_CONTRACT.objects.composer;
175
+ const fieldW = width - SEND_W - 0.01;
176
+ const field = new Group();
177
+ field.position.x = -(width - fieldW) / 2;
178
+ const fb = panelBacking(theme, fieldW, FIELD_H);
179
+ if (fb !== null) {
180
+ field.add(fb);
181
+ }
182
+ else {
183
+ field.add(new Mesh(new PlaneGeometry(fieldW, FIELD_H), new MeshBasicMaterial({ visible: false })));
184
+ }
185
+ const shown = state.draft === '' ? COMPOSER_LABELS.placeholder : state.draft;
186
+ const label = createTextMesh(shown, theme, {
187
+ maxWidthMetres: fieldW - 0.02,
188
+ size: 0.8,
189
+ });
190
+ label.position.z = 0.002;
191
+ field.add(label);
192
+ interactive(field, 'field', state.requestInput);
193
+ g.add(field);
194
+ const send = new Group();
195
+ send.position.x = (width - SEND_W) / 2;
196
+ const sb = panelBacking({ ...theme, panelColor: theme.accentColor, panelMaterial: 'solid' }, SEND_W, FIELD_H);
197
+ if (sb !== null) {
198
+ send.add(sb);
199
+ }
200
+ const sl = createTextMesh(COMPOSER_LABELS.send, theme, {
201
+ maxWidthMetres: SEND_W,
202
+ size: 0.8,
203
+ color: theme.accentTextColor,
204
+ });
205
+ sl.position.z = 0.002;
206
+ send.add(sl);
207
+ if (!props.disabled) {
208
+ interactive(send, 'send', state.submit);
209
+ }
210
+ g.add(send);
211
+ g.userData['size'] = [width, FIELD_H];
212
+ return g;
213
+ }
214
+ //# sourceMappingURL=panel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"panel.js","sourceRoot":"","sources":["../src/panel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The shared selection: one picked card, visible to every renderer and to the
3
+ * host. A pick the visitor taps and a pick the host selects from its own
4
+ * inventory UI are the same event.
5
+ */
6
+ export type SelectionListener = (id: string | null) => void;
7
+ export declare class Selection {
8
+ private current;
9
+ private readonly listeners;
10
+ get id(): string | null;
11
+ set(id: string | null): void;
12
+ subscribe: (l: SelectionListener) => (() => void);
13
+ }
14
+ //# sourceMappingURL=selection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.d.ts","sourceRoot":"","sources":["../src/selection.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;AAE5D,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAE1D,IAAW,EAAE,IAAI,MAAM,GAAG,IAAI,CAE7B;IAEM,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAU5B,SAAS,MAAO,iBAAiB,KAAG,CAAC,MAAM,IAAI,CAAC,CAGrD;CACH"}
@@ -0,0 +1,21 @@
1
+ export class Selection {
2
+ current = null;
3
+ listeners = new Set();
4
+ get id() {
5
+ return this.current;
6
+ }
7
+ set(id) {
8
+ if (id === this.current) {
9
+ return;
10
+ }
11
+ this.current = id;
12
+ for (const l of [...this.listeners]) {
13
+ l(id);
14
+ }
15
+ }
16
+ subscribe = (l) => {
17
+ this.listeners.add(l);
18
+ return () => this.listeners.delete(l);
19
+ };
20
+ }
21
+ //# sourceMappingURL=selection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.js","sourceRoot":"","sources":["../src/selection.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,SAAS;IACZ,OAAO,GAAkB,IAAI,CAAC;IACrB,SAAS,GAAG,IAAI,GAAG,EAAqB,CAAC;IAE1D,IAAW,EAAE;QACX,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAEM,GAAG,CAAC,EAAiB;QAC1B,IAAI,EAAE,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,EAAE,CAAC;QAClB,KAAK,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YACpC,CAAC,CAAC,EAAE,CAAC,CAAC;QACR,CAAC;IACH,CAAC;IAEM,SAAS,GAAG,CAAC,CAAoB,EAAgB,EAAE;QACxD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACtB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC;CACH"}
package/dist/text.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Text on a plane, styled by the resolved `worldTheme`.
3
+ *
4
+ * Text is drawn to a canvas texture when a canvas exists. Without one (a
5
+ * Node test, a worker) the mesh is still built — same geometry, same
6
+ * material, `userData.text` set — just with no map, which is what lets the
7
+ * scene-graph specs run with no GL and no DOM.
8
+ *
9
+ * Layout is measured by a fixed advance (half an em per character) rather
10
+ * than by the canvas, so a plane's size is the same in a browser and in a
11
+ * test, and wrapping never depends on which font happened to load.
12
+ */
13
+ import { Mesh } from 'three';
14
+ import type { ResolvedWorldTheme } from '@chatlychat/core';
15
+ /** Metres of cap-height-ish type at textScale 1: legible at arm's length. */
16
+ export declare const BASE_FONT_METRES = 0.035;
17
+ export interface TextOptions {
18
+ readonly maxWidthMetres: number;
19
+ /** Multiplier on `BASE_FONT_METRES`, on top of the theme's `textScale`. */
20
+ readonly size?: number;
21
+ readonly color?: string;
22
+ }
23
+ export declare function wrapText(text: string, maxChars: number): string[];
24
+ export declare function ensureFont(url: string | null): void;
25
+ export declare function createTextMesh(text: string, theme: ResolvedWorldTheme, opts: TextOptions): Mesh;
26
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../src/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAiB,IAAI,EAAuD,MAAM,OAAO,CAAC;AAEjG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAE3D,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,QAAQ,CAAC;AAMtC,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAgBjE;AAID,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAmBnD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAmD/F"}