mujoco-react 10.1.0 → 10.2.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/dist/spark.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as _sparkjsdev_spark from '@sparkjsdev/spark';
3
- import { n as SplatEnvironmentProps, q as PairedSplatEnvironmentConfig, S as SceneConfig, t as SplatEnvironmentReadiness, o as VisualScenarioConfig } from './types-BHBNJubg.js';
3
+ import { n as SplatEnvironmentProps, q as PairedSplatEnvironmentConfig, S as SceneConfig, t as SplatEnvironmentReadiness, o as VisualScenarioConfig } from './types-CLD5K3JD.js';
4
4
  import 'react';
5
5
  import '@react-three/fiber';
6
6
  import 'three';
package/dist/spark.js CHANGED
@@ -1,4 +1,4 @@
1
- import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-FEKBKHEN.js';
1
+ import { useSplatSceneConfig, useSplatEnvironment, SplatEnvironment, CAMERA_FRAME_CAPTURE_RENDER_USER_DATA_KEY } from './chunk-3BMNRSS2.js';
2
2
  import { useThree } from '@react-three/fiber';
3
3
  import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
4
4
  import * as THREE from 'three';
@@ -573,6 +573,35 @@ interface RayHit {
573
573
  geomId: number;
574
574
  distance: number;
575
575
  }
