mujoco-react 8.9.2 → 8.10.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
@@ -141,6 +141,50 @@ Use it as a child of `<MujocoCanvas>`:
141
141
  </MujocoCanvas>
142
142
  ```
143
143
 
144
+ ## Gaussian Splat Environments
145
+
146
+ Gaussian splats are visual context; MuJoCo XML remains the source of physics, contacts, and task fixtures. Pair each splat asset with collision proxy metadata so scene variants, rollouts, and datasets preserve both sides of the environment.
147
+
148
+ Use the renderer-agnostic boundary from the main package:
149
+
150
+ ```tsx
151
+ import { SplatEnvironment } from "mujoco-react";
152
+
153
+ <SplatEnvironment
154
+ src="/models/lab/scene.spz"
155
+ format="spz"
156
+ collisionProxyMetadata={{
157
+ xmlPath: "/models/lab/collision.xml",
158
+ status: "validated",
159
+ primitives: ["plane", "box"],
160
+ }}
161
+ />;
162
+ ```
163
+
164
+ For first-class Spark rendering, install Spark and import the optional adapter:
165
+
166
+ ```bash
167
+ npm install @sparkjsdev/spark
168
+ ```
169
+
170
+ ```tsx
171
+ import { SparkSplatEnvironment } from "mujoco-react/spark";
172
+
173
+ <MujocoCanvas config={sceneConfig} gl={{ preserveDrawingBuffer: true }}>
174
+ <SparkSplatEnvironment
175
+ src="/models/lab/scene.spz"
176
+ format="spz"
177
+ collisionProxyMetadata={{
178
+ xmlPath: "/models/lab/collision.xml",
179
+ status: "validated",
180
+ primitives: ["plane", "box"],
181
+ }}
182
+ hideGroundMeshes
183
+ onStatusChange={(status) => console.log(status)}
184
+ />
185
+ </MujocoCanvas>;
186
+ ```
187
+
144
188
  ## Write Controllers
145
189
 
146
190
  ```tsx
