mujoco-react 9.0.0 → 9.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/README.md CHANGED
@@ -213,18 +213,20 @@ function Scene() {
213
213
  ## Write Controllers
214
214
 
215
215
  ```tsx
216
- import { useBeforePhysicsStep } from "mujoco-react";
216
+ import { RobotActuators, useBeforePhysicsStep, useCtrl } from "mujoco-react";
217
217
 
218
218
  function MyController() {
219
+ const shoulder = useCtrl(RobotActuators.franka.actuator1);
220
+
219
221
  useBeforePhysicsStep(({ data }) => {
220
- data.ctrl[0] = Math.sin(data.time);
222
+ shoulder.write(Math.sin(data.time));
221
223
  });
222
224
 
223
225
  return null;
224
226
  }
225
227
  ```
226
228
 
227
- Controllers are just React children that read sensors, write `data.ctrl`, apply forces, or call the `MujocoSimAPI` at physics-step time.
229
+ Controllers are just React children that read sensors, write named controls, apply forces, or call the `MujocoSimAPI` at physics-step time.
228
230
 
229
231
  With generated resource values, reusable controllers can be scoped to one robot without hand-typing names:
230
232
 
@@ -388,15 +390,20 @@ function useWebSocketControls(url: string) {
388
390
  For reusable controllers with typed config, default merging, and children, use the `createController` factory:
389
391
 
390
392
  ```tsx
391
- import { createController, useCtrl, useBeforePhysicsStep } from "mujoco-react";
393
+ import {
394
+ RobotActuators,
395
+ createController,
396
+ useBeforePhysicsStep,
397
+ useCtrl,
398
+ } from "mujoco-react";
392
399
 
393
400
  export const MyController = createController<{ gain: number }>(
394
401
  { name: "MyController", defaultConfig: { gain: 1.0 } },
395
402
  ({ config, children }) => {
396
- const shoulder = useCtrl("shoulder");
403
+ const shoulder = useCtrl(RobotActuators.franka.actuator1);
397
404
 
398
- useBeforePhysicsStep(() => {
399
- shoulder.write(config.gain * Math.sin(Date.now() / 1000));
405
+ useBeforePhysicsStep(({ data }) => {
406
+ shoulder.write(config.gain * Math.sin(data.time));
400
407
  });
401
408
 
402
409
  return <>{children}</>;
@@ -826,8 +833,12 @@ if (mujoco) {
826
833
  Run logic **before** `mj_step` each frame. Write to `data.ctrl`, apply forces, drive automation.
827
834
 
828
835
  ```tsx
836
+ import { RobotActuators, useBeforePhysicsStep, useCtrl } from "mujoco-react";
837
+
838
+ const shoulder = useCtrl(RobotActuators.franka.actuator1);
839
+
829
840
  useBeforePhysicsStep(({ data }) => {
830
- data.ctrl[0] = Math.sin(data.time);
841
+ shoulder.write(Math.sin(data.time));
831
842
  });
832
843
  ```
833
844
 
@@ -861,8 +872,8 @@ Read sensor values by name. Returns a `SensorHandle` with `read()`, `dim`, and `
861
872
  ```tsx
862
873
  import { RobotSensors, useSensor } from "mujoco-react";
863
874
 
864
- const force = useSensor(RobotSensors.franka.force_sensor);
865
- // force.read() -> Float64Array, force.dim -> number
875
+ const imu = useSensor(RobotSensors.g1["imu-torso-angular-velocity"]);
876
+ // imu.read() -> Float64Array, imu.dim -> number
866
877
  ```
867
878
 
868
879
  ### `useBodyState(name)`
@@ -983,6 +994,27 @@ const video = useVideoRecorder({ fps: 30, mimeType: "video/webm" });
983
994
  // video.start(), video.stop() -> returns Blob
984
995
  ```
985
996
 
997
+ ### Frame Capture
998
+
999
+ Capture dataset/debug stills from the rendered MuJoCo canvas:
1000
+
1001
+ ```tsx
1002
+ const apiRef = useRef<MujocoSimAPI>(null);
1003
+
1004
+ const frame = await apiRef.current?.captureFrame({ type: "image/png" });
1005
+ const blob = await apiRef.current?.captureFrameBlob({ type: "image/png" });
1006
+ ```
1007
+
1008
+ Use `useFrameCapture()` or the standalone `captureFrame()` helpers when you own
1009
+ the canvas or want to capture a custom container.
1010
+
1011
+ Use `captureCameraFrame()` / `captureCameraFrameBlob()` when dataset generation
1012
+ needs a fixed offscreen camera pose or resolution without moving the user's
1013
+ interactive viewport.
1014
+
1015
+ Use `recordCameraSequence()` / `useCameraSequenceRecorder()` to step policy
1016
+ rollouts and capture synchronized frames from one or more fixed camera configs.
1017
+
986
1018
  ### `useCtrlNoise(config)`
987
1019
 
988
1020
  Apply Gaussian noise to controls for robustness testing:
@@ -1104,7 +1136,10 @@ The full API object available via `ref` or `useMujoco()` (when `isReady`):
1104
1136
  |--------|-------------|
1105
1137
  | `raycast(origin, direction, maxDist?)` | Physics raycast via `mj_ray` |
1106
1138
  | `project2DTo3D(x, y, camPos, lookAt)` | Screen-to-world raycast (returns bodyId + geomId) |
1139
+ | `getCanvas()` | Return the underlying R3F canvas element |
1107
1140
  | `getCanvasSnapshot(w?, h?, mime?)` | Base64 screenshot |
1141
+ | `captureFrame(options?)` | Capture the canvas as a data URL |
1142
+ | `captureFrameBlob(options?)` | Capture the canvas as a Blob |
1108
1143
 
1109
1144
  ### Scene Management
1110
1145
 
package/dist/index.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { M as MujocoContextValue, a as MujocoCanvasProps, b as MujocoSimAPI, S as SceneConfig, R as ReadyCallbackInput, c as StepCallbackInput, d as SelectionCallbackInput, e as MujocoModule, P as PhysicsStepCallback, f as MujocoModel, g as MujocoData, C as ControlGroupInfo, A as ActuatedJointInfo, h as ControlGroupSelector, O as ObservationConfig, i as ObservationResult, I as IkConfig, j as IkContextValue, B as BodyProps, k as IkGizmoProps, D as DragInteractionProps, l as SceneLightsProps, m as ScenarioLightingProps, n as SplatEnvironmentProps, V as VisualScenarioEffectsProps, o as VisualScenarioConfig, p as SplatRendererKind, q as PairedSplatEnvironmentConfig, r as SplatFormat, s as SplatCollisionProxyConfig, t as SplatCollisionPrimitive, u as ScenarioLightingPreset, v as SplatEnvironmentMetadataInput, w as SplatEnvironmentMetadata, x as SplatSceneInput, y as DebugProps, G as GeomInfo, z as ContactListenerProps, T as TrajectoryPlayerProps, E as ActuatorInfo, F as Sites, H as SitePositionResult, J as Sensors, K as SensorHandle, L as SensorInfo, N as Joints, Q as JointStateResult, U as Bodies, W as BodyStateResult, X as Actuators, Y as CtrlHandle, Z as ContactInfo, _ as KeyboardTeleopConfig, $ as PolicyConfig, a0 as PolicyVector, a1 as ObservationHandle, a2 as TrajectoryInput, a3 as TrajectoryStateChangeInput, a4 as PlaybackState, a5 as TrajectoryFrame } from './types-izZlUweI.js';
3
- export { a6 as BodyInfo, a7 as ControlJointInfo, a8 as Geoms, a9 as IKSolveFn, aa as IkGizmoDragInput, ab as IkSolveInput, ac as JointInfo, ad as KeyBinding, ae as Keyframes, af as ModelOptions, ag as MujocoContact, ah as MujocoContactArray, ai as ObservationLayoutItem, aj as ObservationOutput, ak as PhysicsConfig, al as PhysicsStepInput, am as PolicyActionInput, an as PolicyInferenceInput, ao as PolicyObservationInput, ap as RayHit, aq as Register, ar as RegisteredRobotMap, as as ResetCallbackInput, at as ResourceSelector, au as RobotActuators, av as RobotBodies, aw as RobotGeoms, ax as RobotJoints, ay as RobotKeyframes, az as RobotResource, aA as RobotResources, aB as RobotSensors, aC as RobotSites, aD as Robots, aE as ScenarioCameraConfig, aF as ScenarioMaterialConfig, aG as SceneMarker, aH as SceneObject, aI as SensorResult, aJ as SiteInfo, aK as SplatAssetConfig, aL as SplatScenarioConfig, aM as StateSnapshot, aN as TrajectoryData, aO as TrajectoryFrameCallbackInput, aP as VisualScenarioMaterialFilterInput, aQ as XmlPatch, aR as getContact, aS as registerRobotResources } from './types-izZlUweI.js';
2
+ import { M as MujocoContextValue, a as MujocoCanvasProps, b as MujocoSimAPI, S as SceneConfig, R as ReadyCallbackInput, c as StepCallbackInput, d as SelectionCallbackInput, e as MujocoModule, P as PhysicsStepCallback, f as MujocoModel, g as MujocoData, C as ControlGroupInfo, A as ActuatedJointInfo, h as ControlGroupSelector, O as ObservationConfig, i as ObservationResult, I as IkConfig, j as IkContextValue, B as BodyProps, k as IkGizmoProps, D as DragInteractionProps, l as SceneLightsProps, m as ScenarioLightingProps, n as SplatEnvironmentProps, V as VisualScenarioEffectsProps, o as VisualScenarioConfig, p as SplatRendererKind, q as PairedSplatEnvironmentConfig, r as SplatFormat, s as SplatCollisionProxyConfig, t as SplatCollisionPrimitive, u as ScenarioLightingPreset, v as SplatEnvironmentMetadataInput, w as SplatEnvironmentMetadata, x as SplatSceneInput, y as DebugProps, G as GeomInfo, z as ContactListenerProps, T as TrajectoryPlayerProps, E as ActuatorInfo, F as Sites, H as SitePositionResult, J as Sensors, K as SensorHandle, L as SensorInfo, N as Joints, Q as JointStateResult, U as Bodies, W as BodyStateResult, X as Actuators, Y as CtrlHandle, Z as ContactInfo, _ as KeyboardTeleopConfig, $ as PolicyConfig, a0 as PolicyVector, a1 as ObservationHandle, a2 as TrajectoryInput, a3 as TrajectoryStateChangeInput, a4 as PlaybackState, a5 as TrajectoryFrame, a6 as FrameCaptureOptions, a7 as FrameCaptureResult, a8 as FrameCaptureBlobResult, a9 as FrameCaptureAPI, aa as CameraFrameCaptureOptions, ab as CameraFrameCaptureAPI, ac as CameraFrameSequenceRecorderAPI, ad as CameraFrameCaptureResult, ae as CameraFrameCaptureBlobResult } from './types-S8ggQY2n.js';
3
+ export { af as BodyInfo, ag as CameraFrameCaptureQuaternion, ah as CameraFrameCaptureVector3, ai as CameraFrameSequenceCamera, aj as CameraFrameSequenceFrame, ak as CameraFrameSequenceOptions, al as CameraFrameSequenceResult, am as ControlJointInfo, an as FrameCaptureStatus, ao as FrameCaptureTarget, ap as FrameCaptureTargetRef, aq as Geoms, ar as IKSolveFn, as as IkGizmoDragInput, at as IkSolveInput, au as JointInfo, av as KeyBinding, aw as Keyframes, ax as ModelOptions, ay as MujocoContact, az as MujocoContactArray, aA as MujocoFrameCaptureOptions, aB as ObservationLayoutItem, aC as ObservationOutput, aD as PhysicsConfig, aE as PhysicsStepInput, aF as PolicyActionInput, aG as PolicyInferenceInput, aH as PolicyObservationInput, aI as RayHit, aJ as Register, aK as RegisteredRobotMap, aL as ResetCallbackInput, aM as ResourceSelector, aN as RobotActuators, aO as RobotBodies, aP as RobotGeoms, aQ as RobotJoints, aR as RobotKeyframes, aS as RobotResource, aT as RobotResources, aU as RobotSensors, aV as RobotSites, aW as Robots, aX as ScenarioCameraConfig, aY as ScenarioMaterialConfig, aZ as SceneMarker, a_ as SceneObject, a$ as SensorResult, b0 as SiteInfo, b1 as SplatAssetConfig, b2 as SplatScenarioConfig, b3 as StateSnapshot, b4 as TrajectoryData, b5 as TrajectoryFrameCallbackInput, b6 as VisualScenarioMaterialFilterInput, b7 as XmlPatch, b8 as getContact, b9 as registerRobotResources } from './types-S8ggQY2n.js';
4
4
  import * as React$1 from 'react';
5
- import React__default from 'react';
6
5
  import { ThreeElements } from '@react-three/fiber';
7
6
  import * as THREE from 'three';
8
7
 
@@ -243,8 +242,10 @@ type ControllerComponent<TConfig> = React.FC<{
243
242
  * const MyController = createController<{ speed: number }>(
244
243
  * { name: 'my-controller', defaultConfig: { speed: 1.0 } },
245
244
  * function MyControllerImpl({ config }) {
245
+ * const wheel = useCtrl(RobotActuators.mobile.leftWheel);
246
+ *
246
247
  * useBeforePhysicsStep(({ data }) => {
247
- * data.ctrl[0] = config.speed;
248
+ * wheel.write(config.speed);
248
249
  * });
249
250
  * return null;
250
251
  * },
@@ -270,9 +271,11 @@ declare function createController<TConfig>(options: ControllerOptions<TConfig>,
270
271
  * { name: 'useMyController', defaultConfig: { gain: 1.0 } },
271
272
  * function useMyControllerImpl(config) {
272
273
  * // config is MyConfig | null — hooks must be called unconditionally
274
+ * const joint = useCtrl(config?.actuator ?? RobotActuators.franka.actuator1);
275
+ *
273
276
  * useBeforePhysicsStep(({ data }) => {
274
277
  * if (!config) return;
275
- * data.ctrl[0] = config.gain * Math.sin(data.time);
278
+ * joint.write(config.gain * Math.sin(data.time));
276
279
  * });
277
280
  * if (!config) return null;
278
281
  * return { /* value *\/ };
@@ -758,33 +761,6 @@ declare function useVideoRecorder(options?: VideoRecorderOptions): {
758
761
  * useFrameCapture — still-frame capture for canvas-backed MuJoCo/R3F scenes.
759
762
  */
760
763
 
761
- type FrameCaptureStatus = 'idle' | 'capturing' | 'captured' | 'error';
762
- type FrameCaptureTarget = HTMLCanvasElement | HTMLElement | null | undefined;
763
- type FrameCaptureTargetRef = React__default.RefObject<HTMLCanvasElement | HTMLElement | null>;
764
- interface FrameCaptureOptions {
765
- target?: FrameCaptureTarget | FrameCaptureTargetRef;
766
- type?: string;
767
- quality?: number;
768
- waitForAnimationFrame?: boolean;
769
- }
770
- interface FrameCaptureResult {
771
- canvas: HTMLCanvasElement;
772
- dataUrl: string;
773
- type: string;
774
- }
775
- interface FrameCaptureBlobResult {
776
- canvas: HTMLCanvasElement;
777
- blob: Blob;
778
- type: string;
779
- }
780
- interface FrameCaptureAPI {
781
- status: FrameCaptureStatus;
782
- error: Error | null;
783
- isCapturing: boolean;
784
- capture: (options?: FrameCaptureOptions) => Promise<FrameCaptureResult>;
785
- captureBlob: (options?: FrameCaptureOptions) => Promise<FrameCaptureBlobResult>;
786
- reset: () => void;
787
- }
788
764
  /**
789
765
  * Capture the current canvas frame as a data URL.
790
766
  *
@@ -801,6 +777,40 @@ declare function captureFrameBlob(options: FrameCaptureOptions): Promise<FrameCa
801
777
  */
802
778
  declare function useFrameCapture(defaultOptions?: FrameCaptureOptions): FrameCaptureAPI;
803
779
 
780
+ /**
781
+ * @license
782
+ * SPDX-License-Identifier: Apache-2.0
783
+ *
784
+ * React state wrapper around MuJoCo/R3F offscreen camera-frame capture.
785
+ */
786
+
787
+ declare function useCameraFrameCapture(defaultOptions?: CameraFrameCaptureOptions): CameraFrameCaptureAPI;
788
+
789
+ /**
790
+ * @license
791
+ * SPDX-License-Identifier: Apache-2.0
792
+ *
793
+ * React state wrapper around fixed-camera simulation sequence recording.
794
+ */
795
+
796
+ declare function useCameraSequenceRecorder(): CameraFrameSequenceRecorderAPI;
797
+
798
+ /**
799
+ * @license
800
+ * SPDX-License-Identifier: Apache-2.0
801
+ *
802
+ * Offscreen camera-frame capture for R3F/MuJoCo scenes.
803
+ */
804
+
805
+ declare function renderCameraFrameToCanvas(renderer: THREE.WebGLRenderer, scene: THREE.Scene, fallbackCamera: THREE.Camera, options?: CameraFrameCaptureOptions): {
806
+ canvas: HTMLCanvasElement;
807
+ camera: THREE.Camera;
808
+ width: number;
809
+ height: number;
810
+ };
811
+ declare function captureCameraFrame(renderer: THREE.WebGLRenderer, scene: THREE.Scene, fallbackCamera: THREE.Camera, options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureResult>;
812
+ declare function captureCameraFrameBlob(renderer: THREE.WebGLRenderer, scene: THREE.Scene, fallbackCamera: THREE.Camera, options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureBlobResult>;
813
+
804
814
  /**
805
815
  * @license
806
816
  * SPDX-License-Identifier: Apache-2.0
@@ -896,4 +906,4 @@ interface CameraAnimationAPI {
896
906
  */
897
907
  declare function useCameraAnimation(): CameraAnimationAPI;
898
908
 
899
- export { ActuatedJointInfo, ActuatorInfo, Actuators, Bodies, Body, BodyProps, BodyStateResult, type CameraAnimationAPI, ContactInfo, ContactListener, ContactListenerProps, ContactMarkers, ControlGroupInfo, ControlGroupSelector, type ControllerComponent, type ControllerOptions, CtrlHandle, Debug, DebugProps, DragInteraction, DragInteractionProps, FlexRenderer, type FrameCaptureAPI, type FrameCaptureBlobResult, type FrameCaptureOptions, type FrameCaptureResult, type FrameCaptureStatus, type FrameCaptureTarget, type FrameCaptureTargetRef, GeomInfo, IkConfig, IkContextValue, IkGizmo, IkGizmoProps, InstancedGeomRenderer, JointStateResult, Joints, KeyboardTeleopConfig, MujocoCanvas, MujocoCanvasProps, MujocoContextValue, MujocoData, type MujocoLoader, type MujocoLoaderOptions, MujocoModel, MujocoModule, MujocoPhysics, type MujocoPhysicsProps, MujocoProvider, type MujocoProviderProps, MujocoSimAPI, MujocoSimProvider, type MujocoWasmVariant, ObservationConfig, ObservationHandle, ObservationResult, PairedSplatEnvironmentConfig, PhysicsStepCallback, PlaybackState, PolicyConfig, PolicyVector, ReadyCallbackInput, ScenarioLighting, ScenarioLightingPreset, ScenarioLightingProps, SceneConfig, SceneLights, SceneLightsProps, SelectionCallbackInput, SensorHandle, SensorInfo, Sensors, SitePositionResult, Sites, SplatCollisionPrimitive, SplatCollisionProxyConfig, SplatEnvironment, SplatEnvironmentMetadata, SplatEnvironmentMetadataInput, SplatEnvironmentProps, SplatFormat, SplatRendererKind, SplatSceneInput, StepCallbackInput, TendonRenderer, TrajectoryFrame, TrajectoryInput, TrajectoryPlayer, TrajectoryPlayerProps, TrajectoryStateChangeInput, VisualScenarioConfig, VisualScenarioEffects, VisualScenarioEffectsProps, buildObservation, captureFrame, captureFrameBlob, createContiguousControlGroup, createController, createControllerHook, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getControlMap, getName, getScenarioBackground, getScenarioCameraPosition, loadScene, resolveControlGroup, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useContactEvents, useContacts, useCtrl, useCtrlNoise, useFrameCapture, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useSplatEnvironment, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder, useVisualScenarioEffects, withSplatEnvironment };
909
+ export { ActuatedJointInfo, ActuatorInfo, Actuators, Bodies, Body, BodyProps, BodyStateResult, type CameraAnimationAPI, CameraFrameCaptureAPI, CameraFrameCaptureBlobResult, CameraFrameCaptureOptions, CameraFrameCaptureResult, CameraFrameSequenceRecorderAPI, ContactInfo, ContactListener, ContactListenerProps, ContactMarkers, ControlGroupInfo, ControlGroupSelector, type ControllerComponent, type ControllerOptions, CtrlHandle, Debug, DebugProps, DragInteraction, DragInteractionProps, FlexRenderer, FrameCaptureAPI, FrameCaptureBlobResult, FrameCaptureOptions, FrameCaptureResult, GeomInfo, IkConfig, IkContextValue, IkGizmo, IkGizmoProps, InstancedGeomRenderer, JointStateResult, Joints, KeyboardTeleopConfig, MujocoCanvas, MujocoCanvasProps, MujocoContextValue, MujocoData, type MujocoLoader, type MujocoLoaderOptions, MujocoModel, MujocoModule, MujocoPhysics, type MujocoPhysicsProps, MujocoProvider, type MujocoProviderProps, MujocoSimAPI, MujocoSimProvider, type MujocoWasmVariant, ObservationConfig, ObservationHandle, ObservationResult, PairedSplatEnvironmentConfig, PhysicsStepCallback, PlaybackState, PolicyConfig, PolicyVector, ReadyCallbackInput, ScenarioLighting, ScenarioLightingPreset, ScenarioLightingProps, SceneConfig, SceneLights, SceneLightsProps, SelectionCallbackInput, SensorHandle, SensorInfo, Sensors, SitePositionResult, Sites, SplatCollisionPrimitive, SplatCollisionProxyConfig, SplatEnvironment, SplatEnvironmentMetadata, SplatEnvironmentMetadataInput, SplatEnvironmentProps, SplatFormat, SplatRendererKind, SplatSceneInput, StepCallbackInput, TendonRenderer, TrajectoryFrame, TrajectoryInput, TrajectoryPlayer, TrajectoryPlayerProps, TrajectoryStateChangeInput, VisualScenarioConfig, VisualScenarioEffects, VisualScenarioEffectsProps, buildObservation, captureCameraFrame, captureCameraFrameBlob, captureFrame, captureFrameBlob, createContiguousControlGroup, createController, createControllerHook, createPairedSplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, findActuatorByName, findBodyByName, findGeomByName, findJointByName, findKeyframeByName, findSensorByName, findSiteByName, findTendonByName, getActuatedJoints, getControlMap, getName, getScenarioBackground, getScenarioCameraPosition, loadScene, renderCameraFrameToCanvas, resolveControlGroup, useActuators, useAfterPhysicsStep, useBeforePhysicsStep, useBodyMeshes, useBodyState, useCameraAnimation, useCameraFrameCapture, useCameraSequenceRecorder, useContactEvents, useContacts, useCtrl, useCtrlNoise, useFrameCapture, useGamepad, useGravityCompensation, useIkController, useJointState, useKeyboardTeleop, useMujoco, useMujocoWasm, useObservation, usePolicy, useSceneLights, useSelectionHighlight, useSensor, useSensors, useSitePosition, useSplatEnvironment, useTrajectoryPlayer, useTrajectoryRecorder, useVideoRecorder, useVisualScenarioEffects, withSplatEnvironment };