mujoco-react 9.5.0 → 10.0.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.
@@ -0,0 +1,170 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { useFrame } from '@react-three/fiber';
7
+ import { useEffect, useMemo, useRef } from 'react';
8
+ import * as THREE from 'three';
9
+ import type { KeyboardIkTargetAction, KeyboardIkTargetBinding, KeyboardIkTargetConfig } from '../types';
10
+
11
+ const DEFAULT_TRANSLATE_SPEED = 0.25;
12
+ const DEFAULT_ROTATE_SPEED = 1.0;
13
+
14
+ const _translation = new THREE.Vector3();
15
+ const _axis = new THREE.Vector3();
16
+ const _quat = new THREE.Quaternion();
17
+
18
+ function actionSign(action: KeyboardIkTargetAction): 1 | -1 {
19
+ return action.endsWith('+') ? 1 : -1;
20
+ }
21
+
22
+ function actionBase(action: KeyboardIkTargetAction) {
23
+ return action.slice(0, -1);
24
+ }
25
+
26
+ function applyRotation(
27
+ target: THREE.Object3D,
28
+ action: KeyboardIkTargetAction,
29
+ amount: number,
30
+ frame: 'world' | 'target',
31
+ ) {
32
+ const base = actionBase(action);
33
+ if (base === 'pitch') {
34
+ _axis.set(1, 0, 0);
35
+ } else if (base === 'yaw') {
36
+ _axis.set(0, 1, 0);
37
+ } else if (base === 'roll') {
38
+ _axis.set(0, 0, 1);
39
+ } else {
40
+ return;
41
+ }
42
+
43
+ _quat.setFromAxisAngle(_axis, amount);
44
+ if (frame === 'target') {
45
+ target.quaternion.multiply(_quat);
46
+ } else {
47
+ target.quaternion.premultiply(_quat);
48
+ }
49
+ }
50
+
51
+ function addTranslation(action: KeyboardIkTargetAction, amount: number) {
52
+ switch (action) {
53
+ case 'x+':
54
+ _translation.x += amount;
55
+ break;
56
+ case 'x-':
57
+ _translation.x -= amount;
58
+ break;
59
+ case 'y+':
60
+ _translation.y += amount;
61
+ break;
62
+ case 'y-':
63
+ _translation.y -= amount;
64
+ break;
65
+ case 'z+':
66
+ _translation.z += amount;
67
+ break;
68
+ case 'z-':
69
+ _translation.z -= amount;
70
+ break;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Moves an existing IK target from keyboard input.
76
+ *
77
+ * This hook is intentionally robot-agnostic: it only edits the target owned by
78
+ * `useIkController`, then lets the normal IK solver write robot controls.
79
+ */
80
+ export function useKeyboardIkTarget(config: KeyboardIkTargetConfig | null) {
81
+ const pressedRef = useRef(new Set<string>());
82
+ const wasActiveRef = useRef(false);
83
+ const configRef = useRef(config);
84
+ configRef.current = config;
85
+
86
+ const boundCodes = useMemo(() => {
87
+ return new Set((config?.bindings ?? []).map((binding) => binding.code));
88
+ }, [config?.bindings]);
89
+
90
+ useEffect(() => {
91
+ const onKeyDown = (event: KeyboardEvent) => {
92
+ const current = configRef.current;
93
+ if (!current || current.enabled === false || !boundCodes.has(event.code)) return;
94
+ if (current.preventDefault !== false) event.preventDefault();
95
+ pressedRef.current.add(event.code);
96
+ };
97
+ const onKeyUp = (event: KeyboardEvent) => {
98
+ const current = configRef.current;
99
+ if (boundCodes.has(event.code) && current?.preventDefault !== false) {
100
+ event.preventDefault();
101
+ }
102
+ pressedRef.current.delete(event.code);
103
+ };
104
+ const onBlur = () => {
105
+ pressedRef.current.clear();
106
+ wasActiveRef.current = false;
107
+ };
108
+
109
+ window.addEventListener('keydown', onKeyDown);
110
+ window.addEventListener('keyup', onKeyUp);
111
+ window.addEventListener('blur', onBlur);
112
+
113
+ return () => {
114
+ window.removeEventListener('keydown', onKeyDown);
115
+ window.removeEventListener('keyup', onKeyUp);
116
+ window.removeEventListener('blur', onBlur);
117
+ };
118
+ }, [boundCodes]);
119
+
120
+ useFrame((_state, delta) => {
121
+ const current = configRef.current;
122
+ const controller = current?.controller;
123
+ if (!current || current.enabled === false || !controller) {
124
+ wasActiveRef.current = false;
125
+ return;
126
+ }
127
+
128
+ const activeBindings: KeyboardIkTargetBinding[] = [];
129
+ for (const binding of current.bindings) {
130
+ if (pressedRef.current.has(binding.code)) activeBindings.push(binding);
131
+ }
132
+
133
+ if (activeBindings.length === 0) {
134
+ wasActiveRef.current = false;
135
+ return;
136
+ }
137
+
138
+ if (!wasActiveRef.current) {
139
+ if (current.syncOnStart !== false) controller.syncTargetToSite();
140
+ if (current.autoEnableIk !== false && !controller.ikEnabledRef.current) {
141
+ controller.setIkEnabled(true);
142
+ }
143
+ }
144
+ wasActiveRef.current = true;
145
+
146
+ const target = controller.ikTargetRef.current;
147
+ if (!target) return;
148
+
149
+ const frame = current.frame ?? 'world';
150
+ _translation.set(0, 0, 0);
151
+
152
+ for (const binding of activeBindings) {
153
+ const translateSpeed = binding.translateSpeed ?? current.translateSpeed ?? DEFAULT_TRANSLATE_SPEED;
154
+ const rotateSpeed = binding.rotateSpeed ?? current.rotateSpeed ?? DEFAULT_ROTATE_SPEED;
155
+ const amount = actionSign(binding.action) * delta;
156
+ const base = actionBase(binding.action);
157
+
158
+ if (base === 'x' || base === 'y' || base === 'z') {
159
+ addTranslation(binding.action, translateSpeed * delta);
160
+ } else {
161
+ applyRotation(target, binding.action, rotateSpeed * amount, frame);
162
+ }
163
+ }
164
+
165
+ if (_translation.lengthSq() > 0) {
166
+ if (frame === 'target') _translation.applyQuaternion(target.quaternion);
167
+ target.position.add(_translation);
168
+ }
169
+ });
170
+ }
package/src/index.ts CHANGED
@@ -91,6 +91,7 @@ export { useBodyState } from './hooks/useBodyState';
91
91
  export { useCtrl } from './hooks/useCtrl';
92
92
  export { useContacts, useContactEvents } from './hooks/useContacts';
93
93
  export { useKeyboardTeleop } from './hooks/useKeyboardTeleop';
94
+ export { useKeyboardIkTarget } from './hooks/useKeyboardIkTarget';
94
95
  export { usePolicy } from './hooks/usePolicy';
95
96
  export { useObservation } from './hooks/useObservation';
96
97
  export { useTrajectoryPlayer } from './hooks/useTrajectoryPlayer';
@@ -211,6 +212,9 @@ export type {
211
212
  // Keyboard teleop
212
213
  KeyBinding,
213
214
  KeyboardTeleopConfig,
215
+ KeyboardIkTargetAction,
216
+ KeyboardIkTargetBinding,
217
+ KeyboardIkTargetConfig,
214
218
  // Policy
215
219
  PolicyConfig,
216
220
  PolicyVector,
@@ -293,11 +297,15 @@ export type {
293
297
  SensorHandle,
294
298
  BodyStateResult,
295
299
  JointStateResult,
300
+ JointStateKind,
301
+ JointStateOptions,
302
+ ScalarJointStateResult,
303
+ ArrayJointStateResult,
296
304
  // Register (type-safe named resources)
297
305
  Register,
298
- RegisteredRobotMap,
299
- RobotResource,
300
- Robots,
306
+ RegisteredModelMap,
307
+ ModelResource,
308
+ Models,
301
309
  Actuators,
302
310
  Sensors,
303
311
  Bodies,
@@ -309,16 +317,16 @@ export type {
309
317
  } from './types';
310
318
 
311
319
  export {
312
- registerRobotResources,
313
- RobotResources,
314
- RobotActuators,
315
- RobotSensors,
316
- RobotBodies,
317
- RobotJoints,
318
- RobotSites,
319
- RobotGeoms,
320
- RobotKeyframes,
321
- RobotCameras,
320
+ registerModelResources,
321
+ ModelResources,
322
+ ModelActuators,
323
+ ModelSensors,
324
+ ModelBodies,
325
+ ModelJoints,
326
+ ModelSites,
327
+ ModelGeoms,
328
+ ModelKeyframes,
329
+ ModelCameras,
322
330
  SplatEnvironmentReadinessStatus,
323
331
  } from './types';
324
332
 
@@ -22,10 +22,13 @@ export interface ReflectorOptions {
22
22
  interface ReflectorMesh extends THREE.Mesh {
23
23
  type: string;
24
24
  material: THREE.MeshPhysicalMaterial;
25
- // tslint:disable-next-line:no-any
26
- onBeforeRender: (renderer: any, scene: any, camera: any) => void;
25
+ onBeforeRender: (renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.Camera) => void;
27
26
  }
28
27
 
28
+ type CameraWithViewport = THREE.Camera & {
29
+ viewport?: THREE.Vector4;
30
+ };
31
+
29
32
  /**
30
33
  * Reflector
31
34
  * Creates a reflective surface.
@@ -199,8 +202,7 @@ export class Reflector extends THREE.Mesh {
199
202
  renderer.setRenderTarget(currentRenderTarget);
200
203
 
201
204
  // Restore viewport
202
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
203
- const viewport = (camera as any).viewport;
205
+ const viewport = (camera as CameraWithViewport).viewport;
204
206
  if (viewport !== undefined) {
205
207
  renderer.state.viewport(viewport);
206
208
  }
@@ -222,4 +224,4 @@ export class Reflector extends THREE.Mesh {
222
224
  mesh.material.dispose();
223
225
  }
224
226
  }
225
- }
227
+ }
package/src/types.ts CHANGED
@@ -17,7 +17,7 @@ import * as THREE from 'three';
17
17
  * ```ts
18
18
  * declare module 'mujoco-react' {
19
19
  * interface Register {
20
- * robots: {
20
+ * models: {
21
21
  * panda: {
22
22
  * actuators: 'joint1' | 'joint2' | 'gripper';
23
23
  * sensors: 'force_sensor' | 'torque_sensor';
@@ -35,45 +35,45 @@ import * as THREE from 'three';
35
35
  */
36
36
  export interface Register {}
37
37
 
38
- export type RegisteredRobotMap = Register extends { robots: infer T extends Record<string, Record<string, string>> }
38
+ export type RegisteredModelMap = Register extends { models: infer T extends Record<string, Record<string, string>> }
39
39
  ? T
40
40
  : never;
41
- export type Robots = [RegisteredRobotMap] extends [never] ? string : Extract<keyof RegisteredRobotMap, string>;
42
- export type RobotResource<TRobot extends string, TKey extends string> =
43
- [RegisteredRobotMap] extends [never]
41
+ export type Models = [RegisteredModelMap] extends [never] ? string : Extract<keyof RegisteredModelMap, string>;
42
+ export type ModelResource<TModel extends string, TKey extends string> =
43
+ [RegisteredModelMap] extends [never]
44
44
  ? string
45
- : TRobot extends keyof RegisteredRobotMap
46
- ? TKey extends keyof RegisteredRobotMap[TRobot]
47
- ? RegisteredRobotMap[TRobot][TKey]
45
+ : TModel extends keyof RegisteredModelMap
46
+ ? TKey extends keyof RegisteredModelMap[TModel]
47
+ ? RegisteredModelMap[TModel][TKey]
48
48
  : string
49
49
  : never;
50
- export type RobotActuators<TRobot extends string> = RobotResource<TRobot, 'actuators'>;
51
- export type RobotSensors<TRobot extends string> = RobotResource<TRobot, 'sensors'>;
52
- export type RobotBodies<TRobot extends string> = RobotResource<TRobot, 'bodies'>;
53
- export type RobotJoints<TRobot extends string> = RobotResource<TRobot, 'joints'>;
54
- export type RobotSites<TRobot extends string> = RobotResource<TRobot, 'sites'>;
55
- export type RobotGeoms<TRobot extends string> = RobotResource<TRobot, 'geoms'>;
56
- export type RobotKeyframes<TRobot extends string> = RobotResource<TRobot, 'keyframes'>;
57
- export type RobotCameras<TRobot extends string> = RobotResource<TRobot, 'cameras'>;
50
+ export type ModelActuators<TModel extends string> = ModelResource<TModel, 'actuators'>;
51
+ export type ModelSensors<TModel extends string> = ModelResource<TModel, 'sensors'>;
52
+ export type ModelBodies<TModel extends string> = ModelResource<TModel, 'bodies'>;
53
+ export type ModelJoints<TModel extends string> = ModelResource<TModel, 'joints'>;
54
+ export type ModelSites<TModel extends string> = ModelResource<TModel, 'sites'>;
55
+ export type ModelGeoms<TModel extends string> = ModelResource<TModel, 'geoms'>;
56
+ export type ModelKeyframes<TModel extends string> = ModelResource<TModel, 'keyframes'>;
57
+ export type ModelCameras<TModel extends string> = ModelResource<TModel, 'cameras'>;
58
58
 
59
59
  export type RegisterResourceKey = 'actuators' | 'sensors' | 'bodies' | 'joints' | 'sites' | 'geoms' | 'keyframes' | 'cameras';
60
- export type RobotResourceObject<TRobot extends string, TKey extends RegisterResourceKey> =
61
- string extends RobotResource<TRobot, TKey>
60
+ export type ModelResourceObject<TModel extends string, TKey extends RegisterResourceKey> =
61
+ string extends ModelResource<TModel, TKey>
62
62
  ? Record<string, string>
63
- : { readonly [K in RobotResource<TRobot, TKey>]: K };
64
- export type RobotResourceCategory<TKey extends RegisterResourceKey> =
65
- string extends Robots
63
+ : { readonly [K in ModelResource<TModel, TKey>]: K };
64
+ export type ModelResourceCategory<TKey extends RegisterResourceKey> =
65
+ string extends Models
66
66
  ? Record<string, Record<string, string>>
67
- : { readonly [TRobot in Robots]: RobotResourceObject<TRobot, TKey> };
68
- export type RobotResourceRegistry =
69
- string extends Robots
67
+ : { readonly [TModel in Models]: ModelResourceObject<TModel, TKey> };
68
+ export type ModelResourceRegistry =
69
+ string extends Models
70
70
  ? Record<string, Record<RegisterResourceKey, Record<string, string>>>
71
- : { readonly [TRobot in Robots]: { readonly [TKey in RegisterResourceKey]: RobotResourceObject<TRobot, TKey> } };
71
+ : { readonly [TModel in Models]: { readonly [TKey in RegisterResourceKey]: ModelResourceObject<TModel, TKey> } };
72
72
 
73
- type RuntimeRobotResources = Record<string, Record<RegisterResourceKey, Record<string, string>>>;
74
- type RuntimeRobotResourceRegistration = Readonly<Record<string, Readonly<Record<RegisterResourceKey, Readonly<Record<string, string>>>>>>;
73
+ type RuntimeModelResources = Record<string, Record<RegisterResourceKey, Record<string, string>>>;
74
+ type RuntimeModelResourceRegistration = Readonly<Record<string, Readonly<Record<RegisterResourceKey, Readonly<Record<string, string>>>>>>;
75
75
 
76
- const runtimeRobotResources: RuntimeRobotResources = {};
76
+ const runtimeModelResources: RuntimeModelResources = {};
77
77
  const REGISTER_RESOURCE_KEYS: RegisterResourceKey[] = ['actuators', 'sensors', 'bodies', 'joints', 'sites', 'geoms', 'keyframes', 'cameras'];
78
78
 
79
79
  function createEmptyRuntimeResources(): Record<RegisterResourceKey, Record<string, string>> {
@@ -89,54 +89,54 @@ function createEmptyRuntimeResources(): Record<RegisterResourceKey, Record<strin
89
89
  };
90
90
  }
91
91
 
92
- export function registerRobotResources(resources: RuntimeRobotResourceRegistration): void {
93
- for (const [robot, robotResources] of Object.entries(resources)) {
94
- const existing = runtimeRobotResources[robot] ?? createEmptyRuntimeResources();
92
+ export function registerModelResources(resources: RuntimeModelResourceRegistration): void {
93
+ for (const [model, modelResources] of Object.entries(resources)) {
94
+ const existing = runtimeModelResources[model] ?? createEmptyRuntimeResources();
95
95
  for (const key of REGISTER_RESOURCE_KEYS) {
96
- existing[key] = { ...existing[key], ...(robotResources[key] ?? {}) };
96
+ existing[key] = { ...existing[key], ...(modelResources[key] ?? {}) };
97
97
  }
98
- runtimeRobotResources[robot] = existing;
98
+ runtimeModelResources[model] = existing;
99
99
  }
100
100
  }
101
101
 
102
- function createResourceCategory<TKey extends RegisterResourceKey>(key: TKey): RobotResourceCategory<TKey> {
102
+ function createResourceCategory<TKey extends RegisterResourceKey>(key: TKey): ModelResourceCategory<TKey> {
103
103
  return new Proxy({}, {
104
- get(_target, robot) {
105
- if (typeof robot !== 'string') return undefined;
106
- return runtimeRobotResources[robot]?.[key] ?? {};
104
+ get(_target, model) {
105
+ if (typeof model !== 'string') return undefined;
106
+ return runtimeModelResources[model]?.[key] ?? {};
107
107
  },
108
108
  ownKeys() {
109
- return Reflect.ownKeys(runtimeRobotResources);
109
+ return Reflect.ownKeys(runtimeModelResources);
110
110
  },
111
- getOwnPropertyDescriptor(_target, robot) {
112
- if (typeof robot !== 'string' || !(robot in runtimeRobotResources)) return undefined;
111
+ getOwnPropertyDescriptor(_target, model) {
112
+ if (typeof model !== 'string' || !(model in runtimeModelResources)) return undefined;
113
113
  return { enumerable: true, configurable: true };
114
114
  },
115
- }) as RobotResourceCategory<TKey>;
115
+ }) as ModelResourceCategory<TKey>;
116
116
  }
117
117
 
118
- export const RobotResources: RobotResourceRegistry = new Proxy(runtimeRobotResources, {
119
- get(target, robot) {
120
- if (typeof robot !== 'string') return undefined;
121
- return target[robot] ?? createEmptyRuntimeResources();
118
+ export const ModelResources: ModelResourceRegistry = new Proxy(runtimeModelResources, {
119
+ get(target, model) {
120
+ if (typeof model !== 'string') return undefined;
121
+ return target[model] ?? createEmptyRuntimeResources();
122
122
  },
123
123
  ownKeys(target) {
124
124
  return Reflect.ownKeys(target);
125
125
  },
126
- getOwnPropertyDescriptor(target, robot) {
127
- if (typeof robot !== 'string' || !(robot in target)) return undefined;
126
+ getOwnPropertyDescriptor(target, model) {
127
+ if (typeof model !== 'string' || !(model in target)) return undefined;
128
128
  return { enumerable: true, configurable: true };
129
129
  },
130
- }) as RobotResourceRegistry;
130
+ }) as ModelResourceRegistry;
131
131
 
132
- export const RobotActuators: RobotResourceCategory<'actuators'> = createResourceCategory('actuators');
133
- export const RobotSensors: RobotResourceCategory<'sensors'> = createResourceCategory('sensors');
134
- export const RobotBodies: RobotResourceCategory<'bodies'> = createResourceCategory('bodies');
135
- export const RobotJoints: RobotResourceCategory<'joints'> = createResourceCategory('joints');
136
- export const RobotSites: RobotResourceCategory<'sites'> = createResourceCategory('sites');
137
- export const RobotGeoms: RobotResourceCategory<'geoms'> = createResourceCategory('geoms');
138
- export const RobotKeyframes: RobotResourceCategory<'keyframes'> = createResourceCategory('keyframes');
139
- export const RobotCameras: RobotResourceCategory<'cameras'> = createResourceCategory('cameras');
132
+ export const ModelActuators: ModelResourceCategory<'actuators'> = createResourceCategory('actuators');
133
+ export const ModelSensors: ModelResourceCategory<'sensors'> = createResourceCategory('sensors');
134
+ export const ModelBodies: ModelResourceCategory<'bodies'> = createResourceCategory('bodies');
135
+ export const ModelJoints: ModelResourceCategory<'joints'> = createResourceCategory('joints');
136
+ export const ModelSites: ModelResourceCategory<'sites'> = createResourceCategory('sites');
137
+ export const ModelGeoms: ModelResourceCategory<'geoms'> = createResourceCategory('geoms');
138
+ export const ModelKeyframes: ModelResourceCategory<'keyframes'> = createResourceCategory('keyframes');
139
+ export const ModelCameras: ModelResourceCategory<'cameras'> = createResourceCategory('cameras');
140
140
 
141
141
  export type Actuators = Register extends { actuators: infer T extends string } ? T : string;
142
142
  export type Sensors = Register extends { sensors: infer T extends string } ? T : string;
@@ -516,6 +516,14 @@ export interface IkConfig {
516
516
  ikSolveFn?: IKSolveFn;
517
517
  /** DLS damping. Default: 0.01. */
518
518
  damping?: number;
519
+ /** Position error weight for the built-in DLS solver. Default: 1. */
520
+ posWeight?: number;
521
+ /** Orientation error weight for the built-in DLS solver. Default: 0.3. */
522
+ rotWeight?: number;
523
+ /** Solver convergence tolerance. Default: 1e-3. */
524
+ tolerance?: number;
525
+ /** Finite-difference step used by the built-in DLS solver. Default: 1e-6. */
526
+ epsilon?: number;
519
527
  /** Max solver iterations. Default: 50. */
520
528
  maxIterations?: number;
521
529
  }
@@ -864,6 +872,48 @@ export interface IkGizmoDragInput {
864
872
  quaternion: THREE.Quaternion;
865
873
  }
866
874
 
875
+ export type KeyboardIkTargetAction =
876
+ | 'x+'
877
+ | 'x-'
878
+ | 'y+'
879
+ | 'y-'
880
+ | 'z+'
881
+ | 'z-'
882
+ | 'pitch+'
883
+ | 'pitch-'
884
+ | 'yaw+'
885
+ | 'yaw-'
886
+ | 'roll+'
887
+ | 'roll-';
888
+
889
+ export interface KeyboardIkTargetBinding {
890
+ /** KeyboardEvent.code, e.g. `KeyW`, `ArrowUp`, `Space`. */
891
+ code: string;
892
+ action: KeyboardIkTargetAction;
893
+ /** Override translation speed in meters/second for this binding. */
894
+ translateSpeed?: number;
895
+ /** Override rotation speed in radians/second for this binding. */
896
+ rotateSpeed?: number;
897
+ }
898
+
899
+ export interface KeyboardIkTargetConfig {
900
+ controller: IkContextValue | null;
901
+ bindings: KeyboardIkTargetBinding[];
902
+ enabled?: boolean;
903
+ /** Default translation speed in meters/second. Default: 0.25. */
904
+ translateSpeed?: number;
905
+ /** Default rotation speed in radians/second. Default: 1.0. */
906
+ rotateSpeed?: number;
907
+ /** Apply translation and rotation axes in world or current target space. Default: `world`. */
908
+ frame?: 'world' | 'target';
909
+ /** Enable IK while keys are active. Default: true. */
910
+ autoEnableIk?: boolean;
911
+ /** Sync target to current site when keyboard control starts. Default: true. */
912
+ syncOnStart?: boolean;
913
+ /** Prevent browser default behavior for bound keys. Default: true. */
914
+ preventDefault?: boolean;
915
+ }
916
+
867
917
  export interface DragInteractionProps {
868
918
  stiffness?: number;
869
919
  showArrow?: boolean;
@@ -1482,7 +1532,30 @@ export interface BodyStateResult {
1482
1532
  angularVelocity: React.RefObject<THREE.Vector3>;
1483
1533
  }
1484
1534
 
1535
+ export type JointStateKind = 'auto' | 'scalar' | 'array';
1536
+
1537
+ export interface JointStateOptions {
1538
+ /**
1539
+ * Expected joint value shape.
1540
+ *
1541
+ * - `auto`: scalar joints return numbers, ball/free joints return Float64Array.
1542
+ * - `scalar`: return numeric refs for hinge/slide joints.
1543
+ * - `array`: return Float64Array refs for ball/free joints.
1544
+ */
1545
+ kind?: JointStateKind;
1546
+ }
1547
+
1485
1548
  export interface JointStateResult {
1486
1549
  position: React.RefObject<number | Float64Array>;
1487
1550
  velocity: React.RefObject<number | Float64Array>;
1488
1551
  }
1552
+
1553
+ export interface ScalarJointStateResult {
1554
+ position: React.RefObject<number>;
1555
+ velocity: React.RefObject<number>;
1556
+ }
1557
+
1558
+ export interface ArrayJointStateResult {
1559
+ position: React.RefObject<Float64Array>;
1560
+ velocity: React.RefObject<Float64Array>;
1561
+ }
package/src/vite.ts CHANGED
@@ -221,21 +221,21 @@ function renderRegister(
221
221
  const fields = REGISTER_KEYS
222
222
  .filter((key) => names[key].size > 0)
223
223
  .map((key) => ` ${key}: ${renderUnion(names[key])};`);
224
- const robots = models
225
- .map((model) => ` ${quoteProperty(model.id)}: {\n${renderRobotFields(model.names)}\n };`);
224
+ const modelFields = models
225
+ .map((model) => ` ${quoteProperty(model.id)}: {\n${renderModelFields(model.names)}\n };`);
226
226
  const namespaceAliases = renderNamespaceAliases(models);
227
- const registerImport = declarationsOnly ? '' : `import { registerRobotResources } from '${moduleName}';\n`;
228
- const resources = declarationsOnly ? '' : `${renderResourceTypes(models)}\n\n${renderResourceConstants(models)}\n\nregisterRobotResources(generatedRobotResources);\n\n`;
227
+ const registerImport = declarationsOnly ? '' : `import { registerModelResources } from '${moduleName}';\n`;
228
+ const resources = declarationsOnly ? '' : `${renderResourceTypes(models)}\n\n${renderResourceConstants(models)}\n\nregisterModelResources(generatedModelResources);\n\n`;
229
229
 
230
230
  return `// Auto-generated by mujoco-react. Do not edit.
231
231
  // Regenerate by running Vite with the mujocoReact() plugin or \`mujoco-react codegen\`.
232
232
 
233
- ${registerImport}import type { RobotResource } from '${moduleName}';
233
+ ${registerImport}import type { ModelResource } from '${moduleName}';
234
234
 
235
235
  ${resources}declare module '${moduleName}' {
236
236
  interface Register {
237
- robots: {
238
- ${robots.join('\n')}
237
+ models: {
238
+ ${modelFields.join('\n')}
239
239
  };
240
240
  ${fields.join('\n')}
241
241
  }
@@ -249,7 +249,7 @@ function renderResourceTypes(models: readonly ModelEntry[]): string {
249
249
  const modelTypes = models
250
250
  .map((model) => ` readonly ${quoteProperty(model.id)}: {\n${renderResourceTypeFields(model.names)}\n };`)
251
251
  .join('\n');
252
- return `type GeneratedRobotResources = {\n${modelTypes}\n};`;
252
+ return `type GeneratedModelResources = {\n${modelTypes}\n};`;
253
253
  }
254
254
 
255
255
  function renderResourceTypeFields(names: Record<RegisterKey, Set<string>>): string {
@@ -268,7 +268,7 @@ function renderResourceConstants(models: readonly ModelEntry[]): string {
268
268
  const entries = models
269
269
  .map((model) => ` ${quoteProperty(model.id)}: {\n${renderResourceConstantFields(model.names)}\n },`)
270
270
  .join('\n');
271
- return `const generatedRobotResources: GeneratedRobotResources = {\n${entries}\n};`;
271
+ return `const generatedModelResources: GeneratedModelResources = {\n${entries}\n};`;
272
272
  }
273
273
 
274
274
  function renderResourceConstantFields(names: Record<RegisterKey, Set<string>>): string {
@@ -283,7 +283,7 @@ function renderResourceObject(values: Set<string>): string {
283
283
  return entries.length > 0 ? `{\n${entries.join('\n')}\n }` : '{}';
284
284
  }
285
285
 
286
- function renderRobotFields(names: Record<RegisterKey, Set<string>>): string {
286
+ function renderModelFields(names: Record<RegisterKey, Set<string>>): string {
287
287
  return REGISTER_KEYS
288
288
  .map((key) => ` ${key}: ${names[key].size > 0 ? renderUnion(names[key]) : 'never'};`)
289
289
  .join('\n');
@@ -295,23 +295,23 @@ function renderUnion(values: Set<string>): string {
295
295
 
296
296
  function renderNamespaceAliases(models: readonly ModelEntry[]): string {
297
297
  const namespaces: Record<RegisterKey, string> = {
298
- actuators: 'RobotActuators',
299
- sensors: 'RobotSensors',
300
- bodies: 'RobotBodies',
301
- joints: 'RobotJoints',
302
- sites: 'RobotSites',
303
- geoms: 'RobotGeoms',
304
- keyframes: 'RobotKeyframes',
305
- cameras: 'RobotCameras',
298
+ actuators: 'ModelActuators',
299
+ sensors: 'ModelSensors',
300
+ bodies: 'ModelBodies',
301
+ joints: 'ModelJoints',
302
+ sites: 'ModelSites',
303
+ geoms: 'ModelGeoms',
304
+ keyframes: 'ModelKeyframes',
305
+ cameras: 'ModelCameras',
306
306
  };
307
307
 
308
308
  const blocks = REGISTER_KEYS
309
309
  .map((key) => {
310
- const aliases = models
310
+ const modelAliases = models
311
311
  .filter((model) => isIdentifier(model.id))
312
- .map((model) => ` export type ${model.id} = RobotResource<'${escapeTs(model.id)}', '${key}'>;`);
313
- if (aliases.length === 0) return '';
314
- return ` export namespace ${namespaces[key]} {\n${aliases.join('\n')}\n }`;
312
+ .map((model) => ` export type ${model.id} = ModelResource<'${escapeTs(model.id)}', '${key}'>;`);
313
+ if (modelAliases.length === 0) return '';
314
+ return ` export namespace ${namespaces[key]} {\n${modelAliases.join('\n')}\n }`;
315
315
  })
316
316
  .filter(Boolean)
317
317
  .join('\n\n');