@realitycollective/threejs-interactions 0.1.0 → 0.1.1-preview.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/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ Change log for the Reality Collective WebXR Interaction Extensions packages. All
4
4
 
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Preview builds are not listed separately. The entry for a version accumulates while its previews are published, and is dated when that version is released.
6
6
 
7
+ ## [0.1.1]
8
+
9
+ ### Added
10
+
11
+ - `@realitycollective/webxr-interactions` - shared conformance suites for `HitTester` and `TransformPort`, `hitTesterContractCases()` and `transformPortContractCases()`, alongside the existing `inputProviderContractCases()` from `@realitycollective/webxr-input`. Runner-free data, the same pattern as WebXR-UIExtensions' `windowHostContractCases()`: each case throws a plain `Error` naming what a platform's ray/proximity query or transform read/write broke, and asserts only what `ports.ts` documents - a ray or proximity query answers null or an id with a positive distance and a point near the query, the nearer of two targets wins, an offset round-trips, `getWorldPose()` is finite with a unit quaternion, `getRestWorldPose()` is the registration pose and stays put, `getWorldPose()` follows `setLocalOffset`, `setLocalRotation` and `setWorldPose` from that rest frame, every returned tuple is a fresh value the caller owns and no tuple passed in is kept, and the optional `setEffect` accepts its shape without throwing. Each platform's subject starts away from the origin and turned, so a port that ignores its rest frame fails. Run against all five platforms in `packages/iwsdk-interactions/test/port-parity.test.ts`, next to the provider parity suite; every platform passes every case.
12
+ - `@realitycollective/native-interactions` - a native host adapter, the fifth platform. It reads the `input` and `interactions` slices a native app (OpenXR on Quest, CompositorServices on visionOS) installs on `globalThis.__rcHost`, or hands in directly, and maps them onto `InputProvider`, `HitTester` and `TransformPort` with no engine dependency at all - assets and rendering stay in the native app, and only tuples and target ids cross the boundary. Every snapshot, hit and pose read across that boundary is copied, so a host that reuses its own buffers cannot reach an object the app is still holding, and optional members (`getHeadPose`, `sampleHints`, `pulse`, `setPresenceVisible`, `setPresenceModality`, `setWorldPose`, `setEffect`) are present only when the host slice itself carries them. `createNativeInteractions()` is the one-call setup, with the same shape as the other adapters' setups; `attachToHost` drives updates from the app's frame callback. The public surface matches the other adapters, and slice reading and tuple copying stay internal. Covered headlessly by an in-memory fake of both slices, including the shared `InputProvider` contract suite.
13
+
14
+ ### Changed
15
+
16
+ - `@realitycollective/webxr-interactions` - `TransformPort.getWorldPose()` is the LIVE world pose on every platform, and the new required `getRestWorldPose()` returns the rest pose captured at registration, in world space. The core only said "current world pose", and the platforms disagreed: the three.js, IWSDK and XR Blocks ports returned the rest pose, while the Babylon.js port returned the live one. The same behaviour therefore acted differently by platform. The hinge, slide and dial behaviours measure the hand against the rest pose, so they now call `getRestWorldPose()`. Grab and toss scoring keep `getWorldPose()` and now see where the object really is. `ports.ts` also states tuple ownership for `TransformPort` and `HitTester`: every returned tuple is fresh and owned by the caller, and a tuple passed in is read during the call and not kept. Every platform already met the ownership rule; the suites now hold them to it. The native `interactions` slice gains `getRestWorldPose(targetId)`, which the native app must implement. This adds a required member to `TransformPort`, so a port written outside this repository must implement it.
17
+ - `@realitycollective/xrblocks-interactions` - the structural slices `XBVec3Like`, `XBQuatLike`, `XBRayLike` and `XBControllerLike` are exported, under the same `XB` prefix as `XBRaySourceLike`, `XBDirectTouchLike` and `XBFrameLike` whose fields they type. They were module-private, so a consumer building a fake frame for a test had nothing to name, and TypeDoc reported each as referenced but undocumented.
18
+
19
+ ### Fixed
20
+
21
+ - `@realitycollective/threejs-interactions`, `@realitycollective/iwsdk-interactions` and `@realitycollective/xrblocks-interactions` - `getWorldPose()` returned the pose captured at registration, not where the object was. Toss scoring therefore read a thrown ball at its registered position until the app called `recaptureRest()`, and a second grab of an object moved without a recapture took its hold offset from the old position. `getWorldPose()` now returns the live pose, as the Babylon.js port already did.
22
+ - `@realitycollective/webxr-interactions` - `InteractableDescriptor.pokeRadius` had no effect. The value was stored on the registration and documented as the poke trigger radius, but the proximity query always used the 5 cm default, so an interactable asking for a wider or narrower poke got the default. The runtime now queries once per source at the largest radius any enabled interactable registered, and accepts the nearest hit only when it lies inside that interactable's own radius. One query per source is kept deliberately; the trade is that a farther interactable with a larger radius is not found behind a nearer one with a smaller radius, because the hit tester returns only the nearest. Covered by a runtime test that failed before the change.
23
+ - CI - the staging deploy published under `--branch=pr-<number>` while the `-test` Pages project's production branch is `staging`, so only `pr-<n>.webxr-interactions-test.pages.dev` aliases were ever created and the project's root URL was a 404. It now deploys as `staging` on every pull request and on every push to `development`, so `webxr-interactions-test.pages.dev` serves the newest preview build, the same arrangement WebXR-UIExtensions already had.
24
+
7
25
  ## [0.1.0] - 2026-09-17