@@ -0,0 +1,186 @@
1
+ import { useMemo } from 'react';
2
+ import * as THREE from 'three';
3
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
+
5
+ // src/components/VisualScenario.tsx
6
+ var DEFAULT_BACKGROUND = "#181a1f";
7
+ function ScenarioLighting({
8
+ preset = "studio",
9
+ castShadow = true,
10
+ intensity = 1
11
+ }) {
12
+ if (preset === "warehouse") {
13
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
14
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.18 * intensity }),
15
+ /* @__PURE__ */ jsx(
16
+ "directionalLight",
17
+ {
18
+ position: [3.5, -2, 5],
19
+ intensity: 2.2 * intensity,
20
+ castShadow
21
+ }
22
+ ),
23
+ /* @__PURE__ */ jsx("directionalLight", { position: [-2, 1.5, 2.5], intensity: 0.25 * intensity })
24
+ ] });
25
+ }
26
+ if (preset === "low-light") {
27
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
28
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.08 * intensity }),
29
+ /* @__PURE__ */ jsx(
30
+ "directionalLight",
31
+ {
32
+ position: [2, -2, 3],
33
+ intensity: 0.75 * intensity,
34
+ castShadow
35
+ }
36
+ ),
37
+ /* @__PURE__ */ jsx("pointLight", { position: [-0.5, -0.8, 1.3], intensity: 0.6 * intensity })
38
+ ] });
39
+ }
40
+ if (preset === "splat") {
41
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
42
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.42 * intensity }),
43
+ /* @__PURE__ */ jsx(
44
+ "directionalLight",
45
+ {
46
+ position: [1.8, -2.4, 3.5],
47
+ intensity: 1.2 * intensity,
48
+ castShadow
49
+ }
50
+ ),
51
+ /* @__PURE__ */ jsx("pointLight", { position: [0.4, 0.2, 1.4], intensity: 0.35 * intensity })
52
+ ] });
53
+ }
54
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
55
+ /* @__PURE__ */ jsx("ambientLight", { intensity: 0.35 * intensity }),
56
+ /* @__PURE__ */ jsx(
57
+ "directionalLight",
58
+ {
59
+ position: [2.5, -3, 4],
60
+ intensity: 1.6 * intensity,
61
+ castShadow
62
+ }
63
+ )
64
+ ] });
65
+ }
66
+ function getScenarioBackground(preset, fallback = DEFAULT_BACKGROUND) {
67
+ if (preset === "warehouse") return "#20242b";
68
+ if (preset === "low-light") return "#0f1115";
69
+ if (preset === "splat") return "#1b1f24";
70
+ return fallback;
71
+ }
72
+ function getScenarioCameraPosition(basePosition, scenario) {
73
+ const [x, y, z] = basePosition;
74
+ const jitter = scenario?.camera?.jitter ?? 0;
75
+ return [
76
+ Number((x + jitter * 0.6).toFixed(3)),
77
+ Number((y - jitter * 0.4).toFixed(3)),
78
+ Number((z + jitter * 0.25).toFixed(3))
79
+ ];
80
+ }
81
+ function SplatEnvironment({
82
+ environment,
83
+ src,
84
+ format,
85
+ collisionProxy,
86
+ collisionProxyMetadata,
87
+ children,
88
+ showPlaceholder = true,
89
+ ...groupProps
90
+ }) {
91
+ const metadata = useSplatEnvironment({
92
+ environment,
93
+ src,
94
+ format,
95
+ collisionProxy: collisionProxyMetadata
96
+ });
97
+ const existingUserData = typeof groupProps.userData === "object" && groupProps.userData !== null ? groupProps.userData : {};
98
+ return /* @__PURE__ */ jsxs(
99
+ "group",
100
+ {
101
+ ...groupProps,
102
+ userData: {
103
+ ...existingUserData,
104
+ ...metadata.userData
105
+ },
106
+ children: [
107
+ children,
108
+ children || !showPlaceholder ? null : /* @__PURE__ */ jsx(SplatPlaceholder, {}),
109
+ collisionProxy
110
+ ]
111
+ }
112
+ );
113
+ }
114
+ function useSplatEnvironment({
115
+ environment,
116
+ src,
117
+ format,
118
+ collisionProxy
119
+ }) {
120
+ const resolvedSrc = src ?? environment?.splat.src;
121
+ const resolvedFormat = format ?? environment?.splat.format ?? "spz";
122
+ const resolvedCollisionProxy = collisionProxy ?? environment?.collisionProxy;
123
+ return useMemo(
124
+ () => ({
125
+ src: resolvedSrc,
126
+ format: resolvedFormat,
127
+ collisionProxy: resolvedCollisionProxy,
128
+ userData: createSplatEnvironmentUserData({
129
+ environment,
130
+ src: resolvedSrc,
131
+ format: resolvedFormat,
132
+ collisionProxy: resolvedCollisionProxy
133
+ })
134
+ }),
135
+ [environment, resolvedSrc, resolvedFormat, resolvedCollisionProxy]
136
+ );
137
+ }
138
+ function createSplatEnvironmentUserData({
139
+ environment,
140
+ src,
141
+ format = "spz",
142
+ collisionProxy
143
+ }) {
144
+ return {
145
+ role: "splat-environment",
146
+ environmentId: environment?.id,
147
+ environmentLabel: environment?.label,
148
+ splatSrc: src,
149
+ splatFormat: format,
150
+ splatRenderer: environment?.splat.renderer,
151
+ collisionProxyStatus: collisionProxy?.status ?? "missing",
152
+ collisionProxyXmlPath: collisionProxy?.xmlPath,
153
+ collisionProxyPrimitives: collisionProxy?.primitives ?? []
154
+ };
155
+ }
156
+ function createSparkSplatViewerUrl({
157
+ viewerUrl,
158
+ splatSrc
159
+ }) {
160
+ const url = new URL(viewerUrl, "http://mujoco-react.local");
161
+ url.searchParams.set("splat", splatSrc);
162
+ return viewerUrl.startsWith("http") ? url.toString() : `${url.pathname}${url.search}`;
163
+ }
164
+ function SplatPlaceholder() {
165
+ return /* @__PURE__ */ jsx("group", { children: /* @__PURE__ */ jsxs("mesh", { position: [0, 0, 1.2], children: [
166
+ /* @__PURE__ */ jsx("boxGeometry", { args: [2.4, 2.4, 2.4] }),
167
+ /* @__PURE__ */ jsx(
168
+ "meshBasicMaterial",
169
+ {
170
+ color: "#8b8b8b",
171
+ transparent: true,
172
+ opacity: 0.06,
173
+ wireframe: true,
174
+ side: THREE.DoubleSide
175
+ }
176
+ )
177
+ ] }) });
178
+ }
179
+ /**
180
+ * @license
181
+ * SPDX-License-Identifier: Apache-2.0
182
+ */
183
+
184
+ export { ScenarioLighting, SplatEnvironment, createSparkSplatViewerUrl, createSplatEnvironmentUserData, getScenarioBackground, getScenarioCameraPosition, useSplatEnvironment };
185
+ //# sourceMappingURL=chunk-KGFRKPLS.js.map
186
+ //# sourceMappingURL=chunk-KGFRKPLS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/VisualScenario.tsx"],"names":[],"mappings":";;;;;AAqBA,IAAM,kBAAA,GAAqB,SAAA;AAEpB,SAAS,gBAAA,CAAiB;AAAA,EAC/B,MAAA,GAAS,QAAA;AAAA,EACT,UAAA,GAAa,IAAA;AAAA,EACb,SAAA,GAAY;AACd,CAAA,EAA0B;AACxB,EAAA,IAAI,WAAW,WAAA,EAAa;AAC1B,IAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,cAAA,EAAA,EAAa,SAAA,EAAW,IAAA,GAAO,SAAA,EAAW,CAAA;AAAA,sBAC3C,GAAA;AAAA,QAAC,kBAAA;AAAA,QAAA;AAAA,UACC,QAAA,EAAU,CAAC,GAAA,EAAK,EAAA,EAAI,CAAC,CAAA;AAAA,UACrB,WAAW,GAAA,GAAM,SAAA;AAAA,UACjB;AAAA;AAAA,OACF;AAAA,sBACA,GAAA,CAAC,kBAAA,EAAA,EAAiB,QAAA,EAAU,CAAC,EAAA,EAAI,KAAK,GAAG,CAAA,EAAG,SAAA,EAAW,IAAA,GAAO,SAAA,EAAW;AAAA,KAAA,EAC3E,CAAA;AAAA,EAEJ;AAEA,EAAA,IAAI,WAAW,WAAA,EAAa;AAC1B,IAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,cAAA,EAAA,EAAa,SAAA,EAAW,IAAA,GAAO,SAAA,EAAW,CAAA;AAAA,sBAC3C,GAAA;AAAA,QAAC,kBAAA;AAAA,QAAA;AAAA,UACC,QAAA,EAAU,CAAC,CAAA,EAAG,EAAA,EAAI,CAAC,CAAA;AAAA,UACnB,WAAW,IAAA,GAAO,SAAA;AAAA,UAClB;AAAA;AAAA,OACF;AAAA,sBACA,GAAA,CAAC,YAAA,EAAA,EAAW,QAAA,EAAU,CAAC,IAAA,EAAM,MAAM,GAAG,CAAA,EAAG,SAAA,EAAW,GAAA,GAAM,SAAA,EAAW;AAAA,KAAA,EACvE,CAAA;AAAA,EAEJ;AAEA,EAAA,IAAI,WAAW,OAAA,EAAS;AACtB,IAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,sBAAA,GAAA,CAAC,cAAA,EAAA,EAAa,SAAA,EAAW,IAAA,GAAO,SAAA,EAAW,CAAA;AAAA,sBAC3C,GAAA;AAAA,QAAC,kBAAA;AAAA,QAAA;AAAA,UACC,QAAA,EAAU,CAAC,GAAA,EAAK,IAAA,EAAM,GAAG,CAAA;AAAA,UACzB,WAAW,GAAA,GAAM,SAAA;AAAA,UACjB;AAAA;AAAA,OACF;AAAA,sBACA,GAAA,CAAC,YAAA,EAAA,EAAW,QAAA,EAAU,CAAC,GAAA,EAAK,KAAK,GAAG,CAAA,EAAG,SAAA,EAAW,IAAA,GAAO,SAAA,EAAW;AAAA,KAAA,EACtE,CAAA;AAAA,EAEJ;AAEA,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,cAAA,EAAA,EAAa,SAAA,EAAW,IAAA,GAAO,SAAA,EAAW,CAAA;AAAA,oBAC3C,GAAA;AAAA,MAAC,kBAAA;AAAA,MAAA;AAAA,QACC,QAAA,EAAU,CAAC,GAAA,EAAK,EAAA,EAAI,CAAC,CAAA;AAAA,QACrB,WAAW,GAAA,GAAM,SAAA;AAAA,QACjB;AAAA;AAAA;AACF,GAAA,EACF,CAAA;AAEJ;AAEO,SAAS,qBAAA,CACd,MAAA,EACA,QAAA,GAAW,kBAAA,EACX;AACA,EAAA,IAAI,MAAA,KAAW,aAAa,OAAO,SAAA;AACnC,EAAA,IAAI,MAAA,KAAW,aAAa,OAAO,SAAA;AACnC,EAAA,IAAI,MAAA,KAAW,SAAS,OAAO,SAAA;AAC/B,EAAA,OAAO,QAAA;AACT;AAEO,SAAS,yBAAA,CACd,cACA,QAAA,EAC0B;AAC1B,EAAA,MAAM,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,GAAI,YAAA;AAClB,EAAA,MAAM,MAAA,GAAS,QAAA,EAAU,MAAA,EAAQ,MAAA,IAAU,CAAA;AAE3C,EAAA,OAAO;AAAA,IACL,QAAQ,CAAA,GAAI,MAAA,GAAS,GAAA,EAAK,OAAA,CAAQ,CAAC,CAAC,CAAA;AAAA,IACpC,QAAQ,CAAA,GAAI,MAAA,GAAS,GAAA,EAAK,OAAA,CAAQ,CAAC,CAAC,CAAA;AAAA,IACpC,QAAQ,CAAA,GAAI,MAAA,GAAS,IAAA,EAAM,OAAA,CAAQ,CAAC,CAAC;AAAA,GACvC;AACF;AASO,SAAS,gBAAA,CAAiB;AAAA,EAC/B,WAAA;AAAA,EACA,GAAA;AAAA,EACA,MAAA;AAAA,EACA,cAAA;AAAA,EACA,sBAAA;AAAA,EACA,QAAA;AAAA,EACA,eAAA,GAAkB,IAAA;AAAA,EAClB,GAAG;AACL,CAAA,EAA0B;AACxB,EAAA,MAAM,WAAW,mBAAA,CAAoB;AAAA,IACnC,WAAA;AAAA,IACA,GAAA;AAAA,IACA,MAAA;AAAA,IACA,cAAA,EAAgB;AAAA,GACjB,CAAA;AACD,EAAA,MAAM,gBAAA,GACJ,OAAO,UAAA,CAAW,QAAA,KAAa,QAAA,IAAY,WAAW,QAAA,KAAa,IAAA,GAC/D,UAAA,CAAW,QAAA,GACX,EAAC;AAEP,EAAA,uBACE,IAAA;AAAA,IAAC,OAAA;AAAA,IAAA;AAAA,MACE,GAAG,UAAA;AAAA,MACJ,QAAA,EAAU;AAAA,QACR,GAAG,gBAAA;AAAA,QACH,GAAG,QAAA,CAAS;AAAA,OACd;AAAA,MAEC,QAAA,EAAA;AAAA,QAAA,QAAA;AAAA,QACA,QAAA,IAAY,CAAC,eAAA,GAAkB,IAAA,uBAAQ,gBAAA,EAAA,EAAiB,CAAA;AAAA,QACxD;AAAA;AAAA;AAAA,GACH;AAEJ;AAEO,SAAS,mBAAA,CAAoB;AAAA,EAClC,WAAA;AAAA,EACA,GAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAA4D;AAC1D,EAAA,MAAM,WAAA,GAAc,GAAA,IAAO,WAAA,EAAa,KAAA,CAAM,GAAA;AAC9C,EAAA,MAAM,cAAA,GAAiB,MAAA,IAAU,WAAA,EAAa,KAAA,CAAM,MAAA,IAAU,KAAA;AAC9D,EAAA,MAAM,sBAAA,GAAyB,kBAAkB,WAAA,EAAa,cAAA;AAE9D,EAAA,OAAO,OAAA;AAAA,IACL,OAAO;AAAA,MACL,GAAA,EAAK,WAAA;AAAA,MACL,MAAA,EAAQ,cAAA;AAAA,MACR,cAAA,EAAgB,sBAAA;AAAA,MAChB,UAAU,8BAAA,CAA+B;AAAA,QACvC,WAAA;AAAA,QACA,GAAA,EAAK,WAAA;AAAA,QACL,MAAA,EAAQ,cAAA;AAAA,QACR,cAAA,EAAgB;AAAA,OACjB;AAAA,KACH,CAAA;AAAA,IACA,CAAC,WAAA,EAAa,WAAA,EAAa,cAAA,EAAgB,sBAAsB;AAAA,GACnE;AACF;AAEO,SAAS,8BAAA,CAA+B;AAAA,EAC7C,WAAA;AAAA,EACA,GAAA;AAAA,EACA,MAAA,GAAS,KAAA;AAAA,EACT;AACF,CAAA,EAKG;AACD,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,mBAAA;AAAA,IACN,eAAe,WAAA,EAAa,EAAA;AAAA,IAC5B,kBAAkB,WAAA,EAAa,KAAA;AAAA,IAC/B,QAAA,EAAU,GAAA;AAAA,IACV,WAAA,EAAa,MAAA;AAAA,IACb,aAAA,EAAe,aAAa,KAAA,CAAM,QAAA;AAAA,IAClC,oBAAA,EAAsB,gBAAgB,MAAA,IAAU,SAAA;AAAA,IAChD,uBAAuB,cAAA,EAAgB,OAAA;AAAA,IACvC,wBAAA,EAA0B,cAAA,EAAgB,UAAA,IAAc;AAAC,GAC3D;AACF;AAEO,SAAS,yBAAA,CAA0B;AAAA,EACxC,SAAA;AAAA,EACA;AACF,CAAA,EAGG;AACD,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,SAAA,EAAW,2BAA2B,CAAA;AAC1D,EAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,OAAA,EAAS,QAAQ,CAAA;AACtC,EAAA,OAAO,SAAA,CAAU,UAAA,CAAW,MAAM,CAAA,GAAI,GAAA,CAAI,QAAA,EAAS,GAAI,CAAA,EAAG,GAAA,CAAI,QAAQ,CAAA,EAAG,GAAA,CAAI,MAAM,CAAA,CAAA;AACrF;AAEA,SAAS,gBAAA,GAAmB;AAC1B,EAAA,uBACE,GAAA,CAAC,WACC,QAAA,kBAAA,IAAA,CAAC,MAAA,EAAA,EAAK,UAAU,CAAC,CAAA,EAAG,CAAA,EAAG,GAAG,CAAA,EACxB,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,iBAAY,IAAA,EAAM,CAAC,GAAA,EAAK,GAAA,EAAK,GAAG,CAAA,EAAG,CAAA;AAAA,oBACpC,GAAA;AAAA,MAAC,mBAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAM,SAAA;AAAA,QACN,WAAA,EAAW,IAAA;AAAA,QACX,OAAA,EAAS,IAAA;AAAA,QACT,SAAA,EAAS,IAAA;AAAA,QACT,IAAA,EAAY,KAAA,CAAA;AAAA;AAAA;AACd,GAAA,EACF,CAAA,EACF,CAAA;AAEJ","file":"chunk-KGFRKPLS.js","sourcesContent":["/**\n * @license\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport type { ThreeElements } from '@react-three/fiber';\nimport type { ReactNode } from 'react';\nimport { useMemo } from 'react';\nimport * as THREE from 'three';\nimport type {\n PairedSplatEnvironmentConfig,\n SplatCollisionProxyConfig,\n SplatEnvironmentMetadata,\n SplatEnvironmentMetadataInput,\n SplatFormat,\n ScenarioLightingPreset,\n ScenarioLightingProps,\n SplatEnvironmentProps,\n VisualScenarioConfig,\n} from '../types';\n\nconst DEFAULT_BACKGROUND = '#181a1f';\n\nexport function ScenarioLighting({\n preset = 'studio',\n castShadow = true,\n intensity = 1,\n}: ScenarioLightingProps) {\n if (preset === 'warehouse') {\n return (\n <>\n <ambientLight intensity={0.18 * intensity} />\n <directionalLight\n position={[3.5, -2, 5]}\n intensity={2.2 * intensity}\n castShadow={castShadow}\n />\n <directionalLight position={[-2, 1.5, 2.5]} intensity={0.25 * intensity} />\n </>\n );\n }\n\n if (preset === 'low-light') {\n return (\n <>\n <ambientLight intensity={0.08 * intensity} />\n <directionalLight\n position={[2, -2, 3]}\n intensity={0.75 * intensity}\n castShadow={castShadow}\n />\n <pointLight position={[-0.5, -0.8, 1.3]} intensity={0.6 * intensity} />\n </>\n );\n }\n\n if (preset === 'splat') {\n return (\n <>\n <ambientLight intensity={0.42 * intensity} />\n <directionalLight\n position={[1.8, -2.4, 3.5]}\n intensity={1.2 * intensity}\n castShadow={castShadow}\n />\n <pointLight position={[0.4, 0.2, 1.4]} intensity={0.35 * intensity} />\n </>\n );\n }\n\n return (\n <>\n <ambientLight intensity={0.35 * intensity} />\n <directionalLight\n position={[2.5, -3, 4]}\n intensity={1.6 * intensity}\n castShadow={castShadow}\n />\n </>\n );\n}\n\nexport function getScenarioBackground(\n preset: ScenarioLightingPreset | undefined,\n fallback = DEFAULT_BACKGROUND\n) {\n if (preset === 'warehouse') return '#20242b';\n if (preset === 'low-light') return '#0f1115';\n if (preset === 'splat') return '#1b1f24';\n return fallback;\n}\n\nexport function getScenarioCameraPosition(\n basePosition: readonly [number, number, number],\n scenario?: Pick<VisualScenarioConfig, 'camera'>\n): [number, number, number] {\n const [x, y, z] = basePosition;\n const jitter = scenario?.camera?.jitter ?? 0;\n\n return [\n Number((x + jitter * 0.6).toFixed(3)),\n Number((y - jitter * 0.4).toFixed(3)),\n Number((z + jitter * 0.25).toFixed(3)),\n ];\n}\n\n/**\n * Renderer-agnostic Gaussian splat environment boundary.\n *\n * This component intentionally does not import a specific 3DGS renderer. Pass a\n * Spark/GaussianSplats3D object as `children` once the app chooses a renderer,\n * and pass MuJoCo/MJCF collision proxy visuals via `collisionProxy`.\n */\nexport function SplatEnvironment({\n environment,\n src,\n format,\n collisionProxy,\n collisionProxyMetadata,\n children,\n showPlaceholder = true,\n ...groupProps\n}: SplatEnvironmentProps) {\n const metadata = useSplatEnvironment({\n environment,\n src,\n format,\n collisionProxy: collisionProxyMetadata,\n });\n const existingUserData =\n typeof groupProps.userData === 'object' && groupProps.userData !== null\n ? groupProps.userData\n : {};\n\n return (\n <group\n {...groupProps}\n userData={{\n ...existingUserData,\n ...metadata.userData,\n }}\n >\n {children}\n {children || !showPlaceholder ? null : <SplatPlaceholder />}\n {collisionProxy}\n </group>\n );\n}\n\nexport function useSplatEnvironment({\n environment,\n src,\n format,\n collisionProxy,\n}: SplatEnvironmentMetadataInput): SplatEnvironmentMetadata {\n const resolvedSrc = src ?? environment?.splat.src;\n const resolvedFormat = format ?? environment?.splat.format ?? 'spz';\n const resolvedCollisionProxy = collisionProxy ?? environment?.collisionProxy;\n\n return useMemo(\n () => ({\n src: resolvedSrc,\n format: resolvedFormat,\n collisionProxy: resolvedCollisionProxy,\n userData: createSplatEnvironmentUserData({\n environment,\n src: resolvedSrc,\n format: resolvedFormat,\n collisionProxy: resolvedCollisionProxy,\n }),\n }),\n [environment, resolvedSrc, resolvedFormat, resolvedCollisionProxy]\n );\n}\n\nexport function createSplatEnvironmentUserData({\n environment,\n src,\n format = 'spz',\n collisionProxy,\n}: {\n environment?: PairedSplatEnvironmentConfig;\n src?: string;\n format?: SplatFormat;\n collisionProxy?: SplatCollisionProxyConfig;\n}) {\n return {\n role: 'splat-environment',\n environmentId: environment?.id,\n environmentLabel: environment?.label,\n splatSrc: src,\n splatFormat: format,\n splatRenderer: environment?.splat.renderer,\n collisionProxyStatus: collisionProxy?.status ?? 'missing',\n collisionProxyXmlPath: collisionProxy?.xmlPath,\n collisionProxyPrimitives: collisionProxy?.primitives ?? [],\n };\n}\n\nexport function createSparkSplatViewerUrl({\n viewerUrl,\n splatSrc,\n}: {\n viewerUrl: string;\n splatSrc: string;\n}) {\n const url = new URL(viewerUrl, 'http://mujoco-react.local');\n url.searchParams.set('splat', splatSrc);\n return viewerUrl.startsWith('http') ? url.toString() : `${url.pathname}${url.search}`;\n}\n\nfunction SplatPlaceholder() {\n return (\n <group>\n <mesh position={[0, 0, 1.2]}>\n <boxGeometry args={[2.4, 2.4, 2.4]} />\n <meshBasicMaterial\n color=\"#8b8b8b\"\n transparent\n opacity={0.06}\n wireframe\n side={THREE.DoubleSide}\n />\n </mesh>\n </group>\n );\n}\n\nexport type SplatCollisionProxy = ReactNode | ThreeElements['group'];\n"]}