77</ head >
88
99< body >
10- < img src ="loading.gif " alt ="loading " id ="loading ">
1110 < div id ="menu ">
1211 < div class ="border ">
1312 < div class ="border ">
@@ -36,14 +35,15 @@ <h2>Food scans by <a href="https://x.com/tipatat" target="_blank">Tipatat</a></h
3635 import { OrbitControls } from 'three/addons/controls/OrbitControls.js' ;
3736 import { FOOD_ASSETS , FOOD_URL } from './food.js' ;
3837 import { getAssetFileURL } from "/examples/js/get-asset-url.js" ;
38+ import { preloadSplats } from "/examples/js/preloader.js" ;
3939
4040 // Add food items to the menu
4141 FOOD_ASSETS . forEach ( ( food , i ) => {
4242 const el = document . createElement ( "a" ) ;
4343 el . textContent = food . name ;
4444 el . href = 'javascript:;' ;
4545 el . addEventListener ( 'click' , async function ( ) {
46- await switchToFood ( i ) ;
46+ switchToFood ( i ) ;
4747 } ) ;
4848 document . getElementById ( 'menu_list' ) . appendChild ( el ) ;
4949 } ) ;
@@ -58,50 +58,6 @@ <h2>Food scans by <a href="https://x.com/tipatat" target="_blank">Tipatat</a></h
5858 const camera = new THREE . PerspectiveCamera ( 65 , window . innerWidth / window . innerHeight , 0.1 , 1000 ) ;
5959 camera . position . set ( 0 , 0.9 , - 1.2 ) ;
6060
61- // Setup lighting
62- const spotLight = new THREE . SpotLight ( 0xffcc88 ) ;
63- spotLight . position . set ( 0 , 1 , 0 ) ;
64- spotLight . castShadow = true ;
65- spotLight . shadow . mapSize . width = 1024 ;
66- spotLight . shadow . mapSize . height = 1024 ;
67- spotLight . shadow . camera . near = 0.1 ;
68- spotLight . shadow . camera . far = 5 ;
69- spotLight . angle = 0.9 ;
70- spotLight . penumbra = 1 ;
71- spotLight . intensity = 3 ;
72- scene . add ( spotLight ) ;
73-
74- const fillLight = new THREE . PointLight ( 0xffcc88 , 0.2 ) ;
75- fillLight . position . set ( 0 , 0 , - 3 ) ;
76- scene . add ( fillLight ) ;
77-
78- // Splats don't project shadows, so we add a cylinder below the spotlight to fake one ;)
79- const geometry = new THREE . CylinderGeometry ( 0.45 , 0.45 , 0.04 , 40 , 1 ) ;
80- const material = new THREE . MeshPhongMaterial ( { colorWrite : false , depthWrite : false } ) ;
81- const shadow = new THREE . Mesh ( geometry , material ) ;
82- shadow . visible = false ;
83- shadow . castShadow = true ;
84- shadow . position . set ( 0 , 0.1 , 0 ) ;
85- scene . add ( shadow ) ;
86-
87- // Add table
88- const gltfLoader = new GLTFLoader ( ) ;
89- const modelURL = await getAssetFileURL ( "table.glb" ) ;
90- const gltfTable = await gltfLoader . loadAsync ( modelURL ) ;
91- const table = gltfTable . scene ;
92- // Set the table cloth to receive shadows
93- const tableCloth = table . children . find ( item => item . name == 'cover' ) ;
94- tableCloth . receiveShadow = true ;
95- scene . add ( table ) ;
96-
97- // Add floor
98- const plane = new THREE . PlaneGeometry ( 10 , 10 ) ;
99- const floormat = new THREE . MeshPhongMaterial ( { color : 0x777777 } ) ;
100- const floor = new THREE . Mesh ( plane , floormat ) ;
101- floor . rotation . x = - Math . PI / 2 ;
102- floor . position . set ( 0 , - 1.397 , 0 ) ;
103- scene . add ( floor ) ;
104-
10561 // Setup mouse controls to orbit the camera around
10662 const controls = new OrbitControls ( camera , renderer . domElement ) ;
10763 controls . target . set ( 0.2 , 0 , 0 ) ;
@@ -110,21 +66,83 @@ <h2>Food scans by <a href="https://x.com/tipatat" target="_blank">Tipatat</a></h
11066 controls . enablePan = false ;
11167 controls . update ( ) ;
11268
69+
11370 // Current and next food splats
11471 let food , nextFood ;
72+ // Other items
73+ let table , shadow ;
74+
75+ // Add table
76+ const gltfLoader = new GLTFLoader ( ) ;
77+ const modelURL = await getAssetFileURL ( "table.glb" ) ;
78+ const gltfTable = await gltfLoader . loadAsync ( modelURL ) ;
79+ table = gltfTable . scene ;
11580
116- const loadingIcon = document . getElementById ( 'loading' ) ;
11781 // Transition length in frames.
11882 // Two transitions: one for fading out the old food, another for fading in the next one
11983 const TRANSITION_LENGTH = 60 ;
12084 // Transition timers. `null` if transition is not active
12185 let fadeOutTime = null ;
12286 let fadeInTime = null ;
12387
124- // Load first food by default
125- await switchToFood ( 0 ) ;
88+ // Preload all splat files
89+ let splats ;
90+ preloadSplats ( FOOD_ASSETS . map ( t => t . file ) ) . then ( loaded_splats => {
91+ splats = loaded_splats ;
92+ init ( ) ;
93+ } ) ;
94+
95+ async function init ( ) {
96+ // show menu
97+ document . getElementById ( 'menu' ) . style . display = 'block' ;
98+
99+ // Setup lighting
100+ const spotLight = new THREE . SpotLight ( 0xffcc88 ) ;
101+ spotLight . position . set ( 0 , 1 , 0 ) ;
102+ spotLight . castShadow = true ;
103+ spotLight . shadow . mapSize . width = 1024 ;
104+ spotLight . shadow . mapSize . height = 1024 ;
105+ spotLight . shadow . camera . near = 0.1 ;
106+ spotLight . shadow . camera . far = 5 ;
107+ spotLight . angle = 0.9 ;
108+ spotLight . penumbra = 1 ;
109+ spotLight . intensity = 3 ;
110+ scene . add ( spotLight ) ;
111+
112+ const fillLight = new THREE . PointLight ( 0xffcc88 , 0.2 ) ;
113+ fillLight . position . set ( 0 , 0 , - 3 ) ;
114+ scene . add ( fillLight ) ;
115+
116+ // Splats don't project shadows, so we add a cylinder below the spotlight to fake one ;)
117+ const geometry = new THREE . CylinderGeometry ( 0.45 , 0.45 , 0.04 , 40 , 1 ) ;
118+ const material = new THREE . MeshPhongMaterial ( { colorWrite : false , depthWrite : false } ) ;
119+ shadow = new THREE . Mesh ( geometry , material ) ;
120+ shadow . visible = false ;
121+ shadow . castShadow = true ;
122+ shadow . position . set ( 0 , 0.1 , 0 ) ;
123+ scene . add ( shadow ) ;
124+
125+ // Set the table cloth to receive shadows
126+ const tableCloth = table . children . find ( item => item . name == 'cover' ) ;
127+ tableCloth . receiveShadow = true ;
128+ scene . add ( table ) ;
129+
130+ // Add floor
131+ const plane = new THREE . PlaneGeometry ( 10 , 10 ) ;
132+ const floormat = new THREE . MeshPhongMaterial ( { color : 0x777777 } ) ;
133+ const floor = new THREE . Mesh ( plane , floormat ) ;
134+ floor . rotation . x = - Math . PI / 2 ;
135+ floor . position . set ( 0 , - 1.397 , 0 ) ;
136+ scene . add ( floor ) ;
137+
138+ // Load first food by default
139+ switchToFood ( 0 ) ;
140+
141+ // Start render loop
142+ renderer . setAnimationLoop ( animate ) ;
143+ }
126144
127- renderer . setAnimationLoop ( function animate ( time ) {
145+ function animate ( time ) {
128146 controls . update ( ) ;
129147 renderer . render ( scene , camera ) ;
130148 const rotation = time / 10000 ;
@@ -161,20 +179,17 @@ <h2>Food scans by <a href="https://x.com/tipatat" target="_blank">Tipatat</a></h
161179 fadeInTime = null ;
162180 }
163181 }
164-
165-
166- } ) ;
182+ } ;
167183
168184 // Change food from menu link
169- async function switchToFood ( foodIndex ) {
185+ function switchToFood ( foodIndex ) {
170186
171187 // already transitioning
172188 if ( fadeOutTime !== null || fadeInTime !== null ) return ;
173189
174190 const foodItem = FOOD_ASSETS [ foodIndex ] ;
175191
176- const splatURL = await getAssetFileURL ( foodItem . file ) ;
177- nextFood = new SplatMesh ( { url : splatURL } ) ;
192+ nextFood = splats [ foodItem . file ] ;
178193 nextFood . quaternion . set ( 1 , 0 , 0 , 0 ) ;
179194
180195 // Customize splat depending on the settings set for this food
@@ -188,7 +203,6 @@ <h2>Food scans by <a href="https://x.com/tipatat" target="_blank">Tipatat</a></h
188203 // Setup shadow to the required size, and make it visible only when the splat is initialized
189204 nextFood . initialized . then ( ( ) => {
190205 shadow . visible = false ;
191- loadingIcon . style . display = 'none' ;
192206 fadeOutTime = 0 ; // Fade in next food
193207 } ) ;
194208
@@ -197,10 +211,6 @@ <h2>Food scans by <a href="https://x.com/tipatat" target="_blank">Tipatat</a></h
197211 // toggle menu item
198212 const menu_items = document . getElementById ( 'menu_list' ) . children ;
199213 for ( let i = 0 ; i < menu_items . length ; i ++ ) {
200- if ( foodIndex == i ) {
201- loadingIcon . style . display = 'inline' ;
202- menu_items [ i ] . appendChild ( loadingIcon ) ;
203- }
204214 menu_items [ i ] . classList . toggle ( 'active' , foodIndex == i ) ;
205215 }
206216 }
0 commit comments