8
26
 
9
27
  ### Added
@@ -57,4 +75,5 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
57
75
  - All five packages depend on `@realitycollective/webxr-input`, released independently from the [WebXR-Input](https://github.com/realitycollective/WebXR-Input) repository. That package must be published before this one.
58
76
  - The interaction packages themselves carry no IWSDK coupling beyond the adapter: `webxr-interactions` is engine-free, and `threejs-interactions` and `xrblocks-interactions` peer only on three.js.
59
77
 
78
+ [0.1.1]: https://github.com/realitycollective/WebXR-Interactions/compare/v0.1.0...development
60
79
  [0.1.0]: https://github.com/realitycollective/WebXR-Interactions/releases/tag/v0.1.0
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * ThreeTransformPort - the write/read surface of one interactable's
3
3
  * Object3D, honouring the core's FROM-REST semantics: the rest pose is
4
- * captured at construction, offsets/rotations apply relative to it, and
5
- * `getWorldPose()` reports the REST pose in world space (external movers
6
- * that reposition the object should call `recaptureRest()`).
4
+ * captured at construction and offsets/rotations apply relative to it.
5
+ * `getWorldPose()` reports where the object is now, and `getRestWorldPose()`
6
+ * reports the rest pose in world space. An external mover that repositions
7
+ * the object for good should call `recaptureRest()`.
7
8
  */
8
9
  import { type Object3D } from "three";
9
10
  import type { PoseTuple, QuatTuple, Vec3Tuple } from "@realitycollective/webxr-input";
@@ -21,6 +22,7 @@ export declare class ThreeTransformPort implements TransformPort {
21
22
  constructor(object: Object3D);
22
23
  recaptureRest(): void;
23
24
  getWorldPose(): PoseTuple;
25
+ getRestWorldPose(): PoseTuple;
24
26
  getLocalOffset(): Vec3Tuple;
25
27
  setLocalOffset(offset: Vec3Tuple): void;
26
28
  setLocalRotation(quaternion: QuatTuple): void;
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * ThreeTransformPort - the write/read surface of one interactable's
3
3
  * Object3D, honouring the core's FROM-REST semantics: the rest pose is
4
- * captured at construction, offsets/rotations apply relative to it, and
5
- * `getWorldPose()` reports the REST pose in world space (external movers
6
- * that reposition the object should call `recaptureRest()`).
4
+ * captured at construction and offsets/rotations apply relative to it.
5
+ * `getWorldPose()` reports where the object is now, and `getRestWorldPose()`
6
+ * reports the rest pose in world space. An external mover that repositions
7
+ * the object for good should call `recaptureRest()`.
7
8
  */
8
9
  import { Matrix4, Quaternion, Vector3, } from "three";
9
10
  export class ThreeTransformPort {
@@ -27,6 +28,14 @@ export class ThreeTransformPort {
27
28
  this.restScale.copy(this.object.scale);
28
29
  }
29
30
  getWorldPose() {
31
+ this.object.getWorldPosition(this.v);
32
+ this.object.getWorldQuaternion(this.q);
33
+ return {
34
+ position: [this.v.x, this.v.y, this.v.z],
35
+ quaternion: [this.q.x, this.q.y, this.q.z, this.q.w],
36
+ };
37
+ }
38
+ getRestWorldPose() {
30
39
  const parent = this.object.parent;
31
40
  if (parent) {
32
41
  parent.updateWorldMatrix(true, false);
@@ -1 +1 @@
1
- {"version":3,"file":"transform-port.js","sourceRoot":"","sources":["../src/transform-port.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ThreeTransformPort - the write/read surface of one interactable's\n * Object3D, honouring the core's FROM-REST semantics: the rest pose is\n * captured at construction, offsets/rotations apply relative to it, and\n * `getWorldPose()` reports the REST pose in world space (external movers\n * that reposition the object should call `recaptureRest()`).\n */\nimport {\n Matrix4,\n Quaternion,\n Vector3,\n type Material,\n type Mesh,\n type MeshStandardMaterial,\n type Object3D,\n} from \"three\";\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport type { TransformPort } from \"@realitycollective/webxr-interactions\";\n\nexport class ThreeTransformPort implements TransformPort {\n private readonly object: Object3D;\n private readonly restPosition = new Vector3();\n private readonly restQuaternion = new Quaternion();\n private readonly restScale = new Vector3();\n private baseEmissive: number | null = null;\n\n // Temps.\n private readonly v = new Vector3();\n private readonly q = new Quaternion();\n private readonly q2 = new Quaternion();\n private readonly m = new Matrix4();\n\n constructor(object: Object3D) {\n this.object = object;\n this.recaptureRest();\n }\n\n recaptureRest(): void {\n this.restPosition.copy(this.object.position);\n this.restQuaternion.copy(this.object.quaternion);\n this.restScale.copy(this.object.scale);\n }\n\n getWorldPose(): PoseTuple {\n const parent = this.object.parent;\n if (parent) {\n parent.updateWorldMatrix(true, false);\n this.v.copy(this.restPosition).applyMatrix4(parent.matrixWorld);\n parent.getWorldQuaternion(this.q2);\n this.q.copy(this.q2).multiply(this.restQuaternion);\n } else {\n this.v.copy(this.restPosition);\n this.q.copy(this.restQuaternion);\n }\n return {\n position: [this.v.x, this.v.y, this.v.z],\n quaternion: [this.q.x, this.q.y, this.q.z, this.q.w],\n };\n }\n\n getLocalOffset(): Vec3Tuple {\n this.v.copy(this.object.position).sub(this.restPosition);\n this.q.copy(this.restQuaternion).invert();\n this.v.applyQuaternion(this.q);\n return [this.v.x, this.v.y, this.v.z];\n }\n\n setLocalOffset(offset: Vec3Tuple): void {\n this.v.set(...offset).applyQuaternion(this.restQuaternion);\n this.object.position.copy(this.restPosition).add(this.v);\n }\n\n setLocalRotation(quaternion: QuatTuple): void {\n this.q.set(...quaternion);\n this.object.quaternion.copy(this.restQuaternion).multiply(this.q);\n }\n\n setWorldPose(pose: PoseTuple): void {\n const parent = this.object.parent;\n this.v.set(...pose.position);\n this.q.set(...pose.quaternion);\n if (parent) {\n parent.updateWorldMatrix(true, false);\n this.m.copy(parent.matrixWorld).invert();\n this.v.applyMatrix4(this.m);\n parent.getWorldQuaternion(this.q2).invert();\n this.q.premultiply(this.q2);\n }\n this.object.position.copy(this.v);\n this.object.quaternion.copy(this.q);\n }\n\n setEffect(effect: { scale?: number; emissive?: number }): void {\n if (effect.scale !== undefined) {\n this.object.scale.copy(this.restScale).multiplyScalar(effect.scale);\n }\n if (effect.emissive !== undefined) {\n const material = this.firstEmissiveMaterial();\n if (material) {\n if (this.baseEmissive === null) this.baseEmissive = material.emissiveIntensity;\n material.emissiveIntensity = this.baseEmissive + effect.emissive;\n }\n }\n }\n\n private firstEmissiveMaterial(): MeshStandardMaterial | null {\n let found: MeshStandardMaterial | null = null;\n this.object.traverse((child) => {\n if (found) return;\n const material = (child as Mesh).material as Material | Material[] | undefined;\n const single = Array.isArray(material) ? material[0] : material;\n if (single && \"emissiveIntensity\" in single) {\n found = single as MeshStandardMaterial;\n }\n });\n return found;\n }\n}\n"]}
1
+ {"version":3,"file":"transform-port.js","sourceRoot":"","sources":["../src/transform-port.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * ThreeTransformPort - the write/read surface of one interactable's\n * Object3D, honouring the core's FROM-REST semantics: the rest pose is\n * captured at construction and offsets/rotations apply relative to it.\n * `getWorldPose()` reports where the object is now, and `getRestWorldPose()`\n * reports the rest pose in world space. An external mover that repositions\n * the object for good should call `recaptureRest()`.\n */\nimport {\n Matrix4,\n Quaternion,\n Vector3,\n type Material,\n type Mesh,\n type MeshStandardMaterial,\n type Object3D,\n} from \"three\";\nimport type { PoseTuple, QuatTuple, Vec3Tuple } from \"@realitycollective/webxr-input\";\nimport type { TransformPort } from \"@realitycollective/webxr-interactions\";\n\nexport class ThreeTransformPort implements TransformPort {\n private readonly object: Object3D;\n private readonly restPosition = new Vector3();\n private readonly restQuaternion = new Quaternion();\n private readonly restScale = new Vector3();\n private baseEmissive: number | null = null;\n\n // Temps.\n private readonly v = new Vector3();\n private readonly q = new Quaternion();\n private readonly q2 = new Quaternion();\n private readonly m = new Matrix4();\n\n constructor(object: Object3D) {\n this.object = object;\n this.recaptureRest();\n }\n\n recaptureRest(): void {\n this.restPosition.copy(this.object.position);\n this.restQuaternion.copy(this.object.quaternion);\n this.restScale.copy(this.object.scale);\n }\n\n getWorldPose(): PoseTuple {\n this.object.getWorldPosition(this.v);\n this.object.getWorldQuaternion(this.q);\n return {\n position: [this.v.x, this.v.y, this.v.z],\n quaternion: [this.q.x, this.q.y, this.q.z, this.q.w],\n };\n }\n\n getRestWorldPose(): PoseTuple {\n const parent = this.object.parent;\n if (parent) {\n parent.updateWorldMatrix(true, false);\n this.v.copy(this.restPosition).applyMatrix4(parent.matrixWorld);\n parent.getWorldQuaternion(this.q2);\n this.q.copy(this.q2).multiply(this.restQuaternion);\n } else {\n this.v.copy(this.restPosition);\n this.q.copy(this.restQuaternion);\n }\n return {\n position: [this.v.x, this.v.y, this.v.z],\n quaternion: [this.q.x, this.q.y, this.q.z, this.q.w],\n };\n }\n\n getLocalOffset(): Vec3Tuple {\n this.v.copy(this.object.position).sub(this.restPosition);\n this.q.copy(this.restQuaternion).invert();\n this.v.applyQuaternion(this.q);\n return [this.v.x, this.v.y, this.v.z];\n }\n\n setLocalOffset(offset: Vec3Tuple): void {\n this.v.set(...offset).applyQuaternion(this.restQuaternion);\n this.object.position.copy(this.restPosition).add(this.v);\n }\n\n setLocalRotation(quaternion: QuatTuple): void {\n this.q.set(...quaternion);\n this.object.quaternion.copy(this.restQuaternion).multiply(this.q);\n }\n\n setWorldPose(pose: PoseTuple): void {\n const parent = this.object.parent;\n this.v.set(...pose.position);\n this.q.set(...pose.quaternion);\n if (parent) {\n parent.updateWorldMatrix(true, false);\n this.m.copy(parent.matrixWorld).invert();\n this.v.applyMatrix4(this.m);\n parent.getWorldQuaternion(this.q2).invert();\n this.q.premultiply(this.q2);\n }\n this.object.position.copy(this.v);\n this.object.quaternion.copy(this.q);\n }\n\n setEffect(effect: { scale?: number; emissive?: number }): void {\n if (effect.scale !== undefined) {\n this.object.scale.copy(this.restScale).multiplyScalar(effect.scale);\n }\n if (effect.emissive !== undefined) {\n const material = this.firstEmissiveMaterial();\n if (material) {\n if (this.baseEmissive === null) this.baseEmissive = material.emissiveIntensity;\n material.emissiveIntensity = this.baseEmissive + effect.emissive;\n }\n }\n }\n\n private firstEmissiveMaterial(): MeshStandardMaterial | null {\n let found: MeshStandardMaterial | null = null;\n this.object.traverse((child) => {\n if (found) return;\n const material = (child as Mesh).material as Material | Material[] | undefined;\n const single = Array.isArray(material) ? material[0] : material;\n if (single && \"emissiveIntensity\" in single) {\n found = single as MeshStandardMaterial;\n }\n });\n return found;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@realitycollective/threejs-interactions",
3
- "version": "0.1.0",
3
+ "version": "0.1.1-preview.0",
4
4
  "description": "The default, standalone engine adapter for the Reality Collective Interaction Extensions: raw WebXR (the browser's OpenXR binding - XRSession, XRInputSource, select/squeeze, hand joints) plus three.js hit-testing and transform ports, with a desktop mouse fallback. No framework required. Re-exports the @realitycollective/webxr-interactions core.",
5
5
  "keywords": [
6
6
  "realitycollective",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@realitycollective/webxr-input": "^0.1.4",
37
- "@realitycollective/webxr-interactions": "^0.1.0"
37
+ "@realitycollective/webxr-interactions": "^0.1.1-preview.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "three": ">=0.170.0",