├── .gitignore
├── LICENSE.txt
├── README.md
├── TERMS.txt
├── assets
├── fonts
│ ├── icons.svg
│ ├── icons.ttf
│ └── icons.woff
├── images
│ ├── background-removal-preview.jpg
│ ├── custom-shaders-preview.jpg
│ ├── github-mark-16.svg
│ ├── hello-world-preview.jpg
│ ├── og-image.jpg
│ ├── react-three-fiber-preview.jpg
│ ├── scene-lighting-preview.jpg
│ ├── three.js-fog-preview.jpg
│ ├── transmission-preview.jpg
│ └── vr-preview.jpg
├── logo.svg
├── models
│ └── ufo_b11_d_model.glb
└── venice_sunset_1k.hdr
├── index.html
├── misc
├── common.sh
└── deploy.sh
├── package-lock.json
├── package.json
├── src
├── DemoBackgroundRemoval.ts
├── DemoCustomShaders.ts
├── DemoFog.ts
├── DemoHelloWorld.ts
├── DemoLighting.ts
├── DemoReactThreeFiber.tsx
├── DemoTransmission.ts
├── DemoVR.ts
├── LumaSplatsReact.ts
├── index.tsx
├── minimal.html
└── util
│ ├── DownloadArtifacts.ts
│ ├── Environment.ts
│ ├── EnvironmentProbes.ts
│ ├── GUIUtils.ts
│ └── MarkdownImport.d.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Luma AI
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [](https://lumalabs.ai) Luma WebGL Library
2 |
3 | `luma-web` is a [npm package](https://www.npmjs.com/package/@lumaai/luma-web) for rendering photoreal interactive scenes captured by the [Luma app](https://lumalabs.ai/). It includes `LumaSplatsWebGL`, which is a WebGL-only gaussian splatting implementation designed to be integrated with 3D frameworks, and `LumaSplatsThree`, which is a Three.js implementation that uses `LumaSplatsWebGL` under the hood. For these examples we'll use [Three.js](https://threejs.org/).
4 |
5 |
6 | **Request features and report bugs on our [ GitHub repo](https://github.com/lumalabs/luma-web-library)**
7 |
8 | ### Contents
9 | - [Getting Started](#getting-started)
10 | - [Background Removal](#background-removal)
11 | - [Three Fog](#three-fog)
12 | - [Scene Lighting](#scene-lighting)
13 | - [Custom Shaders](#custom-shaders)
14 | - [React Three Fiber](#react-three-fiber)
15 | - [Transmission](#transmission)
16 | - [VR](#vr)
17 |
18 | ## Getting Started
19 | [](#getting-started)
20 |
21 | The simplest way to get started is to create a .html file and load the library from a CDN. Here we load three.js and the luma library and setup a minimal scene with mouse controls. Copy and paste this into a file named `index.html` and open it in your browser (no server needed).
22 |
23 | **[minimal.html](./src/minimal.html)**
24 | ```html
25 |
26 |
35 |
67 | ```
68 |
69 | Alternatively you can install the library from npm:
70 |
71 | ```bash
72 | npm install @lumaai/luma-web
73 | ```
74 |
75 | **Usage**
76 |
77 | Import the `LumaSplatsThree` class:
78 |
79 | ```ts
80 | import { LumaSplatsThree } from "@lumaai/luma-web";
81 | ```
82 |
83 | Then create an instance of `LumaSplatsThree` with a splat `source`, and add it to your scene.
84 |
85 | `source` can be either of:
86 | - URL to a capture on [lumalabs.ai](https://lumalabs.ai)
87 | - path to a luma splats file or folder containing a luma splats artifacts
88 |
89 | **[DemoHelloWorld.ts](./src/DemoHelloWorld.ts)**
90 | ```ts
91 | let splats = new LumaSplatsThree({
92 | source: 'https://lumalabs.ai/capture/ca9ea966-ca24-4ec1-ab0f-af665cb546ff',
93 | // controls the particle entrance animation
94 | particleRevealEnabled: true,
95 | });
96 |
97 | scene.add(splats);
98 | ```
99 |
100 | Splats will integrate with the three.js rendering pipeline and interact with other objects via depth testing. However, splats do not currently write to the depth buffer themselves.
101 |
102 | ### Performance tips
103 |
104 | - Use `antialias: false` when creating the renderer to disable MSAA on the canvas. Splats are already anti-aliased and the high instance count in splats is expensive to render with MSAA
105 | - Set `enableThreeShaderIntegration: false` to disable integration with the three.js rendering pipeline. This will disable features like fog and tone mapping, but will improve performance
106 |
107 | ## Background Removal
108 | [](#background-removal)
109 |
110 | Luma scenes can include multiple semantic layers. By default, all layers are rendered. To filter layers, use the `semanticsMask` property. This is a bit mask, so for example, to show only the foreground layer, set `semanticsMask = LumaSplatsSemantics.FOREGROUND`. To show both foreground and background, set `semanticsMask = LumaSplatsSemantics.FOREGROUND | LumaSplatsSemantics.BACKGROUND`
111 |
112 | **[DemoBackgroundRemoval.ts](./src/DemoBackgroundRemoval.ts)**
113 | ```ts
114 | import { LumaSplatsSemantics, LumaSplatsThree } from "@lumaai/luma-web";
115 |
116 | let splats = new LumaSplatsThree({
117 | source: 'https://lumalabs.ai/capture/1b5f3e33-3900-4398-8795-b585ae13fd2d',
118 | });
119 |
120 | scene.add(splats);
121 |
122 | // filter splats to only show foreground layers
123 | splats.semanticsMask = LumaSplatsSemantics.FOREGROUND;
124 | ```
125 |
126 | ## Three Fog
127 | [](#three-fog)
128 |
129 | Luma splats integrate with the three.js rendering pipeline including features like tone mapping, color spaces and fog. Ensure `enableThreeShaderIntegration` is set to `true` (the default) and set the scene fog
130 |
131 | **[DemoFog.ts](./src/DemoFog.ts)**
132 | ```ts
133 | scene.fog = new FogExp2(new Color(0xe0e1ff).convertLinearToSRGB(), 0.15);
134 | scene.background = scene.fog.color;
135 | ```
136 |
137 | ## Scene Lighting
138 | [](#scene-lighting)
139 |
140 | It's possible to illuminate three.js scenes with Luma splats. To do so, we can render a cubemap of the splats and use it as the scene environment. This is done by calling `captureCubemap()` on the splats object. We first wait for the splats to fully load before capturing the cubemap. To ensure the splats are fully rendered at the time of capture, we disable the loading animation.
141 |
142 | **[DemoLighting.ts](./src/DemoLighting.ts)**
143 | ```ts
144 | let splats = new LumaSplatsThree({
145 | source: 'https://lumalabs.ai/capture/4da7cf32-865a-4515-8cb9-9dfc574c90c2',
146 |
147 | // disable loading animation so model is fully rendered after onLoad
148 | loadingAnimationEnabled: false,
149 | });
150 |
151 | splats.onLoad = () => {
152 | splats.captureCubemap(renderer).then((capturedTexture) => {
153 | scene.environment = capturedTexture;
154 | scene.background = capturedTexture;
155 | scene.backgroundBlurriness = 0.5;
156 | });
157 | }
158 | ```
159 |
160 | ## Custom Shaders
161 | [](#custom-shaders)
162 |
163 | You can inject code into the splat shaders to customize them. To do this, call `setShaderHooks({ ... })` on your splat and provide GLSL functions, uniforms and globals to override default behavior. For example, in this demo we apply a transform matrix to each splat by setting the vertex shader hook `getSplatTransform`. It generates a transform matrix for time-varying sinusoidal offset to the y coordinate.
164 |
165 | The syntax for shader hook function is a GLSL function without a function name. The GLSL function arguments and return are given as documentation on the shader hook fields (see below).
166 |
167 | **[DemoCustomShaders.ts](./src/DemoCustomShaders.ts)**
168 | ```ts
169 | splats.setShaderHooks({
170 | vertexShaderHooks: {
171 | additionalUniforms: {
172 | time_s: ['float', uniformTime],
173 | },
174 |
175 | getSplatTransform: /*glsl*/`
176 | (vec3 position, uint layersBitmask) {
177 | // sin wave on x-axis
178 | float x = 0.;
179 | float z = 0.;
180 | float y = sin(position.x * 1.0 + time_s) * 0.1;
181 | return mat4(
182 | 1., 0., 0., 0,
183 | 0., 1., 0., 0,
184 | 0., 0., 1., 0,
185 | x, y, z, 1.
186 | );
187 | }
188 | `,
189 | }
190 | });
191 | ```
192 |
193 | ### Shader Hook API
194 | ```typescript
195 | type LumaShaderHooks = {
196 |
197 | /** Hooks added to the vertex shader */
198 | vertexShaderHooks?: {
199 | additionalUniforms?: { [name: string]: [UniformTypeGLSL, { value: any }] },
200 |
201 | /** Inject into global space (for example, to add a varying) */
202 | additionalGlobals?: string,
203 |
204 | /**
205 | * Example `(vec3 splatPosition, uint layersBitmask) { return mat4(1.); }`
206 | * @param {vec3} splatPosition, object-space
207 | * @param {uint} layersBitmask, bit mask of layers, where bit 0 is background and bit 1 is foreground
208 | * @returns {mat4} per-splat local transform
209 | */
210 | getSplatTransform?: string,
211 |
212 | /**
213 | * Executed at the end of the main function after gl_Position is set
214 | *
215 | * Example `() {
216 | * vPosition = gl_Position;
217 | * }`
218 | * @returns {void}
219 | */
220 | onMainEnd?: string,
221 |
222 | /**
223 | * Example `(vec4 splatColor, vec3 splatPosition) { return pow(splatColor.rgb, vec3(2.2), splatColor.a); }`
224 | * Use `gl_Position` is available
225 | * @param {vec4} splatColor, default splat color
226 | * @param {vec3} splatPosition, object-space
227 | * @param {uint} layersBitmask, bit mask of layers, where bit 0 is background and bit 1 is foreground
228 | * @returns {vec4} updated splat color
229 | */
230 | getSplatColor?: string,
231 | },
232 |
233 | /** Hooks added to the fragment shader */
234 | fragmentShaderHooks?: {
235 | additionalUniforms?: { [name: string]: [UniformTypeGLSL, { value: any }] },
236 |
237 | /** Inject into global space (for example, to add a varying) */
238 | additionalGlobals?: string,
239 |
240 | /**
241 | * Example `(vec4 fragColor) { return tonemap(fragColor); }`
242 | * @param {vec4} fragColor, default fragment color
243 | * @returns {vec4} updated fragment color
244 | */
245 | getFragmentColor?: string,
246 | }
247 | }
248 | ```
249 |
250 | ## React Three Fiber
251 | [](#react-three-fiber)
252 |
253 | Luma splats can be used with [React Three Fiber](https://docs.pmnd.rs/), a React renderer for Three.js. To do so, we need to extend R3F to include the `LumaSplatsThree` class. This is done by calling `extend` with the class and a name (in this case `LumaSplats` which will be used as the component name). If using TypeScript, we also need to declare the component type.
254 |
255 | **[DemoReactThreeFiber.tsx](./src/DemoReactThreeFiber.tsx)**
256 | ```typescript
257 | import { Object3DNode, extend } from '@react-three/fiber';
258 | import { LumaSplatsThree, LumaSplatsSemantics } from "@lumaai/luma-web";
259 |
260 | // Make LumaSplatsThree available to R3F
261 | extend( { LumaSplats: LumaSplatsThree } );
262 |
263 | // For typeScript support:
264 | declare module '@react-three/fiber' {
265 | interface ThreeElements {
266 | lumaSplats: Object3DNode
267 | }
268 | }
269 |
270 | function Scene() {
271 | return
277 | }
278 | ```
279 |
280 | ## Transmission
281 | [](#transmission)
282 |
283 | Splats can be used in combination with three.js transmission effects, however some care should be taken to make this work. Splats are considered `transparent` materials in three.js which means by default they're not rendered in the transmissive pass, so initially you won't see your splats in transmissive materials. To fix we set `splats.material.transparent = false;`.
284 |
285 | In this example, we draw two splat scenes, one inside a refractive globe and the other outside. To make this work, we want the inner splat scene to _only_ render to the transmission buffer and the outer to the canvas. We do this by checking the render target before rendering and selectively disabling.
286 |
287 | **[DemoTransmission.ts](./src/DemoTransmission.ts)**
288 | ```typescript
289 | // inner splat
290 | let globeSplats = new LumaSplatsThree({
291 | // Chateau de Menthon - Annecy
292 | source: 'https://lumalabs.ai/capture/da82625c-9c8d-4d05-a9f7-3367ecab438c',
293 | enableThreeShaderIntegration: true,
294 | onBeforeRender: (renderer) => {
295 | // disable MSAA on render targets (in this case the transmission render target)
296 | // this improves splatting performance
297 | let target = renderer.getRenderTarget();
298 | if (target) {
299 | target.samples = 0;
300 | }
301 |
302 | // only render in targets and not the canvas
303 | globeSplats.preventDraw = target == null;
304 | }
305 | });
306 |
307 | // disable transparency so the renderer considers it an opaque object
308 | // opaque objects are rendered in the transmission pass (whereas transparent objects are not)
309 | globeSplats.material.transparent = false;
310 |
311 | scene.add(globeSplats);
312 |
313 | // outer splat
314 | let environmentSplats = new LumaSplatsThree({
315 | // Arosa Hörnli - Switzerland
316 | source: 'https://lumalabs.ai/capture/4da7cf32-865a-4515-8cb9-9dfc574c90c2',
317 | // disable animation for lighting capture
318 | loadingAnimationEnabled: false,
319 | // disable three.js shader integration for performance
320 | enableThreeShaderIntegration: false,
321 | });
322 |
323 | scene.add(environmentSplats);
324 |
325 | // add a refractive transmissive sphere
326 | let glassSphere = new Mesh(
327 | new SphereGeometry(1, 32, 32),
328 | new MeshPhysicalMaterial({
329 | roughness: 0,
330 | metalness: 0,
331 | transmission: 1,
332 | ior: 1.341,
333 | thickness: 1.52,
334 | envMapIntensity: 1.2,
335 | clearcoat: 1,
336 | side: FrontSide,
337 | transparent: true,
338 | })
339 | );
340 |
341 | scene.add(glassSphere);
342 | ```
343 |
344 | ## VR
345 | [](#vr)
346 |
347 | Viewing your splats in VR is as simple as enabling XR in three.js and adding a VR button. View this demo with a VR headset (or through a headset browser) and click "Enter VR"! It will work best on PC VR, standalone VR tends to struggle with splats presently
348 |
349 | **[DemoVR.ts](./src/DemoVR.ts)**
350 | ```typescript
351 | import { VRButton } from "three/examples/jsm/webxr/VRButton.js";
352 |
353 | renderer.xr.enabled = true;
354 |
355 | let vrButton = VRButton.createButton(renderer);
356 |
357 | document.body.appendChild(vrButton);
358 |
359 | let splats = new LumaSplatsThree({
360 | // Kind Humanoid @RyanHickman
361 | source: 'https://lumalabs.ai/capture/83e9aae8-7023-448e-83a6-53ccb377ec86',
362 | });
363 |
364 | scene.add(splats);
365 | ```
366 |
--------------------------------------------------------------------------------
/TERMS.txt:
--------------------------------------------------------------------------------
1 | Terms of Service
2 | See https://lumalabs.ai/legal/tos
3 |
4 |
5 | Privacy Policy
6 | See https://lumalabs.ai/legal/privacy
--------------------------------------------------------------------------------
/assets/fonts/icons.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/assets/fonts/icons.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/fonts/icons.ttf
--------------------------------------------------------------------------------
/assets/fonts/icons.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/fonts/icons.woff
--------------------------------------------------------------------------------
/assets/images/background-removal-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/images/background-removal-preview.jpg
--------------------------------------------------------------------------------
/assets/images/custom-shaders-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/images/custom-shaders-preview.jpg
--------------------------------------------------------------------------------
/assets/images/github-mark-16.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/assets/images/hello-world-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/images/hello-world-preview.jpg
--------------------------------------------------------------------------------
/assets/images/og-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/images/og-image.jpg
--------------------------------------------------------------------------------
/assets/images/react-three-fiber-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/images/react-three-fiber-preview.jpg
--------------------------------------------------------------------------------
/assets/images/scene-lighting-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/images/scene-lighting-preview.jpg
--------------------------------------------------------------------------------
/assets/images/three.js-fog-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/images/three.js-fog-preview.jpg
--------------------------------------------------------------------------------
/assets/images/transmission-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/images/transmission-preview.jpg
--------------------------------------------------------------------------------
/assets/images/vr-preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/images/vr-preview.jpg
--------------------------------------------------------------------------------
/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
36 |
--------------------------------------------------------------------------------
/assets/models/ufo_b11_d_model.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/models/ufo_b11_d_model.glb
--------------------------------------------------------------------------------
/assets/venice_sunset_1k.hdr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lumalabs/luma-web-examples/fefe15436908140788d061f962e92684e3413cb8/assets/venice_sunset_1k.hdr
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
165 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
--------------------------------------------------------------------------------
/misc/common.sh:
--------------------------------------------------------------------------------
1 | # bash colors
2 | RED='\033[31m'
3 | GREEN='\033[32m'
4 | ORANGE='\033[33m'
5 | RESET='\033[0m'
6 | BOLD='\033[1m'
7 |
8 | HAS_ERRORS=false
9 |
10 | # log functions
11 | error() {
12 | ERROR="${RED}ERROR: "
13 | echo -e "${ERROR}$1${RESET}"
14 | HAS_ERRORS=true
15 | }
16 | fatal() {
17 | error "$1"
18 | exit 1
19 | }
20 | warning() {
21 | WARNING="${ORANGE}WARNING: "
22 | echo -e "${WARNING}$1${RESET}"
23 | HAS_ERRORS=true
24 | }
25 | ok() {
26 | OK="${BOLD}${GREEN}✓${RESET}${GREEN} "
27 | echo -e "${OK}$1${RESET}"
28 | }
29 | doYouWantToContinue() {
30 | echo -e "Do you want to continue? (${BOLD}y${RESET}/${BOLD}n${RESET})"
31 | read -p "> " -n 1 -r
32 | echo
33 | if [[ ! $REPLY =~ ^[Yy]$ ]]
34 | then
35 | exit
36 | fi
37 | }
--------------------------------------------------------------------------------
/misc/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # include common.sh
4 | source ./misc/common.sh
5 |
6 | # Ensure the script stops if any command fails
7 | set -e
8 | # set -x
9 |
10 | # Get the version of the package @lumaai/luma-web
11 | version_info=$(npm list --depth=0 | grep "@lumaai/luma-web")
12 | # Extract just the version number
13 | luma_package_version=$(echo $version_info | grep -oE '\b[0-9]+\.[0-9]+\.[0-9]+\b')
14 | # We only care about major.minor, so remove the patch version
15 | luma_package_version="${luma_package_version%.*}"
16 | # Get the latest git commit hash
17 | git_hash=$(git rev-parse --short HEAD)
18 |
19 | # Check for gsutil, explain how to install it if not found
20 | if ! command -v gsutil &> /dev/null
21 | then
22 | fatal "Command \`gsutil\` could not be found, you need Google Cloud SDK CLI to publish (https://cloud.google.com/sdk/docs/install). Check it it available in PATH"
23 | fi
24 |
25 | # check we're in the root directory by the presence of package.json
26 | if [ ! -f "package.json" ]
27 | then
28 | fatal "You must run this script from the root directory of the project"
29 | fi
30 |
31 | # ensure tsc runs without error
32 | npx tsc
33 |
34 | # check if version is correct and if the user wants to continue
35 | echo -e "@lumaai/luma-web version: ${BOLD}${luma_package_version}${RESET}"
36 | doYouWantToContinue
37 |
38 | echo -e "This will delete and rebuild ${BOLD}dist/${RESET}"
39 | doYouWantToContinue
40 |
41 | # Delete the dist directory
42 | rm -rf dist
43 | # Rebuild
44 | npm run build
45 |
46 | # Buckets:
47 | # - prod: cdn-luma.com
48 | # - sandbox: sandbox.cdn-luma.com
49 | # Ask for user to type "prod" or "sandbox" publish and loop until valid input
50 | publish_env=""
51 | while [ "$publish_env" != "prod" ] && [ "$publish_env" != "sandbox" ]
52 | do
53 | echo ""
54 | echo -e "Which bucket do you want to publish to? Type \"${BOLD}prod${RESET}\" or \"${BOLD}sandbox${RESET}\":"
55 | # display input caret
56 | read -p "> " publish_env
57 | done
58 |
59 | # Set bucket name based on user input
60 | if [ "$publish_env" == "prod" ]
61 | then
62 | bucket_name="cdn-luma.com"
63 | else
64 | bucket_name="sandbox.cdn-luma.com"
65 | fi
66 |
67 | parent_path="public/lumalabs.ai/luma-web-library"
68 | path="${parent_path}/${luma_package_version}/${git_hash}"
69 |
70 | gsutil_dir="gs://${bucket_name}/${path}"
71 |
72 | # Upload the root directory to Google Cloud Storage, overwriting any existing files
73 | # excluding node_modules/, misc/, .git/, .DS_Store
74 | EXCLUDE="node_modules/*|misc/*|.git/*|.*\.DS_Store"
75 |
76 | # Dry run, add -n to only show what would be uploaded
77 | gsutil -m rsync -r -d -n -x "${EXCLUDE}" ./ "${gsutil_dir}"
78 |
79 | doYouWantToContinue
80 |
81 | # Run the upload
82 | gsutil -m rsync -r -d -x "${EXCLUDE}" ./ "${gsutil_dir}"
83 |
84 | echo ""
85 | ok "Files have been published"
86 |
87 | public_url="https://${bucket_name}/${path}/index.html"
88 | storage_url="https://console.cloud.google.com/storage/browser/${bucket_name}/${path}"
89 |
90 | echo ""
91 | echo -e "${BOLD}Public URL:${RESET} ${public_url}"
92 | echo -e "${BOLD}Storage URL:${RESET} ${storage_url}"
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "three",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "three",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@lumaai/luma-web": "^0.2.1",
13 | "@react-three/drei": "^9.88.17",
14 | "@react-three/fiber": "^8.15.11",
15 | "@types/react": "^18.2.38",
16 | "@types/react-dom": "^18.2.17",
17 | "@types/react-syntax-highlighter": "^15.5.10",
18 | "lil-gui": "^0.19.1",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "react-markdown": "^9.0.1",
22 | "react-syntax-highlighter": "^15.5.0",
23 | "three": "^0.157.0"
24 | },
25 | "devDependencies": {
26 | "@types/three": "^0.157.2",
27 | "esbuild": "^0.19.5",
28 | "typescript": "^5.3.2"
29 | }
30 | },
31 | "../..": {
32 | "extraneous": true
33 | },
34 | "node_modules/@babel/runtime": {
35 | "version": "7.23.4",
36 | "license": "MIT",
37 | "dependencies": {
38 | "regenerator-runtime": "^0.14.0"
39 | },
40 | "engines": {
41 | "node": ">=6.9.0"
42 | }
43 | },
44 | "node_modules/@esbuild/darwin-arm64": {
45 | "version": "0.19.5",
46 | "cpu": [
47 | "arm64"
48 | ],
49 | "dev": true,
50 | "license": "MIT",
51 | "optional": true,
52 | "os": [
53 | "darwin"
54 | ],
55 | "engines": {
56 | "node": ">=12"
57 | }
58 | },
59 | "node_modules/@lumaai/luma-web": {
60 | "version": "0.2.1",
61 | "resolved": "https://registry.npmjs.org/@lumaai/luma-web/-/luma-web-0.2.1.tgz",
62 | "integrity": "sha512-2m01qEZZgQiCbY8fA+5GLICZwmhvVT17IeR/rXG4xMhbw8RtaQQzqkodZhjIEClZ0CxBcmCvYP8oOEbob+YPdQ==",
63 | "peerDependencies": {
64 | "@types/three": "^0.157.2",
65 | "three": "^0.157.0"
66 | }
67 | },
68 | "node_modules/@mediapipe/tasks-vision": {
69 | "version": "0.10.8",
70 | "license": "Apache-2.0"
71 | },
72 | "node_modules/@react-spring/animated": {
73 | "version": "9.6.1",
74 | "license": "MIT",
75 | "dependencies": {
76 | "@react-spring/shared": "~9.6.1",
77 | "@react-spring/types": "~9.6.1"
78 | },
79 | "peerDependencies": {
80 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
81 | }
82 | },
83 | "node_modules/@react-spring/core": {
84 | "version": "9.6.1",
85 | "license": "MIT",
86 | "dependencies": {
87 | "@react-spring/animated": "~9.6.1",
88 | "@react-spring/rafz": "~9.6.1",
89 | "@react-spring/shared": "~9.6.1",
90 | "@react-spring/types": "~9.6.1"
91 | },
92 | "funding": {
93 | "type": "opencollective",
94 | "url": "https://opencollective.com/react-spring/donate"
95 | },
96 | "peerDependencies": {
97 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
98 | }
99 | },
100 | "node_modules/@react-spring/rafz": {
101 | "version": "9.6.1",
102 | "license": "MIT"
103 | },
104 | "node_modules/@react-spring/shared": {
105 | "version": "9.6.1",
106 | "license": "MIT",
107 | "dependencies": {
108 | "@react-spring/rafz": "~9.6.1",
109 | "@react-spring/types": "~9.6.1"
110 | },
111 | "peerDependencies": {
112 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
113 | }
114 | },
115 | "node_modules/@react-spring/three": {
116 | "version": "9.6.1",
117 | "license": "MIT",
118 | "dependencies": {
119 | "@react-spring/animated": "~9.6.1",
120 | "@react-spring/core": "~9.6.1",
121 | "@react-spring/shared": "~9.6.1",
122 | "@react-spring/types": "~9.6.1"
123 | },
124 | "peerDependencies": {
125 | "@react-three/fiber": ">=6.0",
126 | "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
127 | "three": ">=0.126"
128 | }
129 | },
130 | "node_modules/@react-spring/types": {
131 | "version": "9.6.1",
132 | "license": "MIT"
133 | },
134 | "node_modules/@react-three/drei": {
135 | "version": "9.88.17",
136 | "license": "MIT",
137 | "dependencies": {
138 | "@babel/runtime": "^7.11.2",
139 | "@mediapipe/tasks-vision": "0.10.8",
140 | "@react-spring/three": "~9.6.1",
141 | "@use-gesture/react": "^10.2.24",
142 | "camera-controls": "^2.4.2",
143 | "cross-env": "^7.0.3",
144 | "detect-gpu": "^5.0.28",
145 | "glsl-noise": "^0.0.0",
146 | "lodash.clamp": "^4.0.3",
147 | "lodash.omit": "^4.5.0",
148 | "lodash.pick": "^4.4.0",
149 | "maath": "^0.9.0",
150 | "meshline": "^3.1.6",
151 | "react-composer": "^5.0.3",
152 | "react-merge-refs": "^1.1.0",
153 | "stats-gl": "^1.0.4",
154 | "stats.js": "^0.17.0",
155 | "suspend-react": "^0.1.3",
156 | "three-mesh-bvh": "^0.6.7",
157 | "three-stdlib": "^2.28.0",
158 | "troika-three-text": "^0.47.2",
159 | "utility-types": "^3.10.0",
160 | "uuid": "^9.0.1",
161 | "zustand": "^3.5.13"
162 | },
163 | "peerDependencies": {
164 | "@react-three/fiber": ">=8.0",
165 | "react": ">=18.0",
166 | "react-dom": ">=18.0",
167 | "three": ">=0.137"
168 | },
169 | "peerDependenciesMeta": {
170 | "react-dom": {
171 | "optional": true
172 | }
173 | }
174 | },
175 | "node_modules/@react-three/fiber": {
176 | "version": "8.15.11",
177 | "license": "MIT",
178 | "dependencies": {
179 | "@babel/runtime": "^7.17.8",
180 | "@types/react-reconciler": "^0.26.7",
181 | "@types/webxr": "*",
182 | "base64-js": "^1.5.1",
183 | "buffer": "^6.0.3",
184 | "its-fine": "^1.0.6",
185 | "react-reconciler": "^0.27.0",
186 | "react-use-measure": "^2.1.1",
187 | "scheduler": "^0.21.0",
188 | "suspend-react": "^0.1.3",
189 | "zustand": "^3.7.1"
190 | },
191 | "peerDependencies": {
192 | "expo": ">=43.0",
193 | "expo-asset": ">=8.4",
194 | "expo-file-system": ">=11.0",
195 | "expo-gl": ">=11.0",
196 | "react": ">=18.0",
197 | "react-dom": ">=18.0",
198 | "react-native": ">=0.64",
199 | "three": ">=0.133"
200 | },
201 | "peerDependenciesMeta": {
202 | "expo": {
203 | "optional": true
204 | },
205 | "expo-asset": {
206 | "optional": true
207 | },
208 | "expo-file-system": {
209 | "optional": true
210 | },
211 | "expo-gl": {
212 | "optional": true
213 | },
214 | "react-dom": {
215 | "optional": true
216 | },
217 | "react-native": {
218 | "optional": true
219 | }
220 | }
221 | },
222 | "node_modules/@types/debug": {
223 | "version": "4.1.12",
224 | "license": "MIT",
225 | "dependencies": {
226 | "@types/ms": "*"
227 | }
228 | },
229 | "node_modules/@types/draco3d": {
230 | "version": "1.4.9",
231 | "license": "MIT"
232 | },
233 | "node_modules/@types/hast": {
234 | "version": "3.0.3",
235 | "license": "MIT",
236 | "dependencies": {
237 | "@types/unist": "*"
238 | }
239 | },
240 | "node_modules/@types/mdast": {
241 | "version": "4.0.3",
242 | "license": "MIT",
243 | "dependencies": {
244 | "@types/unist": "*"
245 | }
246 | },
247 | "node_modules/@types/ms": {
248 | "version": "0.7.34",
249 | "license": "MIT"
250 | },
251 | "node_modules/@types/offscreencanvas": {
252 | "version": "2019.7.3",
253 | "license": "MIT"
254 | },
255 | "node_modules/@types/prop-types": {
256 | "version": "15.7.11",
257 | "license": "MIT"
258 | },
259 | "node_modules/@types/react": {
260 | "version": "18.2.38",
261 | "license": "MIT",
262 | "dependencies": {
263 | "@types/prop-types": "*",
264 | "@types/scheduler": "*",
265 | "csstype": "^3.0.2"
266 | }
267 | },
268 | "node_modules/@types/react-dom": {
269 | "version": "18.2.17",
270 | "license": "MIT",
271 | "dependencies": {
272 | "@types/react": "*"
273 | }
274 | },
275 | "node_modules/@types/react-reconciler": {
276 | "version": "0.26.7",
277 | "license": "MIT",
278 | "dependencies": {
279 | "@types/react": "*"
280 | }
281 | },
282 | "node_modules/@types/react-syntax-highlighter": {
283 | "version": "15.5.10",
284 | "license": "MIT",
285 | "dependencies": {
286 | "@types/react": "*"
287 | }
288 | },
289 | "node_modules/@types/scheduler": {
290 | "version": "0.16.8",
291 | "license": "MIT"
292 | },
293 | "node_modules/@types/stats.js": {
294 | "version": "0.17.2",
295 | "license": "MIT"
296 | },
297 | "node_modules/@types/three": {
298 | "version": "0.157.2",
299 | "license": "MIT",
300 | "dependencies": {
301 | "@types/stats.js": "*",
302 | "@types/webxr": "*",
303 | "fflate": "~0.6.10",
304 | "meshoptimizer": "~0.18.1"
305 | }
306 | },
307 | "node_modules/@types/unist": {
308 | "version": "3.0.2",
309 | "license": "MIT"
310 | },
311 | "node_modules/@types/webxr": {
312 | "version": "0.5.6",
313 | "license": "MIT"
314 | },
315 | "node_modules/@ungap/structured-clone": {
316 | "version": "1.2.0",
317 | "license": "ISC"
318 | },
319 | "node_modules/@use-gesture/core": {
320 | "version": "10.3.0",
321 | "license": "MIT"
322 | },
323 | "node_modules/@use-gesture/react": {
324 | "version": "10.3.0",
325 | "license": "MIT",
326 | "dependencies": {
327 | "@use-gesture/core": "10.3.0"
328 | },
329 | "peerDependencies": {
330 | "react": ">= 16.8.0"
331 | }
332 | },
333 | "node_modules/bail": {
334 | "version": "2.0.2",
335 | "license": "MIT",
336 | "funding": {
337 | "type": "github",
338 | "url": "https://github.com/sponsors/wooorm"
339 | }
340 | },
341 | "node_modules/base64-js": {
342 | "version": "1.5.1",
343 | "funding": [
344 | {
345 | "type": "github",
346 | "url": "https://github.com/sponsors/feross"
347 | },
348 | {
349 | "type": "patreon",
350 | "url": "https://www.patreon.com/feross"
351 | },
352 | {
353 | "type": "consulting",
354 | "url": "https://feross.org/support"
355 | }
356 | ],
357 | "license": "MIT"
358 | },
359 | "node_modules/bidi-js": {
360 | "version": "1.0.3",
361 | "license": "MIT",
362 | "dependencies": {
363 | "require-from-string": "^2.0.2"
364 | }
365 | },
366 | "node_modules/buffer": {
367 | "version": "6.0.3",
368 | "funding": [
369 | {
370 | "type": "github",
371 | "url": "https://github.com/sponsors/feross"
372 | },
373 | {
374 | "type": "patreon",
375 | "url": "https://www.patreon.com/feross"
376 | },
377 | {
378 | "type": "consulting",
379 | "url": "https://feross.org/support"
380 | }
381 | ],
382 | "license": "MIT",
383 | "dependencies": {
384 | "base64-js": "^1.3.1",
385 | "ieee754": "^1.2.1"
386 | }
387 | },
388 | "node_modules/camera-controls": {
389 | "version": "2.7.3",
390 | "license": "MIT",
391 | "peerDependencies": {
392 | "three": ">=0.126.1"
393 | }
394 | },
395 | "node_modules/character-entities": {
396 | "version": "2.0.2",
397 | "license": "MIT",
398 | "funding": {
399 | "type": "github",
400 | "url": "https://github.com/sponsors/wooorm"
401 | }
402 | },
403 | "node_modules/character-entities-legacy": {
404 | "version": "1.1.4",
405 | "license": "MIT",
406 | "funding": {
407 | "type": "github",
408 | "url": "https://github.com/sponsors/wooorm"
409 | }
410 | },
411 | "node_modules/character-reference-invalid": {
412 | "version": "1.1.4",
413 | "license": "MIT",
414 | "funding": {
415 | "type": "github",
416 | "url": "https://github.com/sponsors/wooorm"
417 | }
418 | },
419 | "node_modules/comma-separated-tokens": {
420 | "version": "2.0.3",
421 | "license": "MIT",
422 | "funding": {
423 | "type": "github",
424 | "url": "https://github.com/sponsors/wooorm"
425 | }
426 | },
427 | "node_modules/cross-env": {
428 | "version": "7.0.3",
429 | "license": "MIT",
430 | "dependencies": {
431 | "cross-spawn": "^7.0.1"
432 | },
433 | "bin": {
434 | "cross-env": "src/bin/cross-env.js",
435 | "cross-env-shell": "src/bin/cross-env-shell.js"
436 | },
437 | "engines": {
438 | "node": ">=10.14",
439 | "npm": ">=6",
440 | "yarn": ">=1"
441 | }
442 | },
443 | "node_modules/cross-spawn": {
444 | "version": "7.0.3",
445 | "license": "MIT",
446 | "dependencies": {
447 | "path-key": "^3.1.0",
448 | "shebang-command": "^2.0.0",
449 | "which": "^2.0.1"
450 | },
451 | "engines": {
452 | "node": ">= 8"
453 | }
454 | },
455 | "node_modules/csstype": {
456 | "version": "3.1.2",
457 | "license": "MIT"
458 | },
459 | "node_modules/debounce": {
460 | "version": "1.2.1",
461 | "license": "MIT"
462 | },
463 | "node_modules/debug": {
464 | "version": "4.3.4",
465 | "license": "MIT",
466 | "dependencies": {
467 | "ms": "2.1.2"
468 | },
469 | "engines": {
470 | "node": ">=6.0"
471 | },
472 | "peerDependenciesMeta": {
473 | "supports-color": {
474 | "optional": true
475 | }
476 | }
477 | },
478 | "node_modules/decode-named-character-reference": {
479 | "version": "1.0.2",
480 | "license": "MIT",
481 | "dependencies": {
482 | "character-entities": "^2.0.0"
483 | },
484 | "funding": {
485 | "type": "github",
486 | "url": "https://github.com/sponsors/wooorm"
487 | }
488 | },
489 | "node_modules/dequal": {
490 | "version": "2.0.3",
491 | "license": "MIT",
492 | "engines": {
493 | "node": ">=6"
494 | }
495 | },
496 | "node_modules/detect-gpu": {
497 | "version": "5.0.37",
498 | "license": "MIT",
499 | "dependencies": {
500 | "webgl-constants": "^1.1.1"
501 | }
502 | },
503 | "node_modules/devlop": {
504 | "version": "1.1.0",
505 | "license": "MIT",
506 | "dependencies": {
507 | "dequal": "^2.0.0"
508 | },
509 | "funding": {
510 | "type": "github",
511 | "url": "https://github.com/sponsors/wooorm"
512 | }
513 | },
514 | "node_modules/draco3d": {
515 | "version": "1.5.6",
516 | "license": "Apache-2.0"
517 | },
518 | "node_modules/esbuild": {
519 | "version": "0.19.5",
520 | "dev": true,
521 | "hasInstallScript": true,
522 | "license": "MIT",
523 | "bin": {
524 | "esbuild": "bin/esbuild"
525 | },
526 | "engines": {
527 | "node": ">=12"
528 | },
529 | "optionalDependencies": {
530 | "@esbuild/android-arm": "0.19.5",
531 | "@esbuild/android-arm64": "0.19.5",
532 | "@esbuild/android-x64": "0.19.5",
533 | "@esbuild/darwin-arm64": "0.19.5",
534 | "@esbuild/darwin-x64": "0.19.5",
535 | "@esbuild/freebsd-arm64": "0.19.5",
536 | "@esbuild/freebsd-x64": "0.19.5",
537 | "@esbuild/linux-arm": "0.19.5",
538 | "@esbuild/linux-arm64": "0.19.5",
539 | "@esbuild/linux-ia32": "0.19.5",
540 | "@esbuild/linux-loong64": "0.19.5",
541 | "@esbuild/linux-mips64el": "0.19.5",
542 | "@esbuild/linux-ppc64": "0.19.5",
543 | "@esbuild/linux-riscv64": "0.19.5",
544 | "@esbuild/linux-s390x": "0.19.5",
545 | "@esbuild/linux-x64": "0.19.5",
546 | "@esbuild/netbsd-x64": "0.19.5",
547 | "@esbuild/openbsd-x64": "0.19.5",
548 | "@esbuild/sunos-x64": "0.19.5",
549 | "@esbuild/win32-arm64": "0.19.5",
550 | "@esbuild/win32-ia32": "0.19.5",
551 | "@esbuild/win32-x64": "0.19.5"
552 | }
553 | },
554 | "node_modules/extend": {
555 | "version": "3.0.2",
556 | "license": "MIT"
557 | },
558 | "node_modules/fault": {
559 | "version": "1.0.4",
560 | "license": "MIT",
561 | "dependencies": {
562 | "format": "^0.2.0"
563 | },
564 | "funding": {
565 | "type": "github",
566 | "url": "https://github.com/sponsors/wooorm"
567 | }
568 | },
569 | "node_modules/fflate": {
570 | "version": "0.6.10",
571 | "license": "MIT"
572 | },
573 | "node_modules/format": {
574 | "version": "0.2.2",
575 | "engines": {
576 | "node": ">=0.4.x"
577 | }
578 | },
579 | "node_modules/glsl-noise": {
580 | "version": "0.0.0",
581 | "license": "MIT"
582 | },
583 | "node_modules/hast-util-parse-selector": {
584 | "version": "2.2.5",
585 | "license": "MIT",
586 | "funding": {
587 | "type": "opencollective",
588 | "url": "https://opencollective.com/unified"
589 | }
590 | },
591 | "node_modules/hast-util-to-jsx-runtime": {
592 | "version": "2.2.0",
593 | "license": "MIT",
594 | "dependencies": {
595 | "@types/hast": "^3.0.0",
596 | "@types/unist": "^3.0.0",
597 | "comma-separated-tokens": "^2.0.0",
598 | "hast-util-whitespace": "^3.0.0",
599 | "property-information": "^6.0.0",
600 | "space-separated-tokens": "^2.0.0",
601 | "style-to-object": "^0.4.0",
602 | "unist-util-position": "^5.0.0",
603 | "vfile-message": "^4.0.0"
604 | },
605 | "funding": {
606 | "type": "opencollective",
607 | "url": "https://opencollective.com/unified"
608 | }
609 | },
610 | "node_modules/hast-util-whitespace": {
611 | "version": "3.0.0",
612 | "license": "MIT",
613 | "dependencies": {
614 | "@types/hast": "^3.0.0"
615 | },
616 | "funding": {
617 | "type": "opencollective",
618 | "url": "https://opencollective.com/unified"
619 | }
620 | },
621 | "node_modules/hastscript": {
622 | "version": "6.0.0",
623 | "license": "MIT",
624 | "dependencies": {
625 | "@types/hast": "^2.0.0",
626 | "comma-separated-tokens": "^1.0.0",
627 | "hast-util-parse-selector": "^2.0.0",
628 | "property-information": "^5.0.0",
629 | "space-separated-tokens": "^1.0.0"
630 | },
631 | "funding": {
632 | "type": "opencollective",
633 | "url": "https://opencollective.com/unified"
634 | }
635 | },
636 | "node_modules/hastscript/node_modules/@types/hast": {
637 | "version": "2.3.8",
638 | "license": "MIT",
639 | "dependencies": {
640 | "@types/unist": "^2"
641 | }
642 | },
643 | "node_modules/hastscript/node_modules/@types/unist": {
644 | "version": "2.0.10",
645 | "license": "MIT"
646 | },
647 | "node_modules/hastscript/node_modules/comma-separated-tokens": {
648 | "version": "1.0.8",
649 | "license": "MIT",
650 | "funding": {
651 | "type": "github",
652 | "url": "https://github.com/sponsors/wooorm"
653 | }
654 | },
655 | "node_modules/hastscript/node_modules/property-information": {
656 | "version": "5.6.0",
657 | "license": "MIT",
658 | "dependencies": {
659 | "xtend": "^4.0.0"
660 | },
661 | "funding": {
662 | "type": "github",
663 | "url": "https://github.com/sponsors/wooorm"
664 | }
665 | },
666 | "node_modules/hastscript/node_modules/space-separated-tokens": {
667 | "version": "1.1.5",
668 | "license": "MIT",
669 | "funding": {
670 | "type": "github",
671 | "url": "https://github.com/sponsors/wooorm"
672 | }
673 | },
674 | "node_modules/highlight.js": {
675 | "version": "10.7.3",
676 | "license": "BSD-3-Clause",
677 | "engines": {
678 | "node": "*"
679 | }
680 | },
681 | "node_modules/html-url-attributes": {
682 | "version": "3.0.0",
683 | "license": "MIT",
684 | "funding": {
685 | "type": "opencollective",
686 | "url": "https://opencollective.com/unified"
687 | }
688 | },
689 | "node_modules/ieee754": {
690 | "version": "1.2.1",
691 | "funding": [
692 | {
693 | "type": "github",
694 | "url": "https://github.com/sponsors/feross"
695 | },
696 | {
697 | "type": "patreon",
698 | "url": "https://www.patreon.com/feross"
699 | },
700 | {
701 | "type": "consulting",
702 | "url": "https://feross.org/support"
703 | }
704 | ],
705 | "license": "BSD-3-Clause"
706 | },
707 | "node_modules/inline-style-parser": {
708 | "version": "0.1.1",
709 | "license": "MIT"
710 | },
711 | "node_modules/is-alphabetical": {
712 | "version": "1.0.4",
713 | "license": "MIT",
714 | "funding": {
715 | "type": "github",
716 | "url": "https://github.com/sponsors/wooorm"
717 | }
718 | },
719 | "node_modules/is-alphanumerical": {
720 | "version": "1.0.4",
721 | "license": "MIT",
722 | "dependencies": {
723 | "is-alphabetical": "^1.0.0",
724 | "is-decimal": "^1.0.0"
725 | },
726 | "funding": {
727 | "type": "github",
728 | "url": "https://github.com/sponsors/wooorm"
729 | }
730 | },
731 | "node_modules/is-decimal": {
732 | "version": "1.0.4",
733 | "license": "MIT",
734 | "funding": {
735 | "type": "github",
736 | "url": "https://github.com/sponsors/wooorm"
737 | }
738 | },
739 | "node_modules/is-hexadecimal": {
740 | "version": "1.0.4",
741 | "license": "MIT",
742 | "funding": {
743 | "type": "github",
744 | "url": "https://github.com/sponsors/wooorm"
745 | }
746 | },
747 | "node_modules/is-plain-obj": {
748 | "version": "4.1.0",
749 | "license": "MIT",
750 | "engines": {
751 | "node": ">=12"
752 | },
753 | "funding": {
754 | "url": "https://github.com/sponsors/sindresorhus"
755 | }
756 | },
757 | "node_modules/isexe": {
758 | "version": "2.0.0",
759 | "license": "ISC"
760 | },
761 | "node_modules/its-fine": {
762 | "version": "1.1.1",
763 | "license": "MIT",
764 | "dependencies": {
765 | "@types/react-reconciler": "^0.28.0"
766 | },
767 | "peerDependencies": {
768 | "react": ">=18.0"
769 | }
770 | },
771 | "node_modules/its-fine/node_modules/@types/react-reconciler": {
772 | "version": "0.28.8",
773 | "license": "MIT",
774 | "dependencies": {
775 | "@types/react": "*"
776 | }
777 | },
778 | "node_modules/js-tokens": {
779 | "version": "4.0.0",
780 | "license": "MIT"
781 | },
782 | "node_modules/lil-gui": {
783 | "version": "0.19.1",
784 | "license": "MIT"
785 | },
786 | "node_modules/lodash.clamp": {
787 | "version": "4.0.3",
788 | "license": "MIT"
789 | },
790 | "node_modules/lodash.omit": {
791 | "version": "4.5.0",
792 | "license": "MIT"
793 | },
794 | "node_modules/lodash.pick": {
795 | "version": "4.4.0",
796 | "license": "MIT"
797 | },
798 | "node_modules/loose-envify": {
799 | "version": "1.4.0",
800 | "license": "MIT",
801 | "dependencies": {
802 | "js-tokens": "^3.0.0 || ^4.0.0"
803 | },
804 | "bin": {
805 | "loose-envify": "cli.js"
806 | }
807 | },
808 | "node_modules/lowlight": {
809 | "version": "1.20.0",
810 | "license": "MIT",
811 | "dependencies": {
812 | "fault": "^1.0.0",
813 | "highlight.js": "~10.7.0"
814 | },
815 | "funding": {
816 | "type": "github",
817 | "url": "https://github.com/sponsors/wooorm"
818 | }
819 | },
820 | "node_modules/maath": {
821 | "version": "0.9.0",
822 | "license": "MIT",
823 | "peerDependencies": {
824 | "@types/three": ">=0.144.0",
825 | "three": ">=0.144.0"
826 | }
827 | },
828 | "node_modules/mdast-util-from-markdown": {
829 | "version": "2.0.0",
830 | "license": "MIT",
831 | "dependencies": {
832 | "@types/mdast": "^4.0.0",
833 | "@types/unist": "^3.0.0",
834 | "decode-named-character-reference": "^1.0.0",
835 | "devlop": "^1.0.0",
836 | "mdast-util-to-string": "^4.0.0",
837 | "micromark": "^4.0.0",
838 | "micromark-util-decode-numeric-character-reference": "^2.0.0",
839 | "micromark-util-decode-string": "^2.0.0",
840 | "micromark-util-normalize-identifier": "^2.0.0",
841 | "micromark-util-symbol": "^2.0.0",
842 | "micromark-util-types": "^2.0.0",
843 | "unist-util-stringify-position": "^4.0.0"
844 | },
845 | "funding": {
846 | "type": "opencollective",
847 | "url": "https://opencollective.com/unified"
848 | }
849 | },
850 | "node_modules/mdast-util-to-hast": {
851 | "version": "13.0.2",
852 | "license": "MIT",
853 | "dependencies": {
854 | "@types/hast": "^3.0.0",
855 | "@types/mdast": "^4.0.0",
856 | "@ungap/structured-clone": "^1.0.0",
857 | "devlop": "^1.0.0",
858 | "micromark-util-sanitize-uri": "^2.0.0",
859 | "trim-lines": "^3.0.0",
860 | "unist-util-position": "^5.0.0",
861 | "unist-util-visit": "^5.0.0"
862 | },
863 | "funding": {
864 | "type": "opencollective",
865 | "url": "https://opencollective.com/unified"
866 | }
867 | },
868 | "node_modules/mdast-util-to-string": {
869 | "version": "4.0.0",
870 | "license": "MIT",
871 | "dependencies": {
872 | "@types/mdast": "^4.0.0"
873 | },
874 | "funding": {
875 | "type": "opencollective",
876 | "url": "https://opencollective.com/unified"
877 | }
878 | },
879 | "node_modules/meshline": {
880 | "version": "3.1.7",
881 | "license": "MIT",
882 | "peerDependencies": {
883 | "three": ">=0.137"
884 | }
885 | },
886 | "node_modules/meshoptimizer": {
887 | "version": "0.18.1",
888 | "license": "MIT"
889 | },
890 | "node_modules/micromark": {
891 | "version": "4.0.0",
892 | "funding": [
893 | {
894 | "type": "GitHub Sponsors",
895 | "url": "https://github.com/sponsors/unifiedjs"
896 | },
897 | {
898 | "type": "OpenCollective",
899 | "url": "https://opencollective.com/unified"
900 | }
901 | ],
902 | "license": "MIT",
903 | "dependencies": {
904 | "@types/debug": "^4.0.0",
905 | "debug": "^4.0.0",
906 | "decode-named-character-reference": "^1.0.0",
907 | "devlop": "^1.0.0",
908 | "micromark-core-commonmark": "^2.0.0",
909 | "micromark-factory-space": "^2.0.0",
910 | "micromark-util-character": "^2.0.0",
911 | "micromark-util-chunked": "^2.0.0",
912 | "micromark-util-combine-extensions": "^2.0.0",
913 | "micromark-util-decode-numeric-character-reference": "^2.0.0",
914 | "micromark-util-encode": "^2.0.0",
915 | "micromark-util-normalize-identifier": "^2.0.0",
916 | "micromark-util-resolve-all": "^2.0.0",
917 | "micromark-util-sanitize-uri": "^2.0.0",
918 | "micromark-util-subtokenize": "^2.0.0",
919 | "micromark-util-symbol": "^2.0.0",
920 | "micromark-util-types": "^2.0.0"
921 | }
922 | },
923 | "node_modules/micromark-core-commonmark": {
924 | "version": "2.0.0",
925 | "funding": [
926 | {
927 | "type": "GitHub Sponsors",
928 | "url": "https://github.com/sponsors/unifiedjs"
929 | },
930 | {
931 | "type": "OpenCollective",
932 | "url": "https://opencollective.com/unified"
933 | }
934 | ],
935 | "license": "MIT",
936 | "dependencies": {
937 | "decode-named-character-reference": "^1.0.0",
938 | "devlop": "^1.0.0",
939 | "micromark-factory-destination": "^2.0.0",
940 | "micromark-factory-label": "^2.0.0",
941 | "micromark-factory-space": "^2.0.0",
942 | "micromark-factory-title": "^2.0.0",
943 | "micromark-factory-whitespace": "^2.0.0",
944 | "micromark-util-character": "^2.0.0",
945 | "micromark-util-chunked": "^2.0.0",
946 | "micromark-util-classify-character": "^2.0.0",
947 | "micromark-util-html-tag-name": "^2.0.0",
948 | "micromark-util-normalize-identifier": "^2.0.0",
949 | "micromark-util-resolve-all": "^2.0.0",
950 | "micromark-util-subtokenize": "^2.0.0",
951 | "micromark-util-symbol": "^2.0.0",
952 | "micromark-util-types": "^2.0.0"
953 | }
954 | },
955 | "node_modules/micromark-factory-destination": {
956 | "version": "2.0.0",
957 | "funding": [
958 | {
959 | "type": "GitHub Sponsors",
960 | "url": "https://github.com/sponsors/unifiedjs"
961 | },
962 | {
963 | "type": "OpenCollective",
964 | "url": "https://opencollective.com/unified"
965 | }
966 | ],
967 | "license": "MIT",
968 | "dependencies": {
969 | "micromark-util-character": "^2.0.0",
970 | "micromark-util-symbol": "^2.0.0",
971 | "micromark-util-types": "^2.0.0"
972 | }
973 | },
974 | "node_modules/micromark-factory-label": {
975 | "version": "2.0.0",
976 | "funding": [
977 | {
978 | "type": "GitHub Sponsors",
979 | "url": "https://github.com/sponsors/unifiedjs"
980 | },
981 | {
982 | "type": "OpenCollective",
983 | "url": "https://opencollective.com/unified"
984 | }
985 | ],
986 | "license": "MIT",
987 | "dependencies": {
988 | "devlop": "^1.0.0",
989 | "micromark-util-character": "^2.0.0",
990 | "micromark-util-symbol": "^2.0.0",
991 | "micromark-util-types": "^2.0.0"
992 | }
993 | },
994 | "node_modules/micromark-factory-space": {
995 | "version": "2.0.0",
996 | "funding": [
997 | {
998 | "type": "GitHub Sponsors",
999 | "url": "https://github.com/sponsors/unifiedjs"
1000 | },
1001 | {
1002 | "type": "OpenCollective",
1003 | "url": "https://opencollective.com/unified"
1004 | }
1005 | ],
1006 | "license": "MIT",
1007 | "dependencies": {
1008 | "micromark-util-character": "^2.0.0",
1009 | "micromark-util-types": "^2.0.0"
1010 | }
1011 | },
1012 | "node_modules/micromark-factory-title": {
1013 | "version": "2.0.0",
1014 | "funding": [
1015 | {
1016 | "type": "GitHub Sponsors",
1017 | "url": "https://github.com/sponsors/unifiedjs"
1018 | },
1019 | {
1020 | "type": "OpenCollective",
1021 | "url": "https://opencollective.com/unified"
1022 | }
1023 | ],
1024 | "license": "MIT",
1025 | "dependencies": {
1026 | "micromark-factory-space": "^2.0.0",
1027 | "micromark-util-character": "^2.0.0",
1028 | "micromark-util-symbol": "^2.0.0",
1029 | "micromark-util-types": "^2.0.0"
1030 | }
1031 | },
1032 | "node_modules/micromark-factory-whitespace": {
1033 | "version": "2.0.0",
1034 | "funding": [
1035 | {
1036 | "type": "GitHub Sponsors",
1037 | "url": "https://github.com/sponsors/unifiedjs"
1038 | },
1039 | {
1040 | "type": "OpenCollective",
1041 | "url": "https://opencollective.com/unified"
1042 | }
1043 | ],
1044 | "license": "MIT",
1045 | "dependencies": {
1046 | "micromark-factory-space": "^2.0.0",
1047 | "micromark-util-character": "^2.0.0",
1048 | "micromark-util-symbol": "^2.0.0",
1049 | "micromark-util-types": "^2.0.0"
1050 | }
1051 | },
1052 | "node_modules/micromark-util-character": {
1053 | "version": "2.0.1",
1054 | "funding": [
1055 | {
1056 | "type": "GitHub Sponsors",
1057 | "url": "https://github.com/sponsors/unifiedjs"
1058 | },
1059 | {
1060 | "type": "OpenCollective",
1061 | "url": "https://opencollective.com/unified"
1062 | }
1063 | ],
1064 | "license": "MIT",
1065 | "dependencies": {
1066 | "micromark-util-symbol": "^2.0.0",
1067 | "micromark-util-types": "^2.0.0"
1068 | }
1069 | },
1070 | "node_modules/micromark-util-chunked": {
1071 | "version": "2.0.0",
1072 | "funding": [
1073 | {
1074 | "type": "GitHub Sponsors",
1075 | "url": "https://github.com/sponsors/unifiedjs"
1076 | },
1077 | {
1078 | "type": "OpenCollective",
1079 | "url": "https://opencollective.com/unified"
1080 | }
1081 | ],
1082 | "license": "MIT",
1083 | "dependencies": {
1084 | "micromark-util-symbol": "^2.0.0"
1085 | }
1086 | },
1087 | "node_modules/micromark-util-classify-character": {
1088 | "version": "2.0.0",
1089 | "funding": [
1090 | {
1091 | "type": "GitHub Sponsors",
1092 | "url": "https://github.com/sponsors/unifiedjs"
1093 | },
1094 | {
1095 | "type": "OpenCollective",
1096 | "url": "https://opencollective.com/unified"
1097 | }
1098 | ],
1099 | "license": "MIT",
1100 | "dependencies": {
1101 | "micromark-util-character": "^2.0.0",
1102 | "micromark-util-symbol": "^2.0.0",
1103 | "micromark-util-types": "^2.0.0"
1104 | }
1105 | },
1106 | "node_modules/micromark-util-combine-extensions": {
1107 | "version": "2.0.0",
1108 | "funding": [
1109 | {
1110 | "type": "GitHub Sponsors",
1111 | "url": "https://github.com/sponsors/unifiedjs"
1112 | },
1113 | {
1114 | "type": "OpenCollective",
1115 | "url": "https://opencollective.com/unified"
1116 | }
1117 | ],
1118 | "license": "MIT",
1119 | "dependencies": {
1120 | "micromark-util-chunked": "^2.0.0",
1121 | "micromark-util-types": "^2.0.0"
1122 | }
1123 | },
1124 | "node_modules/micromark-util-decode-numeric-character-reference": {
1125 | "version": "2.0.1",
1126 | "funding": [
1127 | {
1128 | "type": "GitHub Sponsors",
1129 | "url": "https://github.com/sponsors/unifiedjs"
1130 | },
1131 | {
1132 | "type": "OpenCollective",
1133 | "url": "https://opencollective.com/unified"
1134 | }
1135 | ],
1136 | "license": "MIT",
1137 | "dependencies": {
1138 | "micromark-util-symbol": "^2.0.0"
1139 | }
1140 | },
1141 | "node_modules/micromark-util-decode-string": {
1142 | "version": "2.0.0",
1143 | "funding": [
1144 | {
1145 | "type": "GitHub Sponsors",
1146 | "url": "https://github.com/sponsors/unifiedjs"
1147 | },
1148 | {
1149 | "type": "OpenCollective",
1150 | "url": "https://opencollective.com/unified"
1151 | }
1152 | ],
1153 | "license": "MIT",
1154 | "dependencies": {
1155 | "decode-named-character-reference": "^1.0.0",
1156 | "micromark-util-character": "^2.0.0",
1157 | "micromark-util-decode-numeric-character-reference": "^2.0.0",
1158 | "micromark-util-symbol": "^2.0.0"
1159 | }
1160 | },
1161 | "node_modules/micromark-util-encode": {
1162 | "version": "2.0.0",
1163 | "funding": [
1164 | {
1165 | "type": "GitHub Sponsors",
1166 | "url": "https://github.com/sponsors/unifiedjs"
1167 | },
1168 | {
1169 | "type": "OpenCollective",
1170 | "url": "https://opencollective.com/unified"
1171 | }
1172 | ],
1173 | "license": "MIT"
1174 | },
1175 | "node_modules/micromark-util-html-tag-name": {
1176 | "version": "2.0.0",
1177 | "funding": [
1178 | {
1179 | "type": "GitHub Sponsors",
1180 | "url": "https://github.com/sponsors/unifiedjs"
1181 | },
1182 | {
1183 | "type": "OpenCollective",
1184 | "url": "https://opencollective.com/unified"
1185 | }
1186 | ],
1187 | "license": "MIT"
1188 | },
1189 | "node_modules/micromark-util-normalize-identifier": {
1190 | "version": "2.0.0",
1191 | "funding": [
1192 | {
1193 | "type": "GitHub Sponsors",
1194 | "url": "https://github.com/sponsors/unifiedjs"
1195 | },
1196 | {
1197 | "type": "OpenCollective",
1198 | "url": "https://opencollective.com/unified"
1199 | }
1200 | ],
1201 | "license": "MIT",
1202 | "dependencies": {
1203 | "micromark-util-symbol": "^2.0.0"
1204 | }
1205 | },
1206 | "node_modules/micromark-util-resolve-all": {
1207 | "version": "2.0.0",
1208 | "funding": [
1209 | {
1210 | "type": "GitHub Sponsors",
1211 | "url": "https://github.com/sponsors/unifiedjs"
1212 | },
1213 | {
1214 | "type": "OpenCollective",
1215 | "url": "https://opencollective.com/unified"
1216 | }
1217 | ],
1218 | "license": "MIT",
1219 | "dependencies": {
1220 | "micromark-util-types": "^2.0.0"
1221 | }
1222 | },
1223 | "node_modules/micromark-util-sanitize-uri": {
1224 | "version": "2.0.0",
1225 | "funding": [
1226 | {
1227 | "type": "GitHub Sponsors",
1228 | "url": "https://github.com/sponsors/unifiedjs"
1229 | },
1230 | {
1231 | "type": "OpenCollective",
1232 | "url": "https://opencollective.com/unified"
1233 | }
1234 | ],
1235 | "license": "MIT",
1236 | "dependencies": {
1237 | "micromark-util-character": "^2.0.0",
1238 | "micromark-util-encode": "^2.0.0",
1239 | "micromark-util-symbol": "^2.0.0"
1240 | }
1241 | },
1242 | "node_modules/micromark-util-subtokenize": {
1243 | "version": "2.0.0",
1244 | "funding": [
1245 | {
1246 | "type": "GitHub Sponsors",
1247 | "url": "https://github.com/sponsors/unifiedjs"
1248 | },
1249 | {
1250 | "type": "OpenCollective",
1251 | "url": "https://opencollective.com/unified"
1252 | }
1253 | ],
1254 | "license": "MIT",
1255 | "dependencies": {
1256 | "devlop": "^1.0.0",
1257 | "micromark-util-chunked": "^2.0.0",
1258 | "micromark-util-symbol": "^2.0.0",
1259 | "micromark-util-types": "^2.0.0"
1260 | }
1261 | },
1262 | "node_modules/micromark-util-symbol": {
1263 | "version": "2.0.0",
1264 | "funding": [
1265 | {
1266 | "type": "GitHub Sponsors",
1267 | "url": "https://github.com/sponsors/unifiedjs"
1268 | },
1269 | {
1270 | "type": "OpenCollective",
1271 | "url": "https://opencollective.com/unified"
1272 | }
1273 | ],
1274 | "license": "MIT"
1275 | },
1276 | "node_modules/micromark-util-types": {
1277 | "version": "2.0.0",
1278 | "funding": [
1279 | {
1280 | "type": "GitHub Sponsors",
1281 | "url": "https://github.com/sponsors/unifiedjs"
1282 | },
1283 | {
1284 | "type": "OpenCollective",
1285 | "url": "https://opencollective.com/unified"
1286 | }
1287 | ],
1288 | "license": "MIT"
1289 | },
1290 | "node_modules/ms": {
1291 | "version": "2.1.2",
1292 | "license": "MIT"
1293 | },
1294 | "node_modules/object-assign": {
1295 | "version": "4.1.1",
1296 | "license": "MIT",
1297 | "engines": {
1298 | "node": ">=0.10.0"
1299 | }
1300 | },
1301 | "node_modules/parse-entities": {
1302 | "version": "2.0.0",
1303 | "license": "MIT",
1304 | "dependencies": {
1305 | "character-entities": "^1.0.0",
1306 | "character-entities-legacy": "^1.0.0",
1307 | "character-reference-invalid": "^1.0.0",
1308 | "is-alphanumerical": "^1.0.0",
1309 | "is-decimal": "^1.0.0",
1310 | "is-hexadecimal": "^1.0.0"
1311 | },
1312 | "funding": {
1313 | "type": "github",
1314 | "url": "https://github.com/sponsors/wooorm"
1315 | }
1316 | },
1317 | "node_modules/parse-entities/node_modules/character-entities": {
1318 | "version": "1.2.4",
1319 | "license": "MIT",
1320 | "funding": {
1321 | "type": "github",
1322 | "url": "https://github.com/sponsors/wooorm"
1323 | }
1324 | },
1325 | "node_modules/path-key": {
1326 | "version": "3.1.1",
1327 | "license": "MIT",
1328 | "engines": {
1329 | "node": ">=8"
1330 | }
1331 | },
1332 | "node_modules/potpack": {
1333 | "version": "1.0.2",
1334 | "license": "ISC"
1335 | },
1336 | "node_modules/prismjs": {
1337 | "version": "1.29.0",
1338 | "license": "MIT",
1339 | "engines": {
1340 | "node": ">=6"
1341 | }
1342 | },
1343 | "node_modules/prop-types": {
1344 | "version": "15.8.1",
1345 | "license": "MIT",
1346 | "dependencies": {
1347 | "loose-envify": "^1.4.0",
1348 | "object-assign": "^4.1.1",
1349 | "react-is": "^16.13.1"
1350 | }
1351 | },
1352 | "node_modules/property-information": {
1353 | "version": "6.4.0",
1354 | "license": "MIT",
1355 | "funding": {
1356 | "type": "github",
1357 | "url": "https://github.com/sponsors/wooorm"
1358 | }
1359 | },
1360 | "node_modules/react": {
1361 | "version": "18.2.0",
1362 | "license": "MIT",
1363 | "dependencies": {
1364 | "loose-envify": "^1.1.0"
1365 | },
1366 | "engines": {
1367 | "node": ">=0.10.0"
1368 | }
1369 | },
1370 | "node_modules/react-composer": {
1371 | "version": "5.0.3",
1372 | "license": "MIT",
1373 | "dependencies": {
1374 | "prop-types": "^15.6.0"
1375 | },
1376 | "peerDependencies": {
1377 | "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
1378 | }
1379 | },
1380 | "node_modules/react-dom": {
1381 | "version": "18.2.0",
1382 | "license": "MIT",
1383 | "dependencies": {
1384 | "loose-envify": "^1.1.0",
1385 | "scheduler": "^0.23.0"
1386 | },
1387 | "peerDependencies": {
1388 | "react": "^18.2.0"
1389 | }
1390 | },
1391 | "node_modules/react-dom/node_modules/scheduler": {
1392 | "version": "0.23.0",
1393 | "license": "MIT",
1394 | "dependencies": {
1395 | "loose-envify": "^1.1.0"
1396 | }
1397 | },
1398 | "node_modules/react-is": {
1399 | "version": "16.13.1",
1400 | "license": "MIT"
1401 | },
1402 | "node_modules/react-markdown": {
1403 | "version": "9.0.1",
1404 | "license": "MIT",
1405 | "dependencies": {
1406 | "@types/hast": "^3.0.0",
1407 | "devlop": "^1.0.0",
1408 | "hast-util-to-jsx-runtime": "^2.0.0",
1409 | "html-url-attributes": "^3.0.0",
1410 | "mdast-util-to-hast": "^13.0.0",
1411 | "remark-parse": "^11.0.0",
1412 | "remark-rehype": "^11.0.0",
1413 | "unified": "^11.0.0",
1414 | "unist-util-visit": "^5.0.0",
1415 | "vfile": "^6.0.0"
1416 | },
1417 | "funding": {
1418 | "type": "opencollective",
1419 | "url": "https://opencollective.com/unified"
1420 | },
1421 | "peerDependencies": {
1422 | "@types/react": ">=18",
1423 | "react": ">=18"
1424 | }
1425 | },
1426 | "node_modules/react-merge-refs": {
1427 | "version": "1.1.0",
1428 | "license": "MIT",
1429 | "funding": {
1430 | "type": "github",
1431 | "url": "https://github.com/sponsors/gregberge"
1432 | }
1433 | },
1434 | "node_modules/react-reconciler": {
1435 | "version": "0.27.0",
1436 | "license": "MIT",
1437 | "dependencies": {
1438 | "loose-envify": "^1.1.0",
1439 | "scheduler": "^0.21.0"
1440 | },
1441 | "engines": {
1442 | "node": ">=0.10.0"
1443 | },
1444 | "peerDependencies": {
1445 | "react": "^18.0.0"
1446 | }
1447 | },
1448 | "node_modules/react-syntax-highlighter": {
1449 | "version": "15.5.0",
1450 | "license": "MIT",
1451 | "dependencies": {
1452 | "@babel/runtime": "^7.3.1",
1453 | "highlight.js": "^10.4.1",
1454 | "lowlight": "^1.17.0",
1455 | "prismjs": "^1.27.0",
1456 | "refractor": "^3.6.0"
1457 | },
1458 | "peerDependencies": {
1459 | "react": ">= 0.14.0"
1460 | }
1461 | },
1462 | "node_modules/react-use-measure": {
1463 | "version": "2.1.1",
1464 | "license": "MIT",
1465 | "dependencies": {
1466 | "debounce": "^1.2.1"
1467 | },
1468 | "peerDependencies": {
1469 | "react": ">=16.13",
1470 | "react-dom": ">=16.13"
1471 | }
1472 | },
1473 | "node_modules/refractor": {
1474 | "version": "3.6.0",
1475 | "license": "MIT",
1476 | "dependencies": {
1477 | "hastscript": "^6.0.0",
1478 | "parse-entities": "^2.0.0",
1479 | "prismjs": "~1.27.0"
1480 | },
1481 | "funding": {
1482 | "type": "github",
1483 | "url": "https://github.com/sponsors/wooorm"
1484 | }
1485 | },
1486 | "node_modules/refractor/node_modules/prismjs": {
1487 | "version": "1.27.0",
1488 | "license": "MIT",
1489 | "engines": {
1490 | "node": ">=6"
1491 | }
1492 | },
1493 | "node_modules/regenerator-runtime": {
1494 | "version": "0.14.0",
1495 | "license": "MIT"
1496 | },
1497 | "node_modules/remark-parse": {
1498 | "version": "11.0.0",
1499 | "license": "MIT",
1500 | "dependencies": {
1501 | "@types/mdast": "^4.0.0",
1502 | "mdast-util-from-markdown": "^2.0.0",
1503 | "micromark-util-types": "^2.0.0",
1504 | "unified": "^11.0.0"
1505 | },
1506 | "funding": {
1507 | "type": "opencollective",
1508 | "url": "https://opencollective.com/unified"
1509 | }
1510 | },
1511 | "node_modules/remark-rehype": {
1512 | "version": "11.0.0",
1513 | "license": "MIT",
1514 | "dependencies": {
1515 | "@types/hast": "^3.0.0",
1516 | "@types/mdast": "^4.0.0",
1517 | "mdast-util-to-hast": "^13.0.0",
1518 | "unified": "^11.0.0",
1519 | "vfile": "^6.0.0"
1520 | },
1521 | "funding": {
1522 | "type": "opencollective",
1523 | "url": "https://opencollective.com/unified"
1524 | }
1525 | },
1526 | "node_modules/require-from-string": {
1527 | "version": "2.0.2",
1528 | "license": "MIT",
1529 | "engines": {
1530 | "node": ">=0.10.0"
1531 | }
1532 | },
1533 | "node_modules/scheduler": {
1534 | "version": "0.21.0",
1535 | "license": "MIT",
1536 | "dependencies": {
1537 | "loose-envify": "^1.1.0"
1538 | }
1539 | },
1540 | "node_modules/shebang-command": {
1541 | "version": "2.0.0",
1542 | "license": "MIT",
1543 | "dependencies": {
1544 | "shebang-regex": "^3.0.0"
1545 | },
1546 | "engines": {
1547 | "node": ">=8"
1548 | }
1549 | },
1550 | "node_modules/shebang-regex": {
1551 | "version": "3.0.0",
1552 | "license": "MIT",
1553 | "engines": {
1554 | "node": ">=8"
1555 | }
1556 | },
1557 | "node_modules/space-separated-tokens": {
1558 | "version": "2.0.2",
1559 | "license": "MIT",
1560 | "funding": {
1561 | "type": "github",
1562 | "url": "https://github.com/sponsors/wooorm"
1563 | }
1564 | },
1565 | "node_modules/stats-gl": {
1566 | "version": "1.0.7",
1567 | "license": "MIT"
1568 | },
1569 | "node_modules/stats.js": {
1570 | "version": "0.17.0",
1571 | "license": "MIT"
1572 | },
1573 | "node_modules/style-to-object": {
1574 | "version": "0.4.4",
1575 | "license": "MIT",
1576 | "dependencies": {
1577 | "inline-style-parser": "0.1.1"
1578 | }
1579 | },
1580 | "node_modules/suspend-react": {
1581 | "version": "0.1.3",
1582 | "license": "MIT",
1583 | "peerDependencies": {
1584 | "react": ">=17.0"
1585 | }
1586 | },
1587 | "node_modules/three": {
1588 | "version": "0.157.0",
1589 | "license": "MIT"
1590 | },
1591 | "node_modules/three-mesh-bvh": {
1592 | "version": "0.6.8",
1593 | "license": "MIT",
1594 | "peerDependencies": {
1595 | "three": ">= 0.151.0"
1596 | }
1597 | },
1598 | "node_modules/three-stdlib": {
1599 | "version": "2.28.7",
1600 | "license": "MIT",
1601 | "dependencies": {
1602 | "@types/draco3d": "^1.4.0",
1603 | "@types/offscreencanvas": "^2019.6.4",
1604 | "@types/webxr": "^0.5.2",
1605 | "draco3d": "^1.4.1",
1606 | "fflate": "^0.6.9",
1607 | "potpack": "^1.0.1"
1608 | },
1609 | "peerDependencies": {
1610 | "three": ">=0.128.0"
1611 | }
1612 | },
1613 | "node_modules/trim-lines": {
1614 | "version": "3.0.1",
1615 | "license": "MIT",
1616 | "funding": {
1617 | "type": "github",
1618 | "url": "https://github.com/sponsors/wooorm"
1619 | }
1620 | },
1621 | "node_modules/troika-three-text": {
1622 | "version": "0.47.2",
1623 | "license": "MIT",
1624 | "dependencies": {
1625 | "bidi-js": "^1.0.2",
1626 | "troika-three-utils": "^0.47.2",
1627 | "troika-worker-utils": "^0.47.2",
1628 | "webgl-sdf-generator": "1.1.1"
1629 | },
1630 | "peerDependencies": {
1631 | "three": ">=0.125.0"
1632 | }
1633 | },
1634 | "node_modules/troika-three-utils": {
1635 | "version": "0.47.2",
1636 | "license": "MIT",
1637 | "peerDependencies": {
1638 | "three": ">=0.125.0"
1639 | }
1640 | },
1641 | "node_modules/troika-worker-utils": {
1642 | "version": "0.47.2",
1643 | "license": "MIT"
1644 | },
1645 | "node_modules/trough": {
1646 | "version": "2.1.0",
1647 | "license": "MIT",
1648 | "funding": {
1649 | "type": "github",
1650 | "url": "https://github.com/sponsors/wooorm"
1651 | }
1652 | },
1653 | "node_modules/typescript": {
1654 | "version": "5.3.2",
1655 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
1656 | "integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
1657 | "dev": true,
1658 | "bin": {
1659 | "tsc": "bin/tsc",
1660 | "tsserver": "bin/tsserver"
1661 | },
1662 | "engines": {
1663 | "node": ">=14.17"
1664 | }
1665 | },
1666 | "node_modules/unified": {
1667 | "version": "11.0.4",
1668 | "license": "MIT",
1669 | "dependencies": {
1670 | "@types/unist": "^3.0.0",
1671 | "bail": "^2.0.0",
1672 | "devlop": "^1.0.0",
1673 | "extend": "^3.0.0",
1674 | "is-plain-obj": "^4.0.0",
1675 | "trough": "^2.0.0",
1676 | "vfile": "^6.0.0"
1677 | },
1678 | "funding": {
1679 | "type": "opencollective",
1680 | "url": "https://opencollective.com/unified"
1681 | }
1682 | },
1683 | "node_modules/unist-util-is": {
1684 | "version": "6.0.0",
1685 | "license": "MIT",
1686 | "dependencies": {
1687 | "@types/unist": "^3.0.0"
1688 | },
1689 | "funding": {
1690 | "type": "opencollective",
1691 | "url": "https://opencollective.com/unified"
1692 | }
1693 | },
1694 | "node_modules/unist-util-position": {
1695 | "version": "5.0.0",
1696 | "license": "MIT",
1697 | "dependencies": {
1698 | "@types/unist": "^3.0.0"
1699 | },
1700 | "funding": {
1701 | "type": "opencollective",
1702 | "url": "https://opencollective.com/unified"
1703 | }
1704 | },
1705 | "node_modules/unist-util-stringify-position": {
1706 | "version": "4.0.0",
1707 | "license": "MIT",
1708 | "dependencies": {
1709 | "@types/unist": "^3.0.0"
1710 | },
1711 | "funding": {
1712 | "type": "opencollective",
1713 | "url": "https://opencollective.com/unified"
1714 | }
1715 | },
1716 | "node_modules/unist-util-visit": {
1717 | "version": "5.0.0",
1718 | "license": "MIT",
1719 | "dependencies": {
1720 | "@types/unist": "^3.0.0",
1721 | "unist-util-is": "^6.0.0",
1722 | "unist-util-visit-parents": "^6.0.0"
1723 | },
1724 | "funding": {
1725 | "type": "opencollective",
1726 | "url": "https://opencollective.com/unified"
1727 | }
1728 | },
1729 | "node_modules/unist-util-visit-parents": {
1730 | "version": "6.0.1",
1731 | "license": "MIT",
1732 | "dependencies": {
1733 | "@types/unist": "^3.0.0",
1734 | "unist-util-is": "^6.0.0"
1735 | },
1736 | "funding": {
1737 | "type": "opencollective",
1738 | "url": "https://opencollective.com/unified"
1739 | }
1740 | },
1741 | "node_modules/utility-types": {
1742 | "version": "3.10.0",
1743 | "license": "MIT",
1744 | "engines": {
1745 | "node": ">= 4"
1746 | }
1747 | },
1748 | "node_modules/uuid": {
1749 | "version": "9.0.1",
1750 | "funding": [
1751 | "https://github.com/sponsors/broofa",
1752 | "https://github.com/sponsors/ctavan"
1753 | ],
1754 | "license": "MIT",
1755 | "bin": {
1756 | "uuid": "dist/bin/uuid"
1757 | }
1758 | },
1759 | "node_modules/vfile": {
1760 | "version": "6.0.1",
1761 | "license": "MIT",
1762 | "dependencies": {
1763 | "@types/unist": "^3.0.0",
1764 | "unist-util-stringify-position": "^4.0.0",
1765 | "vfile-message": "^4.0.0"
1766 | },
1767 | "funding": {
1768 | "type": "opencollective",
1769 | "url": "https://opencollective.com/unified"
1770 | }
1771 | },
1772 | "node_modules/vfile-message": {
1773 | "version": "4.0.2",
1774 | "license": "MIT",
1775 | "dependencies": {
1776 | "@types/unist": "^3.0.0",
1777 | "unist-util-stringify-position": "^4.0.0"
1778 | },
1779 | "funding": {
1780 | "type": "opencollective",
1781 | "url": "https://opencollective.com/unified"
1782 | }
1783 | },
1784 | "node_modules/webgl-constants": {
1785 | "version": "1.1.1"
1786 | },
1787 | "node_modules/webgl-sdf-generator": {
1788 | "version": "1.1.1",
1789 | "license": "MIT"
1790 | },
1791 | "node_modules/which": {
1792 | "version": "2.0.2",
1793 | "license": "ISC",
1794 | "dependencies": {
1795 | "isexe": "^2.0.0"
1796 | },
1797 | "bin": {
1798 | "node-which": "bin/node-which"
1799 | },
1800 | "engines": {
1801 | "node": ">= 8"
1802 | }
1803 | },
1804 | "node_modules/xtend": {
1805 | "version": "4.0.2",
1806 | "license": "MIT",
1807 | "engines": {
1808 | "node": ">=0.4"
1809 | }
1810 | },
1811 | "node_modules/zustand": {
1812 | "version": "3.7.2",
1813 | "license": "MIT",
1814 | "engines": {
1815 | "node": ">=12.7.0"
1816 | },
1817 | "peerDependencies": {
1818 | "react": ">=16.8"
1819 | },
1820 | "peerDependenciesMeta": {
1821 | "react": {
1822 | "optional": true
1823 | }
1824 | }
1825 | }
1826 | }
1827 | }
1828 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "three",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/index.tsx",
6 | "scripts": {
7 | "build": "npx esbuild src/index.tsx --bundle --outdir=dist --sourcemap --preserve-symlinks --loader:.md=text --minify",
8 | "dev": "npx esbuild src/index.tsx --bundle --outdir=dist --sourcemap --preserve-symlinks --watch --serve=localhost:3002 --servedir=. --loader:.md=text",
9 | "start": "npm run dev",
10 | "deploy": "./misc/deploy.sh"
11 | },
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "@lumaai/luma-web": "^0.2.1",
16 | "@react-three/drei": "^9.88.17",
17 | "@react-three/fiber": "^8.15.11",
18 | "@types/react": "^18.2.38",
19 | "@types/react-dom": "^18.2.17",
20 | "@types/react-syntax-highlighter": "^15.5.10",
21 | "lil-gui": "^0.19.1",
22 | "react": "^18.2.0",
23 | "react-dom": "^18.2.0",
24 | "react-markdown": "^9.0.1",
25 | "react-syntax-highlighter": "^15.5.0",
26 | "three": "^0.157.0"
27 | },
28 | "devDependencies": {
29 | "@types/three": "^0.157.2",
30 | "esbuild": "^0.19.5",
31 | "typescript": "^5.3.2"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/DemoBackgroundRemoval.ts:
--------------------------------------------------------------------------------
1 | import { LumaSplatsSemantics, LumaSplatsThree } from "@lumaai/luma-web";
2 | import { DemoProps } from ".";
3 | import { loadEnvironment } from "./util/Environment";
4 |
5 | export function DemoBackgroundRemoval(props: DemoProps) {
6 | let { renderer, scene, gui } = props;
7 |
8 | let splats = new LumaSplatsThree({
9 | // Jules Desbois La Femme à l’arc @HouseofJJD
10 | source: 'https://lumalabs.ai/capture/1b5f3e33-3900-4398-8795-b585ae13fd2d',
11 | enableThreeShaderIntegration: false,
12 | });
13 |
14 | scene.add(splats);
15 |
16 | let layersEnabled = {
17 | Background: false,
18 | Foreground: true,
19 | }
20 |
21 | function updateSemanticMask() {
22 | splats.semanticsMask =
23 | (layersEnabled.Background ? LumaSplatsSemantics.BACKGROUND : 0) |
24 | (layersEnabled.Foreground ? LumaSplatsSemantics.FOREGROUND : 0);
25 | }
26 |
27 | updateSemanticMask();
28 |
29 | gui.add(layersEnabled, 'Background').onChange(updateSemanticMask);
30 |
31 | loadEnvironment(renderer, scene, 'assets/venice_sunset_1k.hdr');
32 |
33 | return {
34 | dispose: () => {
35 | splats.dispose();
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/DemoCustomShaders.ts:
--------------------------------------------------------------------------------
1 | import { LumaSplatsLoader, LumaSplatsThree } from "@lumaai/luma-web";
2 | import { FogExp2, Uniform } from "three";
3 | import { DemoProps } from '.';
4 | import { downloadArtifacts } from "./util/DownloadArtifacts";
5 |
6 | export function DemoCustomShaders(props: DemoProps) {
7 | let { renderer, camera, scene, gui } = props;
8 |
9 | let uniformTime = new Uniform(0);
10 |
11 | let splats = new LumaSplatsThree({
12 | // Chateau de Menthon - Annecy @Yannick_Cerrutti
13 | source: `https://lumalabs.ai/capture/da82625c-9c8d-4d05-a9f7-3367ecab438c`,
14 | enableThreeShaderIntegration: true,
15 | onBeforeRender: () => {
16 | uniformTime.value = performance.now() / 1000;
17 | }
18 | });
19 | scene.add(splats);
20 |
21 | gui.add(splats, 'enableThreeShaderIntegration').name("Use Three Pipeline");
22 |
23 | splats.setShaderHooks({
24 | vertexShaderHooks: {
25 | additionalUniforms: {
26 | time_s: ['float', uniformTime],
27 | },
28 |
29 | // additionalGlobals: /*glsl*/``,
30 |
31 | // onMainEnd: /*glsl*/`
32 | // (vec4 p) {
33 | // return p;
34 | // }
35 | // `,
36 |
37 | // returns a mat4
38 | getSplatTransform: /*glsl*/`
39 | (vec3 position, uint layersBitmask) {
40 | // sin wave on x-axis
41 | float x = 0.;
42 | float z = 0.;
43 | float y = sin(position.x * 1.0 + time_s) * 0.1;
44 | return mat4(
45 | 1., 0., 0., 0,
46 | 0., 1., 0., 0,
47 | 0., 0., 1., 0,
48 | x, y, z, 1.
49 | );
50 | }
51 | `,
52 |
53 | // getSplatColor: `
54 | // (vec4 rgba, vec3 position, uint layersBitmask) {
55 | // return rgba * vec4(abs(normalize(position)), 1.0);
56 | // }
57 | // `
58 | }
59 | });
60 |
61 | splats.onInitialCameraTransform = transform => {
62 | camera.matrix.copy(transform);
63 | camera.matrix.decompose(camera.position, camera.quaternion, camera.scale);
64 | camera.updateMatrixWorld();
65 | };
66 |
67 |
68 | let layersEnabled = {
69 | background: true,
70 | foreground: true,
71 | }
72 |
73 | function updateSemanticMask() {
74 | splats.semanticsMask =
75 | (layersEnabled.background ? 1 : 0) |
76 | (layersEnabled.foreground ? 2 : 0);
77 | }
78 |
79 | updateSemanticMask();
80 |
81 | let layersFolder = gui.addFolder('layers');
82 |
83 | layersFolder.add(layersEnabled, 'background').onChange(updateSemanticMask);
84 | layersFolder.add(layersEnabled, 'foreground').onChange(updateSemanticMask);
85 | layersFolder.hide();
86 |
87 | // fog
88 | scene.fog = new FogExp2(0xEEEEEE, 0.05);
89 | scene.background = scene.fog.color;
90 |
91 | // gui for fog
92 | gui.add(scene.fog, 'density', 0, 0.3).name('fog density');
93 | gui.addColor(scene.fog, 'color').name('fog color');
94 |
95 | return {
96 | dispose: () => {
97 | splats.dispose();
98 | }
99 | }
100 | }
--------------------------------------------------------------------------------
/src/DemoFog.ts:
--------------------------------------------------------------------------------
1 | import { LumaSplatsThree } from "@lumaai/luma-web";
2 | import { Color, FogExp2 } from "three";
3 | import { DemoProps } from '.';
4 | import { EnvironmentProbes } from './util/EnvironmentProbes';
5 |
6 | export function DemoFog(props: DemoProps) {
7 | let { renderer, camera, scene, gui } = props;
8 |
9 | // fog
10 | scene.fog = new FogExp2(new Color(0xe0e1ff).convertLinearToSRGB(), 0.20);
11 | scene.background = scene.fog.color;
12 |
13 | let splats = new LumaSplatsThree({
14 | // HOLLYWOOD @DroneFotoBooth
15 | source: 'https://lumalabs.ai/capture/b5faf515-7932-4000-ab23-959fc43f0d94',
16 | loadingAnimationEnabled: false,
17 | });
18 | scene.add(splats);
19 |
20 | // set ideal initial camera transform
21 | splats.onInitialCameraTransform = (transform) => {
22 | camera.matrix.copy(transform);
23 | camera.matrix.decompose(camera.position, camera.quaternion, camera.scale);
24 | camera.updateMatrixWorld();
25 | };
26 |
27 | splats.onLoad = () => {
28 | splats.captureCubemap(renderer).then(environmentMap => {
29 | scene.environment = environmentMap;
30 | let environmentProbes = new EnvironmentProbes(4);
31 | environmentProbes.position.set(-3, 1, 0.25);
32 | environmentProbes.rotation.y = Math.PI / 2;
33 | environmentProbes.scale.setScalar(3);
34 | scene.add(environmentProbes);
35 | });
36 | }
37 |
38 | // gui for fog
39 | gui.add(renderer, 'toneMappingExposure', 0, 10).name('Exposure');
40 | gui.add(scene.fog, 'density', 0, 0.3).name('Fog Density');
41 | gui.addColor(scene.fog, 'color').name('Fog Color');
42 |
43 | return {
44 | dispose: () => {
45 | // stop worker, free resources
46 | splats.dispose();
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/src/DemoHelloWorld.ts:
--------------------------------------------------------------------------------
1 | import { LumaSplatsThree } from "@lumaai/luma-web";
2 | import { Color, DoubleSide, Mesh, MeshStandardMaterial, PlaneGeometry, Texture, Vector3 } from "three";
3 | import { DemoProps } from ".";
4 |
5 | export function DemoHelloWorld(props: DemoProps) {
6 | let { renderer, camera, scene, gui } = props;
7 |
8 | scene.background = new Color('white');
9 |
10 | let splats = new LumaSplatsThree({
11 | // MIT WPU Globe @krazyykrunal
12 | source: 'https://lumalabs.ai/capture/ca9ea966-ca24-4ec1-ab0f-af665cb546ff',
13 | // we disable full three.js for performance
14 | enableThreeShaderIntegration: false,
15 | // particle entrance animation
16 | particleRevealEnabled: true,
17 | });
18 |
19 | scene.add(splats);
20 |
21 | // the splats file can provide an ideal initial viewing location
22 | splats.onInitialCameraTransform = transform => {
23 | transform.decompose(camera.position, camera.quaternion, new Vector3());
24 | };
25 |
26 | scene.add(createText());
27 |
28 | return {
29 | dispose: () => {
30 | // stop worker, free resources
31 | splats.dispose();
32 | }
33 | }
34 | }
35 |
36 | // create a plane with "Hello World" text
37 | function createText() {
38 | // create canvas
39 | const canvas = document.createElement('canvas');
40 | const context = canvas.getContext('2d')!;
41 | canvas.width = 1024;
42 | canvas.height = 512;
43 |
44 | // clear white, 0 alpha
45 | context.fillStyle = 'rgba(255, 255, 255, 0)';
46 | context.fillRect(0, 0, canvas.width, canvas.height);
47 |
48 | // draw text
49 | context.fillStyle = 'white';
50 | // 100px helvetica, arial, sans-serif
51 | context.font = '200px sans-serif';
52 | context.textAlign = 'center';
53 | context.textBaseline = 'middle';
54 | // stroke
55 | context.strokeStyle = 'rgba(0, 0, 0, 0.5)'
56 | context.lineWidth = 5;
57 | context.fillText('Hello World', canvas.width / 2, canvas.height / 2);
58 | context.strokeText('Hello World', canvas.width / 2, canvas.height / 2);
59 |
60 | // create texture from canvas
61 | const texture = new Texture(canvas);
62 | texture.needsUpdate = true;
63 |
64 | // create plane geometry and mesh with the texture
65 | const geometry = new PlaneGeometry(5, 2.5);
66 | const material = new MeshStandardMaterial({
67 | map: texture,
68 | transparent: false,
69 | alphaTest: 0.5,
70 | side: DoubleSide,
71 | premultipliedAlpha: true,
72 | emissive: 'white',
73 | emissiveIntensity: 2,
74 | });
75 | const textPlane = new Mesh(geometry, material);
76 |
77 | // position and rotate
78 | textPlane.position.set(0.8, -0.9, 0);
79 | textPlane.rotation.y = Math.PI / 2;
80 | textPlane.scale.setScalar(0.6);
81 |
82 | return textPlane;
83 | }
--------------------------------------------------------------------------------
/src/DemoLighting.ts:
--------------------------------------------------------------------------------
1 | import { LumaSplatsThree } from "@lumaai/luma-web";
2 | import { Mesh } from "three";
3 | import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
4 | import { EnvironmentProbes } from "./util/EnvironmentProbes";
5 | import { DemoProps } from ".";
6 |
7 | export function DemoLighting(props: DemoProps) {
8 | let { renderer, camera, scene, gui } = props;
9 |
10 | let splats = new LumaSplatsThree({
11 | // Arosa Hörnli - Switzerland @splnlss
12 | source: 'https://lumalabs.ai/capture/4da7cf32-865a-4515-8cb9-9dfc574c90c2',
13 | // disable loading animation so model is fully rendered after onLoad
14 | loadingAnimationEnabled: false,
15 | });
16 |
17 | splats.onLoad = () => {
18 | splats.captureCubemap(renderer).then(capturedTexture => {
19 | scene.environment = capturedTexture;
20 | scene.background = capturedTexture;
21 | scene.backgroundBlurriness = 0.5;
22 | });
23 | }
24 |
25 | scene.add(splats);
26 |
27 | // move camera to ideal viewing position
28 | splats.onInitialCameraTransform = transform => {
29 | camera.matrix.copy(transform);
30 | camera.matrix.decompose(camera.position, camera.quaternion, camera.scale);
31 | camera.position.y = 0.25;
32 | };
33 |
34 | // load ufo glb
35 | let loader = new GLTFLoader();
36 |
37 | /**
38 | * UFO_B11 D Model by Zuncho Multimedia is licensed under Creative Commons Attribution
39 | * https://sketchfab.com/3d-models/ufo-b11-d-model-75cc3cf1fbb648e5b5a046c055df017a#download
40 | */
41 | loader.load('assets/models/ufo_b11_d_model.glb', (gltf) => {
42 | let ufo = gltf.scene;
43 | ufo.scale.setScalar((1/100) * 0.8);
44 | ufo.scale.y *= 0.75;
45 | ufo.position.y = 0.5;
46 | ufo.position.x = -0.8;
47 | scene.add(ufo);
48 |
49 | // make shiny metal
50 | ufo.traverse((child) => {
51 | if (child instanceof Mesh) {
52 | child.material.metalness = 1.0;
53 | child.material.roughness = 0.0;
54 | }
55 | });
56 |
57 | // animate
58 | scene.onBeforeRender = () => {
59 | let t_s = performance.now() / 1000;
60 | ufo.position.y = Math.sin(t_s * 0.25) * 0.05 + 0.5;
61 | }
62 | });
63 |
64 | let probes = new EnvironmentProbes();
65 | probes.position.x = 2.5;
66 | scene.add(probes);
67 |
68 | return {
69 | dispose: () => splats.dispose()
70 | }
71 | }
--------------------------------------------------------------------------------
/src/DemoReactThreeFiber.tsx:
--------------------------------------------------------------------------------
1 | import { Environment, Grid, PivotControls } from '@react-three/drei';
2 | import { LumaSplatsSemantics } from "@lumaai/luma-web";
3 | import React from 'react';
4 | import './LumaSplatsReact';
5 |
6 | export function DemoReactThreeFiber() {
7 | const gridConfig = {
8 | gridSize: [10.5, 10.5],
9 | cellSize: 0.6,
10 | cellThickness: 1,
11 | cellColor: '#6f6f6f',
12 | sectionSize: 3.3,
13 | sectionThickness: 1.5,
14 | sectionColor: '#c1c1c1',
15 | fadeDistance: 25,
16 | fadeStrength: 1,
17 | followCamera: false,
18 | infiniteGrid: true
19 | }
20 |
21 | return <>
22 |
27 |
28 |
29 |
30 |
37 |
38 |
41 |
49 |
50 | >;
51 | }
--------------------------------------------------------------------------------
/src/DemoTransmission.ts:
--------------------------------------------------------------------------------
1 | import { LumaSplatsThree } from "@lumaai/luma-web";
2 | import { NoToneMapping, LinearToneMapping, CineonToneMapping, ReinhardToneMapping, ACESFilmicToneMapping, WebGLRenderer, WebGLRenderTarget, CubeTexture, FrontSide, MathUtils, Mesh, MeshPhysicalMaterial, PerspectiveCamera, SphereGeometry, Vector3 } from "three";
3 | import { DemoProps } from ".";
4 | import { addMaterial } from "./util/GUIUtils";
5 |
6 | const worldSources = [
7 | // Chateau de Menthon - Annecy @Yannick_Cerrutti
8 | { source: 'https://lumalabs.ai/capture/da82625c-9c8d-4d05-a9f7-3367ecab438c', scale: 1 },
9 | // Arosa Hörnli - Switzerland @splnlss
10 | { source: 'https://lumalabs.ai/capture/4da7cf32-865a-4515-8cb9-9dfc574c90c2', scale: 1 },
11 | // Fish reefs – Okinawa @BBCG
12 | { source: 'https://lumalabs.ai/capture/6331c1bb-3352-4c8e-b691-32b9b70ec768', scale: 1 },
13 | // Glacial Erratic - Aspen, Colorado @VibrantNebula_Luma
14 | // { source: 'https://lumalabs.ai/capture/f513900b-69fe-43c8-a72e-80b8d5a16fa4', scale: 1 },
15 | // Meta Girl (Oleg Lobykin) | Burning Man 2022 @VibrantNebula_Luma
16 | // { source: 'https://lumalabs.ai/capture/2d57866c-83dc-47a6-a725-69c27f75ddb0', scale: 1 },
17 | // Pinkerton Hot Springs @VibrantNebula_Luma
18 | // { source: 'https://lumalabs.ai/capture/a5e98f35-3759-4cf5-a226-079b15c805da', scale: 1 },
19 | // HOLLYWOOD @DroneFotoBooth
20 | // { source: 'https://lumalabs.ai/capture/b5faf515-7932-4000-ab23-959fc43f0d94', scale: 1 },
21 | // Metropolis @fotozhora_sk
22 | { source: 'https://lumalabs.ai/capture/d2d2badd-8bdd-4874-84f7-9df2aae27f29', scale: 1 },
23 | ];
24 |
25 | const innerGlobeRadius = 1;
26 | const outerGlobeRadius = 8;
27 | const radiusGap = outerGlobeRadius - innerGlobeRadius;
28 |
29 | export function DemoTransmission(props: DemoProps) {
30 | let { renderer, camera, scene, controls, gui } = props;
31 |
32 | renderer.toneMapping = NoToneMapping;
33 | renderer.localClippingEnabled = false;
34 |
35 | controls.enablePan = false;
36 | controls.autoRotate = false;
37 | camera.position.y = 3;
38 | camera.position.setLength(innerGlobeRadius + radiusGap * 0.25);
39 |
40 | // state, updated in mainLoop before rendering
41 | let state = {
42 | level: 0,
43 | innerSplatIndex: NaN,
44 | outerSplatIndex: NaN,
45 | animate: true,
46 | }
47 |
48 | // add tonemapping gui
49 | gui.add(renderer, 'toneMappingExposure', 0, 10).name('Exposure');
50 | gui.add(renderer, 'toneMapping', {
51 | NoToneMapping,
52 | LinearToneMapping,
53 | CineonToneMapping,
54 | ReinhardToneMapping,
55 | ACESFilmicToneMapping,
56 | }).name('Tone Mapping');
57 | gui.add(state, 'animate').name('Animate');
58 |
59 | // space key to toggle animation
60 | window.addEventListener('keydown', (e) => {
61 | if (e.code === 'Space') {
62 | state.animate = !state.animate;
63 | }
64 | });
65 |
66 | // add a refractive transmissive sphere
67 | let glassGlobe = new Mesh(
68 | new SphereGeometry(1, 32, 32),
69 | new MeshPhysicalMaterial({
70 | roughness: 0,
71 | metalness: 0,
72 | transparent: true,
73 | transmission: 1,
74 | ior: 1.341,
75 | // thickness: 1.52,
76 | thickness: 2,
77 | envMapIntensity: 1.2, // push up the environment map intensity a little
78 | clearcoat: 1,
79 | side: FrontSide,
80 | })
81 | );
82 | const initialMaterialProperties = { ...glassGlobe.material };
83 | addMaterial(gui, glassGlobe.material, 'Glass Globe');
84 | gui.add(glassGlobe, 'renderOrder', -1, 1).step(1);
85 | glassGlobe.scale.setScalar(innerGlobeRadius);
86 | scene.add(glassGlobe);
87 |
88 | const splatWorlds = worldSources.map((world, index) => {
89 | let splat = new LumaSplatsThree({
90 | source: world.source,
91 | // disable loading animation so we can capture an environment map as soon as it's loaded
92 | loadingAnimationEnabled: false,
93 | onBeforeRender: (renderer) => {
94 | let renderTarget = renderer.getRenderTarget();
95 | disableMSAA(renderTarget);
96 |
97 | let isWithinGlobe = state.innerSplatIndex === index;
98 | let isCubeRenderTarget = (renderTarget?.texture as any)?.isCubeTexture === true;
99 | if (isCubeRenderTarget) {
100 | splat.preventDraw = false;
101 | } else {
102 | if (isWithinGlobe) {
103 | // disable rendering to canvas
104 | splat.preventDraw = renderTarget == null;
105 | } else {
106 | // disable rendering to transmission
107 | splat.preventDraw = renderTarget != null;
108 | }
109 | }
110 | }
111 | });
112 | splat.scale.setScalar(world.scale);
113 |
114 | let splatWorld = {
115 | environmentMap: null as CubeTexture | null,
116 | splat,
117 | }
118 |
119 | // capture environment lighting after load
120 | splat.onLoad = () => {
121 | splat.captureCubemap(renderer).then((cubemap) => {
122 | splatWorld.environmentMap = cubemap;
123 | });
124 | }
125 |
126 | return splatWorld;
127 | });
128 |
129 | // main loop
130 | let lastFrameTime_ms = performance.now();
131 | scene.onBeforeRender = () => {
132 | let t_ms = performance.now();
133 |
134 | let dt_s = (t_ms - lastFrameTime_ms) / 1000;
135 | lastFrameTime_ms = t_ms;
136 |
137 | if (state.animate) {
138 | let cameraRadialVector = camera.position.clone().normalize();
139 | camera.position.addScaledVector(cameraRadialVector, dt_s);
140 | // rotate around y axis
141 | camera.position.applyAxisAngle(new Vector3(0, 1, 0), dt_s * 0.5);
142 | camera.lookAt(new Vector3(0, 0, 0));
143 | }
144 |
145 | // check if camera's near plane is inside the globe
146 | camera.updateWorldMatrix(true, false);
147 | let near = (camera as PerspectiveCamera).near;
148 | // let nearVector = new Vector3(0, 0, -near);
149 | // let nearWorld = nearVector.applyMatrix4(camera.matrixWorld);
150 | // let distanceToCenter = nearWorld.distanceTo(glassGlobe.position);
151 | let nearPlaneDistanceToCenter = camera.position.length() - near;
152 | let innerSurfaceDistance = nearPlaneDistanceToCenter - innerGlobeRadius;
153 |
154 | function applyCameraModulo() {
155 | let newInnerSurfaceDistance = MathUtils.euclideanModulo(innerSurfaceDistance, radiusGap);
156 | let newCameraDistance = newInnerSurfaceDistance + innerGlobeRadius + near;
157 | camera.position.setLength(newCameraDistance);
158 | // update innerSurfaceDistance and outerSurfaceDistance
159 | innerSurfaceDistance = newInnerSurfaceDistance;
160 |
161 | camera.updateWorldMatrix(true, false);
162 | }
163 |
164 | if (innerSurfaceDistance > radiusGap) {
165 | applyCameraModulo();
166 | state.level++;
167 | }
168 |
169 | if (innerSurfaceDistance < 0) {
170 | applyCameraModulo();
171 | state.level--;
172 | }
173 |
174 | // determine camera position in the range [0, 1] where 0 is inside the inner globe and 1 is outside the outer globe
175 | // after modulo
176 | let cameraU = innerSurfaceDistance / radiusGap;
177 |
178 | // use level to set scene state
179 | state.innerSplatIndex = MathUtils.euclideanModulo(state.level - 1, splatWorlds.length);
180 | state.outerSplatIndex = MathUtils.euclideanModulo(state.level, splatWorlds.length);
181 |
182 | for (let i = 0; i < splatWorlds.length; i++) {
183 | let splatWorld = splatWorlds[i];
184 | let splat = splatWorld.splat;
185 |
186 | let isInnerSplat = state.innerSplatIndex === i;
187 | let isOuterSplat = state.outerSplatIndex === i;
188 | let isVisible = state.innerSplatIndex === i || state.outerSplatIndex === i;
189 | // disable enableThreeShaderIntegration to improve performance when not required
190 | // we must do this before splat.onBeforeRender is called because by then it's too late to change material for the frame
191 | splat.enableThreeShaderIntegration = isInnerSplat;
192 | splat.material.transparent = !isInnerSplat;
193 |
194 | if (isVisible) {
195 | scene.add(splat);
196 | } else {
197 | scene.remove(splat);
198 | }
199 |
200 | // make the world lit by the outer splat
201 | if (isOuterSplat) {
202 | if (scene.environment != splatWorld.environmentMap) {
203 | scene.environment = splatWorld.environmentMap;
204 | scene.background = splatWorld.environmentMap;
205 | }
206 | }
207 |
208 | // scale the inner splat for continuity across the boundary
209 | if (isInnerSplat) {
210 | let r = innerGlobeRadius / outerGlobeRadius;
211 | // splat.scale.setScalar(MathUtils.lerp(r, 1, cameraU));
212 | splat.scale.setScalar(r);
213 | } else {
214 | splat.scale.setScalar(1);
215 | }
216 |
217 | if (isVisible) {
218 | splat.updateMatrix();
219 | splat.updateMatrixWorld();
220 | }
221 | }
222 |
223 | // adjust globe thickness
224 | glassGlobe.material.thickness = MathUtils.lerp(initialMaterialProperties.thickness, 0, MathUtils.smoothstep(0.2, 0, innerSurfaceDistance));
225 | // glassGlobe.visible = innerSurfaceDistance > 0;
226 |
227 | // scale the globe to appear
228 | let s = 1 - cameraU;
229 | glassGlobe.scale.setScalar(MathUtils.smootherstep(s, 0.0, 0.2));
230 | glassGlobe.updateMatrix();
231 | glassGlobe.updateMatrixWorld();
232 | // glassGlobe.material.opacity = MathUtils.lerp(1, 0, cameraU * cameraU * cameraU);
233 | }
234 |
235 | return {
236 | dispose: () => {
237 | for (let splatWorld of splatWorlds) {
238 | splatWorld.splat.dispose();
239 | }
240 | }
241 | }
242 | }
243 |
244 | function disableMSAA(target: WebGLRenderTarget | null) {
245 | // disable MSAA on render targets (in this case the transmission render target)
246 | // this improves splatting performance
247 | if (target) {
248 | target.samples = 0;
249 | }
250 | }
--------------------------------------------------------------------------------
/src/DemoVR.ts:
--------------------------------------------------------------------------------
1 | import { LumaSplatsThree } from "@lumaai/luma-web";
2 | import { VRButton } from "three/examples/jsm/webxr/VRButton.js";
3 | import { DemoProps } from ".";
4 |
5 | export function DemoVR(props: DemoProps) {
6 | let { renderer, camera, scene, controls, gui } = props;
7 |
8 | renderer.xr.enabled = true;
9 |
10 | let vrButton = VRButton.createButton(renderer);
11 | let canvas = renderer.getContext().canvas as HTMLCanvasElement;
12 | canvas.parentElement!.append(vrButton);
13 |
14 | let splats = new LumaSplatsThree({
15 | // Kind Humanoid @RyanHickman
16 | source: 'https://lumalabs.ai/capture/83e9aae8-7023-448e-83a6-53ccb377ec86',
17 | // disable three.js shader integration for performance
18 | enableThreeShaderIntegration: false,
19 | });
20 |
21 | scene.add(splats);
22 |
23 | return {
24 | dispose: () => {
25 | splats.dispose();
26 | vrButton.remove();
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/src/LumaSplatsReact.ts:
--------------------------------------------------------------------------------
1 | import { Object3DNode, extend } from '@react-three/fiber';
2 | import { LumaSplatsThree } from "@lumaai/luma-web";
3 |
4 | // Make LumaSplatsThree available to R3F
5 | extend( { LumaSplats: LumaSplatsThree } );
6 |
7 | // For typeScript support:
8 | declare module '@react-three/fiber' {
9 | interface ThreeElements {
10 | lumaSplats: Object3DNode
11 | }
12 | }
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { AdaptiveDpr, OrbitControls, PerspectiveCamera } from '@react-three/drei';
2 | import { OrbitControls as OrbitControlsStdLib } from 'three-stdlib';
3 | import { Canvas, useThree } from '@react-three/fiber';
4 | import GUI from 'lil-gui';
5 | import React, { useEffect, useRef, useState } from 'react';
6 | import { createRoot } from 'react-dom/client';
7 | import Markdown from 'react-markdown';
8 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
9 | import { nord as syntaxTheme } from 'react-syntax-highlighter/dist/esm/styles/prism';
10 | import { Camera, CineonToneMapping, Scene, WebGLRenderer } from 'three';
11 | import readme from '../README.md';
12 | import { DemoBackgroundRemoval } from './DemoBackgroundRemoval';
13 | import { DemoCustomShaders } from './DemoCustomShaders';
14 | import { DemoFog } from './DemoFog';
15 | import { DemoHelloWorld } from './DemoHelloWorld';
16 | import { DemoLighting } from './DemoLighting';
17 | import { DemoReactThreeFiber } from './DemoReactThreeFiber';
18 | import { DemoTransmission } from './DemoTransmission';
19 | import { DemoVR } from './DemoVR';
20 |
21 | export type DemoProps = {
22 | renderer: WebGLRenderer,
23 | scene: Scene,
24 | camera: Camera,
25 | controls: OrbitControlsStdLib,
26 | gui: GUI,
27 | }
28 |
29 | type DemoFn =
30 | (props: DemoProps)
31 | => { dispose: () => void } | void;
32 |
33 | const demos = {
34 | basic: {
35 | "getting-started": DemoHelloWorld,
36 | "three-fog": DemoFog,
37 | "background-removal": DemoBackgroundRemoval,
38 | "scene-lighting": DemoLighting,
39 | "custom-shaders": DemoCustomShaders,
40 | "transmission": DemoTransmission,
41 | "vr": DemoVR,
42 | } as Record,
43 | react: {
44 | "react-three-fiber": DemoReactThreeFiber
45 | } as Record>
46 | }
47 |
48 | let globalGUI: GUI | null = null;
49 |
50 | function DemoScene(props: {
51 | expanded: boolean,
52 | demoBasicFn: DemoFn | null,
53 | demoReactFn: React.FC<{ gui: GUI }> | null,
54 | onExpandToggle: (expanded: boolean) => void,
55 | }) {
56 | let { scene, gl: renderer, camera } = useThree();
57 |
58 | let [gui, setGUI] = useState(globalGUI);
59 | let [autoRotate, setAutoRotate] = useState(true);
60 | let [showUI, setShowUI] = useState(true);
61 | let controlsRef = useRef(null);
62 |
63 | useEffect(() => {
64 | globalGUI?.destroy();
65 |
66 | globalGUI = new GUI({
67 | container: renderer.domElement.parentElement!,
68 | });
69 | globalGUI.close();
70 | globalGUI.domElement.style.position = 'absolute';
71 | globalGUI.domElement.style.top = '0';
72 | globalGUI.domElement.style.right = '0';
73 | globalGUI.domElement.style.zIndex = '1000';
74 | globalGUI.domElement.addEventListener('pointerdown', (e) => {
75 | e.stopPropagation();
76 | });
77 |
78 | let pixelRatioProxy = {
79 | get pixelRatio() {
80 | return renderer.getPixelRatio()
81 | },
82 | set pixelRatio(value: number) {
83 | renderer.setPixelRatio(value);
84 |
85 | // update url parameter
86 | let url = new URL(window.location.href);
87 | url.searchParams.set('pixelRatio', value.toString());
88 | window.history.replaceState({}, '', url.href);
89 | }
90 | }
91 | // initial pixel ratio from url parameter if available
92 | const url = new URL(window.location.href);
93 | let pixelRatioParam = url.searchParams.get('pixelRatio');
94 | if (pixelRatioParam != null) {
95 | pixelRatioProxy.pixelRatio = parseFloat(pixelRatioParam);
96 | }
97 |
98 | globalGUI.add(pixelRatioProxy, 'pixelRatio', 0.5, window.devicePixelRatio, 0.25).name('Pixel Ratio');
99 |
100 | setGUI(globalGUI);
101 |
102 | let demoProps = {
103 | renderer,
104 | scene,
105 | camera,
106 | controls: controlsRef.current!,
107 | gui: globalGUI,
108 | }
109 |
110 | if (props.demoBasicFn) {
111 | let demoDispose = props.demoBasicFn(demoProps)?.dispose;
112 |
113 | return () => {
114 | // call .dispose() on all objects in the scene
115 | scene.traverse((obj) => {
116 | (obj as any).dispose?.();
117 | });
118 |
119 | demoDispose?.();
120 |
121 | renderer.dispose();
122 |
123 | globalGUI?.destroy();
124 | globalGUI = null;
125 | }
126 | }
127 | }, []);
128 |
129 | useEffect(() => {
130 | // h key to hide/show gui
131 | function toggleGUI(e: KeyboardEvent) {
132 | if (e.key === 'h') {
133 | if (showUI) {
134 | gui?.hide();
135 | setShowUI(false);
136 | } else {
137 | gui?.show();
138 | setShowUI(true);
139 | }
140 | }
141 | }
142 |
143 | window.addEventListener('keydown', toggleGUI);
144 |
145 | let expandButton = document.createElement('div');
146 | expandButton.style.visibility = showUI ? 'visible' : 'hidden';
147 | expandButton.className = 'expand-button ' + (props.expanded ? 'icon-compress' : 'icon-expand');
148 | expandButton.onclick = () => {
149 | props.onExpandToggle(!props.expanded);
150 | }
151 | renderer.domElement.parentElement!.prepend(expandButton);
152 |
153 | return () => {
154 | window.removeEventListener('keydown', toggleGUI);
155 | expandButton.remove();
156 | }
157 | }, [props.expanded, showUI, gui]);
158 |
159 | return <>
160 |
161 | {
168 | setAutoRotate(false);
169 | }}
170 | makeDefault
171 | />
172 | {props.demoReactFn && gui && }
173 | >
174 | }
175 |
176 | function App() {
177 | const demoKeys = Object.keys(demos.basic).concat(Object.keys(demos.react));
178 |
179 | const [demoKey, setDemoKey] = useState(() => {
180 | // get url parameter
181 | const url = new URL(window.location.href);
182 | let demoParam = url.hash.replace(/^#/, '');
183 | let demoExists = demoParam != null && demoKeys.includes(demoParam);
184 | return demoExists ? demoParam : demoKeys[0];
185 | });
186 |
187 | const [showDocs, setShowDocs] = useState(() => {
188 | // get url parameter
189 | const url = new URL(window.location.href);
190 | return url.searchParams.get('hide-docs') == null;
191 | });
192 |
193 | useEffect(() => {
194 | // update hide-docs url parameter
195 | let url = new URL(window.location.href);
196 | if (!showDocs) {
197 | url.searchParams.set('hide-docs', '');
198 | } else {
199 | url.searchParams.delete('hide-docs');
200 | }
201 | window.history.replaceState({}, '', url.href);
202 | }, [showDocs]);
203 |
204 | const demoBasicFn = demoKey != null ? demos.basic[demoKey] : null;
205 | const demoReactFn = demoKey != null ? demos.react[demoKey] : null;
206 | const hasDemo = demoBasicFn != null || demoReactFn != null;
207 |
208 | useEffect(() => {
209 | // react to url changes
210 | window.addEventListener('hashchange', () => {
211 | setDemoKey(window.location.hash.replace(/^#/, ''));
212 | });
213 |
214 | // scroll to demo
215 | if (demoKey) {
216 | document.getElementById(demoKey)?.scrollIntoView({ behavior: 'smooth' });
217 | }
218 |
219 | // press e to expand demo
220 | function onKeyDown(e: KeyboardEvent) {
221 | if (e.key === 'e') {
222 | setShowDocs(e => !e);
223 | }
224 | }
225 | window.addEventListener('keydown', onKeyDown);
226 |
227 | return () => {
228 | window.removeEventListener('keydown', onKeyDown);
229 | }
230 | }, []);
231 |
232 | return <>
233 | {showDocs &&
234 |
) {
241 | document.getElementById(id)?.querySelector('a')?.click();
242 | // setDemoKey(id);
243 | // document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
244 | }
245 | // make clickable
246 | return
252 | {children}
253 | {!isActive && }
254 |
;
255 | },
256 | code(props) {
257 | const { children, className, node, ref, ...rest } = props
258 | const match = /language-(\w+)/.exec(className || '')
259 | return match ? (
260 |
266 | ) : (
267 |
268 | {children}
269 |
270 | )
271 | },
272 | a(props) {
273 | let { href, ...rest } = props;
274 |
275 | // replace ./src links with github links for better readability
276 | if (href?.startsWith('./src')) {
277 | href = 'https://github.com/lumalabs/luma-web-examples/blob/main/' + href.slice(1);
278 | }
279 |
280 | let isAbsolute = /^https?:\/\//.test(href ?? '');
281 | if (isAbsolute) {
282 | // open in new tab
283 | return
284 | } else {
285 | return
286 | }
287 | }
288 | }}
289 | >{readme}
290 |
}
291 |
292 | {hasDemo && }
317 | >
318 | }
319 |
320 | const reactRoot = document.getElementById('react-root');
321 | createRoot(reactRoot!).render();
--------------------------------------------------------------------------------
/src/minimal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
23 |
70 |
71 |
--------------------------------------------------------------------------------
/src/util/DownloadArtifacts.ts:
--------------------------------------------------------------------------------
1 | import { LumaSplatsLoader } from "@lumaai/luma-web";
2 |
3 | export async function downloadArtifacts(splatLoader: LumaSplatsLoader, onProgress: (progress: number) => void = ()=>{}) {
4 | let artifacts: { [key: string]: string | undefined } = await splatLoader.getArtifacts();
5 |
6 | // we care about the following
7 | let required = [
8 | 'gs_web_meta',
9 | 'gs_web_gauss1',
10 | 'gs_web_gauss2',
11 | 'gs_web_sh',
12 | 'gs_web_webmeta',
13 | 'gs_compressed_meta',
14 | 'gs_compressed',
15 | 'with_background_gs_camera_params',
16 | 'semantics',
17 | ];
18 |
19 | let totalFiles = required.reduce((total, key) => {
20 | return artifacts[key] ? (total + 1) : total
21 | }, 0);
22 | let filesCompete = 0;
23 |
24 | onProgress(0);
25 |
26 | let blobs = required.map(async key => {
27 | let url = artifacts[key];
28 | if (!url) {
29 | console.log('missing', key);
30 | return null;
31 | }
32 |
33 | let urlFilename = url?.split('/').pop();
34 | let fileType = urlFilename?.split('.').pop();
35 |
36 | let filename = `${key}.${fileType}`;
37 |
38 | console.log('downloading', filename);
39 |
40 | let response = await fetch(url);
41 | let blob = await response.blob();
42 |
43 | filesCompete++;
44 | onProgress(filesCompete / totalFiles);
45 |
46 | return {filename, url, blob};
47 | });
48 |
49 | // trigger download for all the files
50 | let files = await Promise.all(blobs);
51 | files.forEach(file => {
52 | if (!file) {
53 | return;
54 | }
55 | let {filename, blob} = file;
56 | let url = URL.createObjectURL(blob);
57 | let link = document.createElement('a');
58 | link.href = url;
59 | link.download = filename;
60 | link.click();
61 | });
62 |
63 | onProgress(1);
64 | }
65 |
66 | export function createDownloadArtifactsButton(splatLoader: LumaSplatsLoader) {
67 |
68 |
69 | // add download button
70 | let downloadButton = document.createElement('button');
71 | downloadButton.innerText = 'Download Artifacts';
72 | downloadButton.style.position = 'absolute';
73 | downloadButton.style.bottom = '5px';
74 | downloadButton.style.left = '5px';
75 | downloadButton.style.zIndex = '100';
76 | downloadButton.onclick = (e) => {
77 | downloadArtifacts(splatLoader, progress => {
78 | if (progress < 1) {
79 | downloadButton.innerText = `Downloading ${Math.floor(progress * 100)}%`;
80 | } else {
81 | downloadButton.innerText = 'Download Artifacts';
82 | }
83 | });
84 | };
85 |
86 | return downloadButton;
87 | }
--------------------------------------------------------------------------------
/src/util/Environment.ts:
--------------------------------------------------------------------------------
1 | import { PMREMGenerator, Scene, Texture, WebGLRenderer } from "three";
2 | import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
3 |
4 | export function loadEnvironment(renderer: WebGLRenderer, scene: Scene, path: string) {
5 | // environment
6 | let rgbeLoader = new RGBELoader().load(
7 | path,
8 | (texture: Texture) => {
9 | const pmremGenerator = new PMREMGenerator(renderer)
10 | pmremGenerator.compileEquirectangularShader();
11 | const environment = pmremGenerator.fromEquirectangular(texture).texture;
12 | scene.environment = environment;
13 | scene.background = environment;
14 | scene.backgroundBlurriness = 0.5;
15 |
16 | texture.dispose();
17 | pmremGenerator.dispose();
18 | rgbeLoader.dispose();
19 | }
20 | );
21 | }
--------------------------------------------------------------------------------
/src/util/EnvironmentProbes.ts:
--------------------------------------------------------------------------------
1 | import { Color, Mesh, MeshStandardMaterial, Object3D, SphereGeometry } from "three";
2 |
3 | export class EnvironmentProbes extends Object3D {
4 |
5 | constructor(gridSize: number = 3) {
6 | super();
7 | // add grid of spheres to test lighting
8 | let sphereGeometry = new SphereGeometry(0.05, 32, 32);
9 |
10 | for (let i = 0; i < gridSize; i++) {
11 | for (let j = 0; j < gridSize; j++) {
12 | for (let k = 0; k < gridSize; k++) {
13 | let roughness = i / (gridSize - 1);
14 | let metalness = j / (gridSize - 1);
15 | let color = k / (gridSize - 1);
16 | let sphere = new Mesh(sphereGeometry, new MeshStandardMaterial({
17 | color: new Color(color, color, color),
18 | roughness: roughness,
19 | metalness: metalness,
20 | }));
21 | sphere.position.set(i, j, k).subScalar((gridSize - 1) / 2).multiplyScalar(0.25);
22 | this.add(sphere);
23 | }
24 | }
25 | }
26 | }
27 |
28 | dispose() {
29 | this.traverse((child) => {
30 | if (child instanceof Mesh) {
31 | child.geometry.dispose();
32 | child.material.dispose();
33 | }
34 | });
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/src/util/GUIUtils.ts:
--------------------------------------------------------------------------------
1 | import GUI from "lil-gui";
2 | import { AdditiveBlending, CustomBlending, MeshPhysicalMaterial, MultiplyBlending, NoBlending, NormalBlending, SubtractiveBlending } from "three";
3 |
4 | export function addMaterial(gui: GUI, material: MeshPhysicalMaterial, name: string) {
5 | let materialFolder = gui.addFolder(name);
6 | materialFolder.addColor(material, 'color');
7 | materialFolder.add(material, 'flatShading');
8 | materialFolder.add(material, 'depthWrite');
9 | materialFolder.add(material, 'depthTest');
10 | materialFolder.add(material, 'transparent');
11 | materialFolder.add(material, 'blending', {
12 | NoBlending,
13 | NormalBlending,
14 | AdditiveBlending,
15 | SubtractiveBlending,
16 | MultiplyBlending,
17 | CustomBlending,
18 | });
19 | materialFolder.add(material, 'premultipliedAlpha');
20 | materialFolder.add(material, 'opacity', 0, 1);
21 | materialFolder.add(material, 'metalness', 0, 1);
22 | materialFolder.add(material, 'roughness', 0, 1);
23 | materialFolder.add(material, 'emissiveIntensity', 0, 4);
24 | materialFolder.add(material, 'iridescence', 0, 1);
25 | materialFolder.add(material, 'iridescenceIOR', 0, 3);
26 | let iridescenceThicknessRange = { min: 0, max: 1 };
27 | materialFolder.add(iridescenceThicknessRange, 'min', 0, 1).onChange(() => {
28 | material.iridescenceThicknessRange = [iridescenceThicknessRange.min, iridescenceThicknessRange.max];
29 | });
30 | materialFolder.add(iridescenceThicknessRange, 'max', 0, 1).onChange(() => {
31 | material.iridescenceThicknessRange = [iridescenceThicknessRange.min, iridescenceThicknessRange.max];
32 | });
33 | materialFolder.add(material, 'envMapIntensity', 0, 4);
34 |
35 | // transmission
36 | materialFolder.add(material, 'transmission', 0, 1);
37 | materialFolder.add(material, 'ior', 0, 3);
38 | materialFolder.add(material, 'thickness', 0, 10);
39 | materialFolder.addColor(material, 'attenuationColor');
40 | materialFolder.add(material, 'attenuationDistance', 0, 10);
41 |
42 | // clearcoat
43 | materialFolder.add(material, 'clearcoat', 0, 1);
44 | materialFolder.add(material, 'clearcoatRoughness', 0, 1);
45 | materialFolder.add(material, 'reflectivity', 0, 1);
46 |
47 | return materialFolder;
48 | }
--------------------------------------------------------------------------------
/src/util/MarkdownImport.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.md" {
2 | const value: string;
3 | export default value;
4 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "./src/**/*"
4 | ],
5 | "compilerOptions": {
6 | "outDir": "./dist/",
7 | "noImplicitAny": true,
8 | "module": "ES2020",
9 | "target": "ES2020",
10 | "moduleResolution": "node",
11 | "jsx": "react",
12 | "allowSyntheticDefaultImports": true,
13 | "allowJs": true,
14 | "strict": true,
15 | "skipLibCheck": true,
16 | // we use esbuild to compile and typescript for typechecking
17 | "noEmit": true,
18 | }
19 | }
--------------------------------------------------------------------------------