mujoco-react 9.2.0 → 9.3.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 +96 -10
- package/dist/{chunk-33CV6HSV.js → chunk-T3GVZJ4F.js} +222 -8
- package/dist/chunk-T3GVZJ4F.js.map +1 -0
- package/dist/index.d.ts +158 -4
- package/dist/index.js +692 -130
- package/dist/index.js.map +1 -1
- package/dist/spark.d.ts +24 -2
- package/dist/spark.js +89 -3
- package/dist/spark.js.map +1 -1
- package/dist/{types-S8ggQY2n.d.ts → types-oxbxOkAx.d.ts} +120 -3
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +6 -3
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
- package/src/components/VisualScenario.tsx +178 -1
- package/src/core/MujocoSimProvider.tsx +373 -29
- package/src/core/SceneLoader.ts +13 -0
- package/src/hooks/useMountedCameraSequenceRecorder.ts +107 -0
- package/src/index.ts +49 -0
- package/src/rendering/cameraFrameCapture.ts +195 -26
- package/src/rendering/cameraFrameSource.ts +375 -0
- package/src/spark.tsx +144 -0
- package/src/types.ts +122 -2
- package/src/vite.ts +5 -2
- package/dist/chunk-33CV6HSV.js.map +0 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*
|
|
5
|
+
* React state wrapper for named MuJoCo camera/site/body sequence recording.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useCallback, useState } from 'react';
|
|
9
|
+
import { useMujoco } from '../core/MujocoSimProvider';
|
|
10
|
+
import {
|
|
11
|
+
createMountedCameraFrameSequencePlanFromApi,
|
|
12
|
+
recordMountedCameraFrameSequence,
|
|
13
|
+
type MountedCameraFrameSequencePlan,
|
|
14
|
+
type MountedCameraFrameSequencePlanOptions,
|
|
15
|
+
type MountedCameraFrameSequenceRecordOptions,
|
|
16
|
+
type MountedCameraFrameSequenceRecordResult,
|
|
17
|
+
} from '../rendering/cameraFrameSource';
|
|
18
|
+
import type {
|
|
19
|
+
CameraFrameSequenceRecorderAPI,
|
|
20
|
+
FrameCaptureStatus,
|
|
21
|
+
} from '../types';
|
|
22
|
+
|
|
23
|
+
export type MountedCameraSequencePlanOptions =
|
|
24
|
+
MountedCameraFrameSequencePlanOptions;
|
|
25
|
+
export type MountedCameraSequenceRecordOptions =
|
|
26
|
+
MountedCameraFrameSequenceRecordOptions;
|
|
27
|
+
export type MountedCameraSequenceRecordResult =
|
|
28
|
+
MountedCameraFrameSequenceRecordResult;
|
|
29
|
+
|
|
30
|
+
export interface MountedCameraSequenceRecorderAPI
|
|
31
|
+
extends Omit<CameraFrameSequenceRecorderAPI, 'record'> {
|
|
32
|
+
createPlan: (
|
|
33
|
+
cameraKeys: readonly string[],
|
|
34
|
+
options?: MountedCameraSequencePlanOptions
|
|
35
|
+
) => MountedCameraFrameSequencePlan;
|
|
36
|
+
record: (
|
|
37
|
+
options: MountedCameraSequenceRecordOptions
|
|
38
|
+
) => Promise<MountedCameraSequenceRecordResult>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function useMountedCameraSequenceRecorder(
|
|
42
|
+
defaultOptions: MountedCameraSequencePlanOptions = {}
|
|
43
|
+
): MountedCameraSequenceRecorderAPI {
|
|
44
|
+
const mujoco = useMujoco();
|
|
45
|
+
const [status, setStatus] = useState<FrameCaptureStatus>('idle');
|
|
46
|
+
const [error, setError] = useState<Error | null>(null);
|
|
47
|
+
|
|
48
|
+
const reset = useCallback(() => {
|
|
49
|
+
setStatus('idle');
|
|
50
|
+
setError(null);
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
const createPlan = useCallback(
|
|
54
|
+
(
|
|
55
|
+
cameraKeys: readonly string[],
|
|
56
|
+
options: MountedCameraSequencePlanOptions = {}
|
|
57
|
+
) => {
|
|
58
|
+
if (!mujoco.api) {
|
|
59
|
+
throw new Error('MuJoCo scene is not ready for mounted camera sequence planning.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return createMountedCameraFrameSequencePlanFromApi(mujoco.api, cameraKeys, {
|
|
63
|
+
...defaultOptions,
|
|
64
|
+
...options,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
[defaultOptions, mujoco.api]
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const record = useCallback(
|
|
71
|
+
async (options: MountedCameraSequenceRecordOptions) => {
|
|
72
|
+
if (!mujoco.api) {
|
|
73
|
+
throw new Error('MuJoCo scene is not ready for mounted camera sequence recording.');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setStatus('capturing');
|
|
77
|
+
setError(null);
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const result = await recordMountedCameraFrameSequence(mujoco.api, {
|
|
81
|
+
...defaultOptions,
|
|
82
|
+
...options,
|
|
83
|
+
});
|
|
84
|
+
setStatus('captured');
|
|
85
|
+
return result;
|
|
86
|
+
} catch (nextError) {
|
|
87
|
+
const error =
|
|
88
|
+
nextError instanceof Error
|
|
89
|
+
? nextError
|
|
90
|
+
: new Error('Unable to record the requested mounted camera sequence.');
|
|
91
|
+
setError(error);
|
|
92
|
+
setStatus('error');
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
[defaultOptions, mujoco.api]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
status,
|
|
101
|
+
error,
|
|
102
|
+
isRecording: status === 'capturing',
|
|
103
|
+
createPlan,
|
|
104
|
+
record,
|
|
105
|
+
reset,
|
|
106
|
+
};
|
|
107
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -48,9 +48,11 @@ export {
|
|
|
48
48
|
createPairedSplatEnvironment,
|
|
49
49
|
createSparkSplatViewerUrl,
|
|
50
50
|
createSplatEnvironmentUserData,
|
|
51
|
+
getSplatEnvironmentReadiness,
|
|
51
52
|
getScenarioBackground,
|
|
52
53
|
getScenarioCameraPosition,
|
|
53
54
|
useSplatEnvironment,
|
|
55
|
+
useSplatSceneConfig,
|
|
54
56
|
useVisualScenarioEffects,
|
|
55
57
|
withSplatEnvironment,
|
|
56
58
|
} from './components/VisualScenario';
|
|
@@ -84,11 +86,47 @@ export {
|
|
|
84
86
|
} from './hooks/useFrameCapture';
|
|
85
87
|
export { useCameraFrameCapture } from './hooks/useCameraFrameCapture';
|
|
86
88
|
export { useCameraSequenceRecorder } from './hooks/useCameraSequenceRecorder';
|
|
89
|
+
export { useMountedCameraSequenceRecorder } from './hooks/useMountedCameraSequenceRecorder';
|
|
90
|
+
export type {
|
|
91
|
+
MountedCameraSequencePlanOptions,
|
|
92
|
+
MountedCameraSequenceRecorderAPI,
|
|
93
|
+
MountedCameraSequenceRecordOptions,
|
|
94
|
+
MountedCameraSequenceRecordResult,
|
|
95
|
+
} from './hooks/useMountedCameraSequenceRecorder';
|
|
87
96
|
export {
|
|
88
97
|
captureCameraFrame,
|
|
89
98
|
captureCameraFrameBlob,
|
|
99
|
+
createCameraFrameCaptureSession,
|
|
90
100
|
renderCameraFrameToCanvas,
|
|
91
101
|
} from './rendering/cameraFrameCapture';
|
|
102
|
+
export {
|
|
103
|
+
createMountedCameraFrameSequenceReadiness,
|
|
104
|
+
MountedCameraFrameSequenceReadinessStatus,
|
|
105
|
+
createMountedCameraFrameSequencePlanFromApi,
|
|
106
|
+
createMountedCameraFrameSequencePlan,
|
|
107
|
+
getCameraFrameCaptureSourceTarget,
|
|
108
|
+
getMountedCameraFrameCaptureSource,
|
|
109
|
+
isMountedCameraFrameCaptureSource,
|
|
110
|
+
recordMountedCameraFrameSequence,
|
|
111
|
+
resolveMountedCameraFrameSource,
|
|
112
|
+
} from './rendering/cameraFrameSource';
|
|
113
|
+
export type {
|
|
114
|
+
CameraFrameMountSelector,
|
|
115
|
+
CreateMountedCameraFrameSequencePlanOptions,
|
|
116
|
+
MountedCameraFrameCaptureSource,
|
|
117
|
+
MountedCameraFrameSequencePlanOptions,
|
|
118
|
+
MountedCameraFrameSequenceRecorderTarget,
|
|
119
|
+
MountedCameraFrameSequenceCameraOptions,
|
|
120
|
+
MountedCameraFrameSequenceDefaults,
|
|
121
|
+
MountedCameraFrameSequencePlan,
|
|
122
|
+
MountedCameraFrameSequenceReadiness,
|
|
123
|
+
MountedCameraFrameSequenceRecordOptions,
|
|
124
|
+
MountedCameraFrameSequenceRecordResult,
|
|
125
|
+
MountedCameraFrameSequenceSourceReadiness,
|
|
126
|
+
NamedCameraFrameResource,
|
|
127
|
+
ResolveMountedCameraFrameSourceOptions,
|
|
128
|
+
ResolvedMountedCameraFrameSource,
|
|
129
|
+
} from './rendering/cameraFrameSource';
|
|
92
130
|
export { useCtrlNoise } from './hooks/useCtrlNoise';
|
|
93
131
|
export { useBodyMeshes } from './hooks/useBodyMeshes';
|
|
94
132
|
export { useSelectionHighlight } from './hooks/useSelectionHighlight';
|
|
@@ -130,6 +168,7 @@ export type {
|
|
|
130
168
|
SiteInfo,
|
|
131
169
|
ActuatorInfo,
|
|
132
170
|
SensorInfo,
|
|
171
|
+
CameraInfo,
|
|
133
172
|
// Contacts
|
|
134
173
|
ContactInfo,
|
|
135
174
|
// Raycast
|
|
@@ -172,8 +211,11 @@ export type {
|
|
|
172
211
|
SplatScenarioConfig,
|
|
173
212
|
SplatCollisionProxyConfig,
|
|
174
213
|
PairedSplatEnvironmentConfig,
|
|
214
|
+
SplatEnvironmentReadiness,
|
|
175
215
|
SplatEnvironmentMetadataInput,
|
|
176
216
|
SplatEnvironmentMetadata,
|
|
217
|
+
SplatSceneConfigInput,
|
|
218
|
+
SplatSceneConfigState,
|
|
177
219
|
SplatSceneInput,
|
|
178
220
|
VisualScenarioConfig,
|
|
179
221
|
ScenarioLightingProps,
|
|
@@ -193,12 +235,16 @@ export type {
|
|
|
193
235
|
CameraFrameCaptureOptions,
|
|
194
236
|
CameraFrameCaptureQuaternion,
|
|
195
237
|
CameraFrameCaptureResult,
|
|
238
|
+
CameraFrameCaptureSource,
|
|
196
239
|
CameraFrameCaptureVector3,
|
|
197
240
|
CameraFrameSequenceCamera,
|
|
241
|
+
CameraFrameSequenceCameraSummary,
|
|
198
242
|
CameraFrameSequenceFrame,
|
|
199
243
|
CameraFrameSequenceOptions,
|
|
200
244
|
CameraFrameSequenceRecorderAPI,
|
|
201
245
|
CameraFrameSequenceResult,
|
|
246
|
+
CameraFrameSequenceSampleInput,
|
|
247
|
+
CameraFrameSequenceStepInput,
|
|
202
248
|
MujocoCanvasProps,
|
|
203
249
|
MujocoContextValue,
|
|
204
250
|
// Hook return types
|
|
@@ -227,6 +273,7 @@ export type {
|
|
|
227
273
|
Sites,
|
|
228
274
|
Geoms,
|
|
229
275
|
Keyframes,
|
|
276
|
+
Cameras,
|
|
230
277
|
} from './types';
|
|
231
278
|
|
|
232
279
|
export {
|
|
@@ -239,6 +286,8 @@ export {
|
|
|
239
286
|
RobotSites,
|
|
240
287
|
RobotGeoms,
|
|
241
288
|
RobotKeyframes,
|
|
289
|
+
RobotCameras,
|
|
290
|
+
SplatEnvironmentReadinessStatus,
|
|
242
291
|
} from './types';
|
|
243
292
|
|
|
244
293
|
// Re-export MuJoCo types for convenience
|
|
@@ -10,9 +10,25 @@ import type {
|
|
|
10
10
|
CameraFrameCaptureBlobResult,
|
|
11
11
|
CameraFrameCaptureOptions,
|
|
12
12
|
CameraFrameCaptureResult,
|
|
13
|
+
CameraFrameCaptureSource,
|
|
13
14
|
CameraFrameCaptureVector3,
|
|
14
15
|
} from '../types';
|
|
15
16
|
|
|
17
|
+
export interface CameraFrameCaptureSession {
|
|
18
|
+
readonly width: number;
|
|
19
|
+
readonly height: number;
|
|
20
|
+
capture(options?: CameraFrameCaptureOptions): {
|
|
21
|
+
canvas: HTMLCanvasElement;
|
|
22
|
+
camera: THREE.Camera;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
source: CameraFrameCaptureSource;
|
|
26
|
+
};
|
|
27
|
+
captureDataUrl(options?: CameraFrameCaptureOptions): CameraFrameCaptureResult;
|
|
28
|
+
captureBlob(options?: CameraFrameCaptureOptions): Promise<CameraFrameCaptureBlobResult>;
|
|
29
|
+
dispose(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
16
32
|
function toVector3(
|
|
17
33
|
value: CameraFrameCaptureVector3 | undefined,
|
|
18
34
|
fallback: THREE.Vector3
|
|
@@ -75,24 +91,55 @@ function createCaptureCamera(
|
|
|
75
91
|
return camera;
|
|
76
92
|
}
|
|
77
93
|
|
|
94
|
+
function getCaptureDimensions(
|
|
95
|
+
renderer: THREE.WebGLRenderer,
|
|
96
|
+
options: CameraFrameCaptureOptions
|
|
97
|
+
) {
|
|
98
|
+
const width = Math.max(
|
|
99
|
+
1,
|
|
100
|
+
Math.floor(options.width ?? renderer.domElement.width)
|
|
101
|
+
);
|
|
102
|
+
const height = Math.max(
|
|
103
|
+
1,
|
|
104
|
+
Math.floor(options.height ?? renderer.domElement.height)
|
|
105
|
+
);
|
|
106
|
+
return { width, height };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function prepareCaptureCamera(
|
|
110
|
+
camera: THREE.Camera,
|
|
111
|
+
options: CameraFrameCaptureOptions,
|
|
112
|
+
fallbackCamera: THREE.Camera,
|
|
113
|
+
width: number,
|
|
114
|
+
height: number
|
|
115
|
+
) {
|
|
116
|
+
if (options.camera) {
|
|
117
|
+
camera.copy(options.camera);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (camera instanceof THREE.PerspectiveCamera) {
|
|
121
|
+
camera.aspect = width / height;
|
|
122
|
+
camera.fov = options.fov ?? camera.fov;
|
|
123
|
+
camera.near = options.near ?? camera.near;
|
|
124
|
+
camera.far = options.far ?? camera.far;
|
|
125
|
+
camera.updateProjectionMatrix();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
applyCameraPose(camera, options, fallbackCamera);
|
|
129
|
+
}
|
|
130
|
+
|
|
78
131
|
function readRenderTargetToCanvas(
|
|
79
132
|
renderer: THREE.WebGLRenderer,
|
|
80
133
|
target: THREE.WebGLRenderTarget,
|
|
134
|
+
canvas: HTMLCanvasElement,
|
|
135
|
+
context: CanvasRenderingContext2D,
|
|
136
|
+
pixels: Uint8Array,
|
|
137
|
+
imageData: ImageData,
|
|
81
138
|
width: number,
|
|
82
139
|
height: number
|
|
83
140
|
) {
|
|
84
|
-
const pixels = new Uint8Array(width * height * 4);
|
|
85
141
|
renderer.readRenderTargetPixels(target, 0, 0, width, height, pixels);
|
|
86
142
|
|
|
87
|
-
const canvas = document.createElement('canvas');
|
|
88
|
-
canvas.width = width;
|
|
89
|
-
canvas.height = height;
|
|
90
|
-
const context = canvas.getContext('2d');
|
|
91
|
-
if (!context) {
|
|
92
|
-
throw new Error('Unable to create a 2D canvas for camera frame capture.');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const imageData = context.createImageData(width, height);
|
|
96
143
|
const rowBytes = width * 4;
|
|
97
144
|
for (let y = 0; y < height; y += 1) {
|
|
98
145
|
const sourceStart = (height - y - 1) * rowBytes;
|
|
@@ -106,34 +153,156 @@ function readRenderTargetToCanvas(
|
|
|
106
153
|
return canvas;
|
|
107
154
|
}
|
|
108
155
|
|
|
109
|
-
|
|
156
|
+
function getCameraFrameCaptureSource(
|
|
157
|
+
options: CameraFrameCaptureOptions
|
|
158
|
+
): CameraFrameCaptureSource {
|
|
159
|
+
if (options.source) return options.source;
|
|
160
|
+
if (options.cameraName) {
|
|
161
|
+
return { kind: 'mujoco-camera', cameraName: options.cameraName };
|
|
162
|
+
}
|
|
163
|
+
if (options.siteName) {
|
|
164
|
+
return { kind: 'mujoco-site', siteName: options.siteName };
|
|
165
|
+
}
|
|
166
|
+
if (options.bodyName) {
|
|
167
|
+
return { kind: 'mujoco-body', bodyName: options.bodyName };
|
|
168
|
+
}
|
|
169
|
+
if (options.camera) return { kind: 'custom-camera' };
|
|
170
|
+
if (options.position || options.lookAt || options.quaternion) {
|
|
171
|
+
return { kind: 'explicit-pose' };
|
|
172
|
+
}
|
|
173
|
+
return { kind: 'fallback-camera' };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function createCameraFrameCaptureSession(
|
|
110
177
|
renderer: THREE.WebGLRenderer,
|
|
111
178
|
scene: THREE.Scene,
|
|
112
179
|
fallbackCamera: THREE.Camera,
|
|
113
180
|
options: CameraFrameCaptureOptions = {}
|
|
114
|
-
) {
|
|
115
|
-
const width =
|
|
116
|
-
const height = Math.max(1, Math.floor(options.height ?? renderer.domElement.height));
|
|
181
|
+
): CameraFrameCaptureSession {
|
|
182
|
+
const { width, height } = getCaptureDimensions(renderer, options);
|
|
117
183
|
const camera = createCaptureCamera(options, fallbackCamera, width, height);
|
|
118
184
|
const target = new THREE.WebGLRenderTarget(width, height, {
|
|
119
185
|
format: THREE.RGBAFormat,
|
|
120
186
|
type: THREE.UnsignedByteType,
|
|
121
187
|
});
|
|
122
|
-
const
|
|
123
|
-
|
|
188
|
+
const canvas = document.createElement('canvas');
|
|
189
|
+
canvas.width = width;
|
|
190
|
+
canvas.height = height;
|
|
191
|
+
const context = canvas.getContext('2d');
|
|
192
|
+
if (!context) {
|
|
193
|
+
target.dispose();
|
|
194
|
+
throw new Error('Unable to create a 2D canvas for camera frame capture.');
|
|
195
|
+
}
|
|
196
|
+
const drawContext = context;
|
|
197
|
+
|
|
198
|
+
const pixels = new Uint8Array(width * height * 4);
|
|
199
|
+
const imageData = drawContext.createImageData(width, height);
|
|
200
|
+
|
|
201
|
+
function capture(nextOptions: CameraFrameCaptureOptions = {}) {
|
|
202
|
+
const captureOptions = { ...options, ...nextOptions };
|
|
203
|
+
const nextDimensions = getCaptureDimensions(renderer, captureOptions);
|
|
204
|
+
if (
|
|
205
|
+
nextDimensions.width !== width ||
|
|
206
|
+
nextDimensions.height !== height
|
|
207
|
+
) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
'Camera frame capture sessions require stable width and height.'
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
prepareCaptureCamera(
|
|
214
|
+
camera,
|
|
215
|
+
captureOptions,
|
|
216
|
+
fallbackCamera,
|
|
217
|
+
width,
|
|
218
|
+
height
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const previousTarget = renderer.getRenderTarget();
|
|
222
|
+
const previousXrEnabled = renderer.xr.enabled;
|
|
124
223
|
|
|
125
|
-
|
|
224
|
+
scene.updateMatrixWorld(true);
|
|
225
|
+
try {
|
|
226
|
+
renderer.xr.enabled = false;
|
|
227
|
+
renderer.setRenderTarget(target);
|
|
228
|
+
renderer.clear();
|
|
229
|
+
renderer.render(scene, camera);
|
|
230
|
+
readRenderTargetToCanvas(
|
|
231
|
+
renderer,
|
|
232
|
+
target,
|
|
233
|
+
canvas,
|
|
234
|
+
drawContext,
|
|
235
|
+
pixels,
|
|
236
|
+
imageData,
|
|
237
|
+
width,
|
|
238
|
+
height
|
|
239
|
+
);
|
|
240
|
+
return {
|
|
241
|
+
canvas,
|
|
242
|
+
camera,
|
|
243
|
+
width,
|
|
244
|
+
height,
|
|
245
|
+
source: getCameraFrameCaptureSource(captureOptions),
|
|
246
|
+
};
|
|
247
|
+
} finally {
|
|
248
|
+
renderer.setRenderTarget(previousTarget);
|
|
249
|
+
renderer.xr.enabled = previousXrEnabled;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
width,
|
|
255
|
+
height,
|
|
256
|
+
capture,
|
|
257
|
+
captureDataUrl(nextOptions = {}) {
|
|
258
|
+
const type = nextOptions.type ?? options.type ?? 'image/png';
|
|
259
|
+
const result = capture(nextOptions);
|
|
260
|
+
return {
|
|
261
|
+
...result,
|
|
262
|
+
dataUrl: result.canvas.toDataURL(
|
|
263
|
+
type,
|
|
264
|
+
nextOptions.quality ?? options.quality
|
|
265
|
+
),
|
|
266
|
+
type,
|
|
267
|
+
};
|
|
268
|
+
},
|
|
269
|
+
async captureBlob(nextOptions = {}) {
|
|
270
|
+
const type = nextOptions.type ?? options.type ?? 'image/png';
|
|
271
|
+
const result = capture(nextOptions);
|
|
272
|
+
const blob = await new Promise<Blob>((resolve, reject) => {
|
|
273
|
+
result.canvas.toBlob(
|
|
274
|
+
(nextBlob) => {
|
|
275
|
+
if (nextBlob) resolve(nextBlob);
|
|
276
|
+
else reject(new Error('Camera frame capture did not produce a Blob.'));
|
|
277
|
+
},
|
|
278
|
+
type,
|
|
279
|
+
nextOptions.quality ?? options.quality
|
|
280
|
+
);
|
|
281
|
+
});
|
|
282
|
+
return { ...result, blob, type };
|
|
283
|
+
},
|
|
284
|
+
dispose() {
|
|
285
|
+
target.dispose();
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function renderCameraFrameToCanvas(
|
|
291
|
+
renderer: THREE.WebGLRenderer,
|
|
292
|
+
scene: THREE.Scene,
|
|
293
|
+
fallbackCamera: THREE.Camera,
|
|
294
|
+
options: CameraFrameCaptureOptions = {}
|
|
295
|
+
) {
|
|
296
|
+
const session = createCameraFrameCaptureSession(
|
|
297
|
+
renderer,
|
|
298
|
+
scene,
|
|
299
|
+
fallbackCamera,
|
|
300
|
+
options
|
|
301
|
+
);
|
|
126
302
|
try {
|
|
127
|
-
|
|
128
|
-
renderer.setRenderTarget(target);
|
|
129
|
-
renderer.clear();
|
|
130
|
-
renderer.render(scene, camera);
|
|
131
|
-
const canvas = readRenderTargetToCanvas(renderer, target, width, height);
|
|
132
|
-
return { canvas, camera, width, height };
|
|
303
|
+
return session.capture();
|
|
133
304
|
} finally {
|
|
134
|
-
|
|
135
|
-
renderer.xr.enabled = previousXrEnabled;
|
|
136
|
-
target.dispose();
|
|
305
|
+
session.dispose();
|
|
137
306
|
}
|
|
138
307
|
}
|
|
139
308
|
|