@@ -2,7 +2,7 @@ import { Canvas } from '@react-three/fiber';
2
2
import { XR , XROrigin , createXRStore } from '@react-three/xr' ;
3
3
import * as THREE from 'three' ;
4
4
import ImmersiveImageMesh from './ImmersiveImageMesh' ;
5
- import { useRef , useState } from 'react' ;
5
+ import { useEffect , useRef , useState } from 'react' ;
6
6
import { PerspectiveCamera } from '@react-three/drei' ;
7
7
8
8
const store = createXRStore ( ) ;
@@ -14,12 +14,37 @@ const cameraLayers = new THREE.Layers();
14
14
cameraLayers . enable ( 0 ) ;
15
15
cameraLayers . enable ( 1 ) ;
16
16
17
+ const clamp = ( val , min , max ) => {
18
+ return Math . min ( Math . max ( val , min ) , max ) ;
19
+ }
20
+
21
+ const linearScale = ( factor , minInput , maxInput , minOutput , maxOutput , shouldClamp = true ) => {
22
+ if ( shouldClamp ) {
23
+ factor = clamp ( factor , minInput , maxInput ) ;
24
+ }
25
+
26
+ return minOutput + ( maxOutput - minOutput ) *
27
+ ( factor - minInput ) / ( maxInput - minInput ) ;
28
+ }
29
+
17
30
export default function Viewer ( ) {
18
31
const [ imageSrc , setImageSrc ] = useState ( "" ) ;
19
32
const [ isInXR , setIsInXR ] = useState ( false ) ;
20
33
const [ isDraggingOver , setIsDraggingOver ] = useState ( false ) ;
21
34
const cameraRef = useRef ( ) ;
22
35
36
+ const resetFOV = ( ) => {
37
+ // Experimentally determined values
38
+ cameraRef . current . fov = linearScale ( window . innerWidth / window . innerHeight , 0.30 , 2.5 , 120 , 70 , true ) ;
39
+ cameraRef . current . updateProjectionMatrix ( ) ;
40
+ }
41
+
42
+ const handleResize = ( ) => {
43
+ if ( ! isInXR && cameraRef . current ) {
44
+ resetFOV ( ) ;
45
+ }
46
+ } ;
47
+
23
48
const processBrowsedFile = ( e ) => {
24
49
if ( ! ( e . target . files && e . target . files . length ) ) return ;
25
50
@@ -39,8 +64,7 @@ export default function Viewer() {
39
64
if ( ! newIsInXR && cameraRef . current ) {
40
65
cameraRef . current . rotation . set ( ...DEFAULT_CAMERA_ROTATION ) ;
41
66
cameraRef . current . position . set ( ...DEFAULT_CAMERA_POSITION_M ) ;
42
- cameraRef . current . fov = DEFAULT_FOV ;
43
- cameraRef . current . updateProjectionMatrix ( ) ;
67
+ resetFOV ( ) ;
44
68
}
45
69
46
70
setIsInXR ( newIsInXR ) ;
@@ -73,6 +97,13 @@ export default function Viewer() {
73
97
setIsDraggingOver ( false ) ;
74
98
} ;
75
99
100
+ useEffect ( ( ) => {
101
+ window . addEventListener ( 'resize' , handleResize ) ;
102
+ return ( ) => {
103
+ window . removeEventListener ( 'resize' , handleResize ) ;
104
+ } ;
105
+ } , [ ] ) ;
106
+
76
107
return (
77
108
< div className = 'w-full h-full min-h-screen bg-black absolute flex items-center' onDragOver = { handleDragOver } onDrop = { handleDrop } onDragLeave = { handleDragLeave } >
78
109
< Canvas
@@ -90,14 +121,16 @@ export default function Viewer() {
90
121
</ XR >
91
122
</ Canvas >
92
123
93
- < div className = 'fixed left-1/2 -translate-x-1/2 bottom-0 bg-white/80 flex justify-center gap-4 md:gap-12 p-4 rounded-t-md' >
94
- < div className = 'flex flex-col gap-1' >
95
- < p className = 'font-semibold' > Drop or select an Apple Spatial Photo < span className = 'font-mono' > (.heic)</ span > </ p >
96
- < input className = 'cursor-pointer' type = "file" accept = ".heic" onChange = { processBrowsedFile } />
97
- < p className = 'text-xs italic' > Your photo is processed on your device.</ p >
98
- </ div >
124
+ < div className = 'fixed w-full flex justify-center bottom-0' >
125
+ < div className = 'absolute mx-4 bottom-4 bg-white/80 flex justify-center items-stretch flex-wrap gap-y-4 gap-x-8 p-2 md:p-4 rounded-md' >
126
+ < div className = 'flex flex-col gap-1' >
127
+ < p className = 'font-semibold' > Drop or select an Apple Spatial Photo < span className = 'font-mono' > (.heic)</ span > </ p >
128
+ < input className = 'cursor-pointer' type = "file" accept = ".heic" onChange = { processBrowsedFile } />
129
+ < p className = 'text-xs italic' > Your photo is processed on your device.</ p >
130
+ </ div >
99
131
100
- < button className = 'bg-amber-600 outline-double outline-0 hover:outline-2 active:outline-4 focus:outline-2 outline-white text-white px-8 py-2 font-semibold text-xl rounded-md transition-all duration-[25ms]' onClick = { onXRButtonClicked } > { isInXR ? 'Exit' : 'Enter' } VR</ button >
132
+ < button className = 'bg-amber-600 outline-double outline-0 hover:outline-2 active:outline-4 focus:outline-2 outline-white text-white px-8 py-2 font-semibold text-xl rounded-md transition-all duration-[25ms]' onClick = { onXRButtonClicked } > { isInXR ? 'Exit' : 'Enter' } VR</ button >
133
+ </ div >
101
134
</ div >
102
135
103
136
< div className = { `absolute inset-0 rounded-md border-dashed border-2 border-white bg-neutral-800/90 text-amber-500 font-semibold text-xl md:text-2xl flex-col gap-2 justify-center items-center ${ isDraggingOver ? "flex" : "hidden" } pointer-events-none` } >
0 commit comments