576
+ type ImagePointCoordinateSpace = 'normalized' | 'normalized-1000' | 'pixel' | 'ndc';
577
+ interface ImagePointProjectionOptions extends CameraFrameCaptureOptions {
578
+ /** X coordinate in the selected coordinate space. Defaults to normalized 0..1. */
579
+ x: number;
580
+ /** Y coordinate in the selected coordinate space. Defaults to normalized 0..1 with origin at top-left. */
581
+ y: number;
582
+ /**
583
+ * Coordinate convention for x/y:
584
+ * - normalized: 0..1 image coordinates, top-left origin
585
+ * - normalized-1000: 0..1000 detector coordinates, top-left origin
586
+ * - pixel: pixel coordinates, top-left origin
587
+ * - ndc: Three.js normalized device coordinates, -1..1
588
+ */
589
+ coordinateSpace?: ImagePointCoordinateSpace;
590
+ /** Image width for pixel coordinates. Falls back to `width` or renderer canvas width. */
591
+ imageWidth?: number;
592
+ /** Image height for pixel coordinates. Falls back to `height` or renderer canvas height. */
593
+ imageHeight?: number;
594
+ /** Ignore hits farther than this distance from the camera ray origin. */
595
+ maxDistance?: number;
596
+ }
597
+ interface ImagePointProjectionResult extends RayHit {
598
+ /** NDC coordinates used for raycasting. */
599
+ ndc: [number, number];
600
+ /** Image dimensions used when interpreting pixel coordinates. */
601
+ imageSize: [number, number];
602
+ /** Camera pose provenance, matching camera-frame capture results. */
603
+ source: CameraFrameCaptureSource;
604
+ }
576
605
  interface ModelOptions {
577
606
  timestep: number;
578
607
  gravity: [number, number, number];
@@ -1094,6 +1123,7 @@ interface MujocoSimAPI {
1094
1123
  bodyId: number;
1095
1124
  geomId: number;
1096
1125
  } | null;
1126
+ projectImagePointTo3D(options: ImagePointProjectionOptions): ImagePointProjectionResult | null;
1097
1127
  setBodyMass(name: Bodies, mass: number): void;
1098
1128
  setGeomFriction(name: Geoms, friction: [number, number, number]): void;
1099
1129
  setGeomSize(name: Geoms, size: [number, number, number]): void;
@@ -1350,4 +1380,4 @@ interface ArrayJointStateResult {
1350
1380
  velocity: React__default.RefObject<Float64Array>;
1351
1381
  }
1352
1382
 
1353
- export { type ArrayJointStateResult as $, type ActuatedJointInfo as A, type BodyProps as B, type ControlGroupInfo as C, type DragInteractionProps as D, type ScenarioLightingPreset as E, type SplatEnvironmentMetadataInput as F, type SplatEnvironmentMetadata as G, type SplatSceneInput as H, type IkConfig as I, type DebugProps as J, type GeomInfo as K, type ContactListenerProps as L, type MujocoContextValue as M, type ActuatorInfo as N, type ObservationConfig as O, type PhysicsStepCallback as P, type Sites as Q, type ReadyCallbackInput as R, type SceneConfig as S, type TrajectoryPlayerProps as T, type SitePositionResult as U, type VisualScenarioEffectsProps as V, type Sensors as W, type SensorHandle as X, type SensorInfo as Y, type Joints as Z, type ScalarJointStateResult as _, type MujocoCanvasProps as a, ModelGeoms as a$, type JointStateOptions as a0, type JointStateResult as a1, type Bodies as a2, type BodyStateResult as a3, type Geoms as a4, type Actuators as a5, type CtrlHandle as a6, type ContactInfo as a7, type KeyboardTeleopConfig as a8, type KeyboardIkTargetConfig as a9, type CameraFrameSequenceRecorderAPI as aA, type CameraFrameCaptureResult as aB, type CameraFrameCaptureBlobResult as aC, type PolicyVector as aD, type BodyInfo as aE, type CameraFrameCaptureQuaternion as aF, type CameraFrameCaptureVector3 as aG, type CameraFrameSequenceCameraSummary as aH, type CameraFrameSequenceFrame as aI, type CameraFrameSequenceSampleInput as aJ, type CameraFrameSequenceStepInput as aK, type CameraInfo as aL, type ControlJointInfo as aM, type FrameCaptureTarget as aN, type FrameCaptureTargetRef as aO, type IKSolveFn as aP, type IkGizmoDragInput as aQ, type IkSolveInput as aR, type JointInfo as aS, type JointStateKind as aT, type KeyBinding as aU, type KeyboardIkTargetAction as aV, type KeyboardIkTargetBinding as aW, type Keyframes as aX, ModelActuators as aY, ModelBodies as aZ, ModelCameras as a_, type PolicyConfig as aa, type PolicyAPI as ab, type RemotePolicyConfig as ac, type RemotePolicyAPI as ad, type ObservationHandle as ae, type ObservationOutput as af, type TrajectoryInput as ag, type TrajectoryStateChangeInput as ah, type PlaybackState as ai, type TrajectoryFrame as aj, type FrameCaptureOptions as ak, type FrameCaptureResult as al, type FrameCaptureBlobResult as am, type FrameCaptureAPI as an, type CameraFrameCaptureOptions as ao, type CameraFrameCaptureAPI as ap, type Cameras as aq, type CameraFrameSequenceCamera as ar, type CameraFrameCaptureSource as as, type CameraFrameSequenceOptions as at, type CameraFrameSequenceResult as au, type PolicyCameraFrameStream as av, type PolicyCameraFrameCaptureOptions as aw, type PolicyCameraFrameCaptureResult as ax, type FrameCaptureStatus as ay, type PolicyCameraFrameCaptureAPI as az, type MujocoSimAPI as b, ModelJoints as b0, ModelKeyframes as b1, type ModelOptions as b2, type ModelResource as b3, ModelResources as b4, ModelSensors as b5, ModelSites as b6, type Models as b7, type MujocoContact as b8, type MujocoContactArray as b9, type SiteInfo as bA, type SplatAssetConfig as bB, type SplatScenarioConfig as bC, type StateSnapshot as bD, type TrajectoryData as bE, type TrajectoryFrameCallbackInput as bF, type VisualScenarioMaterialFilterInput as bG, type XmlPatch as bH, getContact as bI, registerModelResources as bJ, type MujocoFrameCaptureOptions as ba, type ObservationLayoutItem as bb, type PhysicsConfig as bc, type PhysicsStepInput as bd, type PolicyActionChunk as be, type PolicyActionInput as bf, type PolicyInferenceInput as bg, type PolicyInferenceOutput as bh, type PolicyInferenceResult as bi, type PolicyObservationInput as bj, type RayHit as bk, type Register as bl, type RegisteredModelMap as bm, type RemotePolicyRequestInfo as bn, type RemotePolicyRequestInput as bo, type RemotePolicyResponseInfo as bp, type RemotePolicyStatus as bq, type ResetCallbackInput as br, type ResolvedScenarioCameraConfig as bs, type ResolvedScenarioMaterialConfig as bt, type ResourceSelector as bu, type ScenarioCameraConfig as bv, type ScenarioMaterialConfig as bw, type SceneMarker as bx, type SceneObject as by, type SensorResult as bz, type StepCallbackInput as c, type SelectionCallbackInput as d, type MujocoModule as e, type MujocoModel as f, type MujocoData as g, type ControlGroupSelector as h, type ObservationResult as i, type IkContextValue as j, type IkGizmoProps as k, type SceneLightsProps as l, type ScenarioLightingProps as m, type SplatEnvironmentProps as n, type VisualScenarioConfig as o, type SplatRendererKind as p, type PairedSplatEnvironmentConfig as q, type SplatFormat as r, type SplatCollisionProxyConfig as s, type SplatEnvironmentReadiness as t, type SplatCollisionPrimitive as u, SplatEnvironmentReadinessStatus as v, type SplatSceneConfigInput as w, type SplatSceneConfigState as x, type VisualScenarioExecutionContextInput as y, type VisualScenarioExecutionContext as z };
1383
+ export { type ArrayJointStateResult as $, type ActuatedJointInfo as A, type BodyProps as B, type ControlGroupInfo as C, type DragInteractionProps as D, type ScenarioLightingPreset as E, type SplatEnvironmentMetadataInput as F, type SplatEnvironmentMetadata as G, type SplatSceneInput as H, type IkConfig as I, type DebugProps as J, type GeomInfo as K, type ContactListenerProps as L, type MujocoContextValue as M, type ActuatorInfo as N, type ObservationConfig as O, type PhysicsStepCallback as P, type Sites as Q, type ReadyCallbackInput as R, type SceneConfig as S, type TrajectoryPlayerProps as T, type SitePositionResult as U, type VisualScenarioEffectsProps as V, type Sensors as W, type SensorHandle as X, type SensorInfo as Y, type Joints as Z, type ScalarJointStateResult as _, type MujocoCanvasProps as a, ModelActuators as a$, type JointStateOptions as a0, type JointStateResult as a1, type Bodies as a2, type BodyStateResult as a3, type Geoms as a4, type Actuators as a5, type CtrlHandle as a6, type ContactInfo as a7, type KeyboardTeleopConfig as a8, type KeyboardIkTargetConfig as a9, type CameraFrameSequenceRecorderAPI as aA, type CameraFrameCaptureResult as aB, type CameraFrameCaptureBlobResult as aC, type ImagePointCoordinateSpace as aD, type ImagePointProjectionOptions as aE, type ImagePointProjectionResult as aF, type PolicyVector as aG, type BodyInfo as aH, type CameraFrameCaptureQuaternion as aI, type CameraFrameCaptureVector3 as aJ, type CameraFrameSequenceCameraSummary as aK, type CameraFrameSequenceFrame as aL, type CameraFrameSequenceSampleInput as aM, type CameraFrameSequenceStepInput as aN, type CameraInfo as aO, type ControlJointInfo as aP, type FrameCaptureTarget as aQ, type FrameCaptureTargetRef as aR, type IKSolveFn as aS, type IkGizmoDragInput as aT, type IkSolveInput as aU, type JointInfo as aV, type JointStateKind as aW, type KeyBinding as aX, type KeyboardIkTargetAction as aY, type KeyboardIkTargetBinding as aZ, type Keyframes as a_, type PolicyConfig as aa, type PolicyAPI as ab, type RemotePolicyConfig as ac, type RemotePolicyAPI as ad, type ObservationHandle as ae, type ObservationOutput as af, type TrajectoryInput as ag, type TrajectoryStateChangeInput as ah, type PlaybackState as ai, type TrajectoryFrame as aj, type FrameCaptureOptions as ak, type FrameCaptureResult as al, type FrameCaptureBlobResult as am, type FrameCaptureAPI as an, type CameraFrameCaptureOptions as ao, type CameraFrameCaptureAPI as ap, type Cameras as aq, type CameraFrameSequenceCamera as ar, type CameraFrameCaptureSource as as, type CameraFrameSequenceOptions as at, type CameraFrameSequenceResult as au, type PolicyCameraFrameStream as av, type PolicyCameraFrameCaptureOptions as aw, type PolicyCameraFrameCaptureResult as ax, type FrameCaptureStatus as ay, type PolicyCameraFrameCaptureAPI as az, type MujocoSimAPI as b, ModelBodies as b0, ModelCameras as b1, ModelGeoms as b2, ModelJoints as b3, ModelKeyframes as b4, type ModelOptions as b5, type ModelResource as b6, ModelResources as b7, ModelSensors as b8, ModelSites as b9, type SceneMarker as bA, type SceneObject as bB, type SensorResult as bC, type SiteInfo as bD, type SplatAssetConfig as bE, type SplatScenarioConfig as bF, type StateSnapshot as bG, type TrajectoryData as bH, type TrajectoryFrameCallbackInput as bI, type VisualScenarioMaterialFilterInput as bJ, type XmlPatch as bK, getContact as bL, registerModelResources as bM, type Models as ba, type MujocoContact as bb, type MujocoContactArray as bc, type MujocoFrameCaptureOptions as bd, type ObservationLayoutItem as be, type PhysicsConfig as bf, type PhysicsStepInput as bg, type PolicyActionChunk as bh, type PolicyActionInput as bi, type PolicyInferenceInput as bj, type PolicyInferenceOutput as bk, type PolicyInferenceResult as bl, type PolicyObservationInput as bm, type RayHit as bn, type Register as bo, type RegisteredModelMap as bp, type RemotePolicyRequestInfo as bq, type RemotePolicyRequestInput as br, type RemotePolicyResponseInfo as bs, type RemotePolicyStatus as bt, type ResetCallbackInput as bu, type ResolvedScenarioCameraConfig as bv, type ResolvedScenarioMaterialConfig as bw, type ResourceSelector as bx, type ScenarioCameraConfig as by, type ScenarioMaterialConfig as bz, type StepCallbackInput as c, type SelectionCallbackInput as d, type MujocoModule as e, type MujocoModel as f, type MujocoData as g, type ControlGroupSelector as h, type ObservationResult as i, type IkContextValue as j, type IkGizmoProps as k, type SceneLightsProps as l, type ScenarioLightingProps as m, type SplatEnvironmentProps as n, type VisualScenarioConfig as o, type SplatRendererKind as p, type PairedSplatEnvironmentConfig as q, type SplatFormat as r, type SplatCollisionProxyConfig as s, type SplatEnvironmentReadiness as t, type SplatCollisionPrimitive as u, SplatEnvironmentReadinessStatus as v, type SplatSceneConfigInput as w, type SplatSceneConfigState as x, type VisualScenarioExecutionContextInput as y, type VisualScenarioExecutionContext as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mujoco-react",
3
- "version": "10.1.0",
3
+ "version": "10.2.0",
4
4
  "description": "Composable React Three Fiber building blocks for MuJoCo WASM simulations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,6 +31,8 @@ import {
31
31
  ControlGroupSelector,
32
32
  ContactInfo,
33
33
  GeomInfo,
34
+ ImagePointProjectionOptions,
35
+ ImagePointProjectionResult,
34
36
  JointInfo,
35
37
  LoadFromFilesOptions,
36
38
  LocalMujocoFile,
@@ -61,6 +63,7 @@ import {
61
63
  getCameraFrameCaptureSourceTarget,
62
64
  isMountedCameraFrameCaptureSource,
63
65
  } from '../rendering/cameraFrameSource';
66
+ import { projectImagePointTo3D as projectImagePointTo3DFromScene } from '../rendering/imageProjection';
64
67
  import {
65
68
  loadScene,
66
69
  createSceneConfigFromFiles,
@@ -1590,6 +1593,31 @@ export function MujocoSimProvider({
1590
1593
  [camera, gl]
1591
1594
  );
1592
1595
 
1596
+ const projectImagePointTo3D = useCallback(
1597
+ (options: ImagePointProjectionOptions): ImagePointProjectionResult | null => {
1598
+ const {
1599
+ x,
1600
+ y,
1601
+ coordinateSpace,
1602
+ imageWidth,
1603
+ imageHeight,
1604
+ maxDistance,
1605
+ ...captureOptions
1606
+ } = options;
1607
+ const resolvedCaptureOptions = resolveCameraCaptureOptions(captureOptions);
1608
+ return projectImagePointTo3DFromScene(gl, scene, camera, {
1609
+ ...resolvedCaptureOptions,
1610
+ x,
1611
+ y,
1612
+ coordinateSpace,
1613
+ imageWidth,
1614
+ imageHeight,
1615
+ maxDistance,
1616
+ });
1617
+ },
1618
+ [camera, gl, resolveCameraCaptureOptions, scene]
1619
+ );
1620
+
1593
1621
  // --- Domain randomization ---
1594
1622
 
1595
1623
  const setBodyMass = useCallback((name: string, mass: number): void => {
@@ -1676,6 +1704,7 @@ export function MujocoSimProvider({
1676
1704
  captureCameraFrameBlob: captureCameraFrameBlobApi,
1677
1705
  recordCameraSequence: recordCameraSequenceApi,
1678
1706
  project2DTo3D,
1707
+ projectImagePointTo3D,
1679
1708
  setBodyMass,
1680
1709
  setGeomFriction,
1681
1710
  setGeomSize,
@@ -1696,6 +1725,7 @@ export function MujocoSimProvider({
1696
1725
  captureCameraFrameApi, captureCameraFrameBlobApi,
1697
1726
  recordCameraSequenceApi,
1698
1727
  project2DTo3D,
1728
+ projectImagePointTo3D,
1699
1729
  setBodyMass, setGeomFriction, setGeomSize,
1700
1730
  ]
1701
1731
  );
package/src/index.ts CHANGED
@@ -148,6 +148,10 @@ export {
148
148
  createCameraFrameCaptureSession,
149
149
  renderCameraFrameToCanvas,
150
150
  } from './rendering/cameraFrameCapture';
151
+ export {
152
+ imagePointToNdc,
153
+ projectImagePointTo3D,
154
+ } from './rendering/imageProjection';
151
155
  export {
152
156
  capturePolicyCameraFrames,
153
157
  capturePolicyCameraFramesFromMountedStreams,
@@ -269,6 +273,9 @@ export type {
269
273
  ContactInfo,
270
274
  // Raycast
271
275
  RayHit,
276
+ ImagePointCoordinateSpace,
277
+ ImagePointProjectionOptions,
278
+ ImagePointProjectionResult,
272
279
  // Model options
273
280
  ModelOptions,
274
281
  // Trajectory
@@ -0,0 +1,186 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ *
5
+ * Project detector/image coordinates from a camera view into the rendered MuJoCo scene.
6
+ */
7
+
8
+ import * as THREE from 'three';
9
+ import type {
10
+ CameraFrameCaptureOptions,
11
+ CameraFrameCaptureSource,
12
+ ImagePointCoordinateSpace,
13
+ ImagePointProjectionOptions,
14
+ ImagePointProjectionResult,
15
+ } from '../types';
16
+ import { CAPTURE_EXCLUDE_KEY } from './cameraFrameCapture';
17
+
18
+ const _raycaster = new THREE.Raycaster();
19
+ const _ndc = new THREE.Vector2();
20
+
21
+ function toVector3(
22
+ value: CameraFrameCaptureOptions['position'] | undefined,
23
+ fallback: THREE.Vector3
24
+ ) {
25
+ if (!value) return fallback.clone();
26
+ return value instanceof THREE.Vector3
27
+ ? value.clone()
28
+ : new THREE.Vector3(value[0], value[1], value[2]);
29
+ }
30
+
31
+ function applyCameraPose(
32
+ camera: THREE.Camera,
33
+ options: CameraFrameCaptureOptions,
34
+ fallbackCamera: THREE.Camera
35
+ ) {
36
+ camera.position.copy(toVector3(options.position, fallbackCamera.position));
37
+ camera.up.copy(toVector3(options.up, fallbackCamera.up));
38
+
39
+ if (options.quaternion) {
40
+ if (options.quaternion instanceof THREE.Quaternion) {
41
+ camera.quaternion.copy(options.quaternion);
42
+ } else {
43
+ camera.quaternion.set(
44
+ options.quaternion[0],
45
+ options.quaternion[1],
46
+ options.quaternion[2],
47
+ options.quaternion[3]
48
+ );
49
+ }
50
+ } else if (options.lookAt) {
51
+ camera.lookAt(toVector3(options.lookAt, new THREE.Vector3()));
52
+ } else {
53
+ camera.quaternion.copy(fallbackCamera.quaternion);
54
+ }
55
+
56
+ camera.updateMatrixWorld();
57
+ }
58
+
59
+ function createProjectionCamera(
60
+ fallbackCamera: THREE.Camera,
61
+ options: CameraFrameCaptureOptions,
62
+ width: number,
63
+ height: number
64
+ ) {
65
+ const camera = options.camera
66
+ ? options.camera.clone()
67
+ : fallbackCamera instanceof THREE.PerspectiveCamera
68
+ ? fallbackCamera.clone()
69
+ : new THREE.PerspectiveCamera(45, width / height, 0.01, 100);
70
+
71
+ if (camera instanceof THREE.PerspectiveCamera) {
72
+ camera.aspect = width / height;
73
+ camera.fov = options.fov ?? camera.fov;
74
+ camera.near = options.near ?? camera.near;
75
+ camera.far = options.far ?? camera.far;
76
+ camera.updateProjectionMatrix();
77
+ }
78
+
79
+ applyCameraPose(camera, options, fallbackCamera);
80
+ return camera;
81
+ }
82
+
83
+ function getProjectionSource(options: CameraFrameCaptureOptions): CameraFrameCaptureSource {
84
+ if (options.source) return options.source;
85
+ if (options.cameraName) return { kind: 'mujoco-camera', cameraName: options.cameraName };
86
+ if (options.siteName) return { kind: 'mujoco-site', siteName: options.siteName };
87
+ if (options.bodyName) return { kind: 'mujoco-body', bodyName: options.bodyName };
88
+ if (options.camera) return { kind: 'custom-camera' };
89
+ if (options.position || options.lookAt || options.quaternion) return { kind: 'explicit-pose' };
90
+ return { kind: 'fallback-camera' };
91
+ }
92
+
93
+ function imageSize(
94
+ renderer: THREE.WebGLRenderer,
95
+ options: ImagePointProjectionOptions
96
+ ): [number, number] {
97
+ return [
98
+ Math.max(1, Math.floor(options.imageWidth ?? options.width ?? renderer.domElement.width)),
99
+ Math.max(1, Math.floor(options.imageHeight ?? options.height ?? renderer.domElement.height)),
100
+ ];
101
+ }
102
+
103
+ export function imagePointToNdc(
104
+ x: number,
105
+ y: number,
106
+ coordinateSpace: ImagePointCoordinateSpace = 'normalized',
107
+ width = 1,
108
+ height = 1
109
+ ): [number, number] {
110
+ if (coordinateSpace === 'ndc') return [x, y];
111
+ if (coordinateSpace === 'normalized-1000') {
112
+ return [(x / 1000) * 2 - 1, 1 - (y / 1000) * 2];
113
+ }
114
+ if (coordinateSpace === 'pixel') {
115
+ return [(x / width) * 2 - 1, 1 - (y / height) * 2];
116
+ }
117
+ return [x * 2 - 1, 1 - y * 2];
118
+ }
119
+
120
+ function isProjectionCandidate(object: THREE.Object3D, options: ImagePointProjectionOptions) {
121
+ if (!object.visible) return false;
122
+ if (object.userData[CAPTURE_EXCLUDE_KEY]) return false;
123
+
124
+ const geomGroup = object.userData.geomGroup;
125
+ const geomName = object.userData.geomName;
126
+ if (options.hiddenGeomNames && typeof geomName === 'string' && options.hiddenGeomNames.includes(geomName)) {
127
+ return false;
128
+ }
129
+ if (options.hiddenGeomGroups && typeof geomGroup === 'number' && options.hiddenGeomGroups.includes(geomGroup)) {
130
+ return false;
131
+ }
132
+ if (options.visibleGeomGroups && typeof geomGroup === 'number' && !options.visibleGeomGroups.includes(geomGroup)) {
133
+ return false;
134
+ }
135
+ return true;
136
+ }
137
+
138
+ function findBodyId(object: THREE.Object3D) {
139
+ let current: THREE.Object3D | null = object;
140
+ while (current && current.userData.bodyID === undefined && current.parent) {
141
+ current = current.parent;
142
+ }
143
+ return typeof current?.userData.bodyID === 'number' ? current.userData.bodyID : -1;
144
+ }
145
+
146
+ export function projectImagePointTo3D(
147
+ renderer: THREE.WebGLRenderer,
148
+ scene: THREE.Scene,
149
+ fallbackCamera: THREE.Camera,
150
+ options: ImagePointProjectionOptions
151
+ ): ImagePointProjectionResult | null {
152
+ const [width, height] = imageSize(renderer, options);
153
+ const [ndcX, ndcY] = imagePointToNdc(
154
+ options.x,
155
+ options.y,
156
+ options.coordinateSpace,
157
+ width,
158
+ height
159
+ );
160
+ const projectionCamera = createProjectionCamera(fallbackCamera, options, width, height);
161
+
162
+ scene.updateMatrixWorld(true);
163
+ _ndc.set(ndcX, ndcY);
164
+ _raycaster.setFromCamera(_ndc, projectionCamera);
165
+ _raycaster.far = options.maxDistance ?? Infinity;
166
+
167
+ const objects: THREE.Object3D[] = [];
168
+ scene.traverse((object) => {
169
+ if ((object as THREE.Mesh).isMesh && isProjectionCandidate(object, options)) {
170
+ objects.push(object);
171
+ }
172
+ });
173
+
174
+ const [hit] = _raycaster.intersectObjects(objects, true);
175
+ if (!hit) return null;
176
+
177
+ return {
178
+ point: hit.point.clone(),
179
+ bodyId: findBodyId(hit.object),
180
+ geomId: typeof hit.object.userData.geomID === 'number' ? hit.object.userData.geomID : -1,
181
+ distance: hit.distance,
182
+ ndc: [ndcX, ndcY],
183
+ imageSize: [width, height],
184
+ source: getProjectionSource(options),
185
+ };
186
+ }
package/src/types.ts CHANGED
@@ -747,6 +747,42 @@ export interface RayHit {
747
747
  distance: number;
748
748
  }
749
749
 
750
+ export type ImagePointCoordinateSpace =
751
+ | 'normalized'
752
+ | 'normalized-1000'
753
+ | 'pixel'
754
+ | 'ndc';
755
+
756
+ export interface ImagePointProjectionOptions extends CameraFrameCaptureOptions {
757
+ /** X coordinate in the selected coordinate space. Defaults to normalized 0..1. */
758
+ x: number;
759
+ /** Y coordinate in the selected coordinate space. Defaults to normalized 0..1 with origin at top-left. */
760
+ y: number;
761
+ /**
762
+ * Coordinate convention for x/y:
763
+ * - normalized: 0..1 image coordinates, top-left origin
764
+ * - normalized-1000: 0..1000 detector coordinates, top-left origin
765
+ * - pixel: pixel coordinates, top-left origin
766
+ * - ndc: Three.js normalized device coordinates, -1..1
767
+ */
768
+ coordinateSpace?: ImagePointCoordinateSpace;
769
+ /** Image width for pixel coordinates. Falls back to `width` or renderer canvas width. */
770
+ imageWidth?: number;
771
+ /** Image height for pixel coordinates. Falls back to `height` or renderer canvas height. */
772
+ imageHeight?: number;
773
+ /** Ignore hits farther than this distance from the camera ray origin. */
774
+ maxDistance?: number;
775
+ }
776
+
777
+ export interface ImagePointProjectionResult extends RayHit {
778
+ /** NDC coordinates used for raycasting. */
779
+ ndc: [number, number];
780
+ /** Image dimensions used when interpreting pixel coordinates. */
781
+ imageSize: [number, number];
782
+ /** Camera pose provenance, matching camera-frame capture results. */
783
+ source: CameraFrameCaptureSource;
784
+ }
785
+
750
786
  // ---- Model Options (spec 5.3) ----
751
787
 
752
788
  export interface ModelOptions {
@@ -1404,6 +1440,7 @@ export interface MujocoSimAPI {
1404
1440
  cameraPos: THREE.Vector3,
1405
1441
  lookAt: THREE.Vector3
1406
1442
  ): { point: THREE.Vector3; bodyId: number; geomId: number } | null;
1443
+ projectImagePointTo3D(options: ImagePointProjectionOptions): ImagePointProjectionResult | null;
1407
1444
 
1408
1445
  // Domain randomization (spec 10.3)
1409
1446
  setBodyMass(name: Bodies, mass: number): void;