Skip to content

Commit 18dc389

Browse files
authored
Merge pull request #3 from EnchantmentEngine/fix-web-xr
Fix WebXR
2 parents 0028eda + e266f5a commit 18dc389

File tree

10 files changed

+770
-496
lines changed

10 files changed

+770
-496
lines changed

resources.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -645,13 +645,13 @@
645645
"Model"
646646
],
647647
"dependencies": [
648-
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/1a8a75bafb18c29e94be4f6e0df4503932791c732fd5b85f5991993ffef3199e.ktx2",
649-
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/1f68862e8529230781b98f1825852cb37039ab137a796da4e1a54368ab308693.ktx2",
650-
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/3ebba2b690b91a31064fec6d6ca9e1558f4f56ddb804294c4663017e2a2ae8fc.bin",
651-
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/5cbedd9382f99d1be408692e8d3b4172edb4aa5edcd9d90bca57f7c6b7db4e2b.bin",
652-
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/9613d363e55e1af7cfb0dfb020bae9e417ccb11f6051da379ac96aa35cba59a0.ktx2",
648+
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/ffd667f5c4c57f61a582c47337035d704ae616104df6ee505b3c715455c4c11c.ktx2",
653649
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/Adam_data.bin",
654-
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/ffd667f5c4c57f61a582c47337035d704ae616104df6ee505b3c715455c4c11c.ktx2"
650+
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/9613d363e55e1af7cfb0dfb020bae9e417ccb11f6051da379ac96aa35cba59a0.ktx2",
651+
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/5cbedd9382f99d1be408692e8d3b4172edb4aa5edcd9d90bca57f7c6b7db4e2b.bin",
652+
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/3ebba2b690b91a31064fec6d6ca9e1558f4f56ddb804294c4663017e2a2ae8fc.bin",
653+
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/1f68862e8529230781b98f1825852cb37039ab137a796da4e1a54368ab308693.ktx2",
654+
"projects/enchantmentengine/ee-development-test-suite/public/avatars/Adam/1a8a75bafb18c29e94be4f6e0df4503932791c732fd5b85f5991993ffef3199e.ktx2"
655655
],
656656
"name": "Adam.gltf",
657657
"thumbnailKey": "projects/enchantmentengine/ee-development-test-suite/public/thumbnails/public_avatars_Adam.gltf-thumbnail.png",

src/devtool/EmulatorDevtools.tsx

Lines changed: 198 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useHookstate, useImmediateEffect, useMutableState } from '@ir-engine/hyperflux'
22
import { endXRSession, requestXRSession } from '@ir-engine/spatial/src/xr/XRSessionFunctions'
33
import Button from '@ir-engine/ui/src/primitives/tailwind/Button'
4-
import React from 'react'
4+
import React, { useEffect, useRef, useState } from 'react'
55

66
import EmulatedDevice from './js/emulatedDevice'
77
import { EmulatorSettings, emulatorStates } from './js/emulatorStates'
@@ -15,15 +15,8 @@ import { WebXREventDispatcher } from '@ir-engine/spatial/tests/webxr/emulator/We
1515
import { POLYFILL_ACTIONS } from '@ir-engine/spatial/tests/webxr/emulator/actions'
1616

1717
export async function overrideXR(args: { mode: 'immersive-vr' | 'immersive-ar' }) {
18-
// inject the webxr polyfill from the webxr emulator source - this is a script added by the bot
19-
// globalThis.WebXRPolyfillInjection()
20-
2118
const { CustomWebXRPolyfill } = await import('@ir-engine/spatial/tests/webxr/emulator/CustomWebXRPolyfill')
2219
new CustomWebXRPolyfill()
23-
// override session supported request, it hangs indefinitely for some reason
24-
;(navigator as any).xr.isSessionSupported = () => {
25-
return true
26-
}
2720

2821
const deviceDefinition = {
2922
id: 'Oculus Quest',
@@ -74,7 +67,6 @@ const setup = async (mode: 'immersive-vr' | 'immersive-ar') => {
7467

7568
return device
7669
}
77-
7870
export const EmulatorDevtools = (props: { mode: 'immersive-vr' | 'immersive-ar' }) => {
7971
const xrState = useMutableState(XRState)
8072
const xrActive = xrState.sessionActive.value && !xrState.requestingSession.value
@@ -86,6 +78,24 @@ export const EmulatorDevtools = (props: { mode: 'immersive-vr' | 'immersive-ar'
8678
})
8779
}, [])
8880

81+
// Panel state
82+
const [panelPosition, setPanelPosition] = useState({ x: window.innerWidth - 720, y: 20 })
83+
const [panelSize, setPanelSize] = useState({ width: 700, height: 900 })
84+
const [isDragging, setIsDragging] = useState(false)
85+
const [isResizing, setIsResizing] = useState(false)
86+
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 })
87+
const [resizeDirection, setResizeDirection] = useState('')
88+
const [isVisible, setIsVisible] = useState(false)
89+
const [isMinimized, setIsMinimized] = useState(false)
90+
91+
const panelRef = useRef<HTMLDivElement>(null)
92+
const resizeHandleRef = useRef<HTMLDivElement>(null)
93+
94+
// Refs for performance optimization
95+
const dragPosRef = useRef(panelPosition)
96+
const sizeRef = useRef(panelSize)
97+
const rafRef = useRef<number | null>(null)
98+
8999
const toggleXR = async () => {
90100
if (xrActive) {
91101
endXRSession()
@@ -104,24 +114,192 @@ export const EmulatorDevtools = (props: { mode: 'immersive-vr' | 'immersive-ar'
104114
}
105115
}
106116

117+
const handleClosePanel = () => setIsVisible(false)
118+
119+
const handleMinimizePanel = () => {
120+
setIsMinimized(!isMinimized)
121+
setPanelSize((prev) => ({
122+
...prev,
123+
height: isMinimized ? 600 : 50
124+
}))
125+
}
126+
127+
// Mouse event handlers for dragging
128+
const handleMouseDown = (e: React.MouseEvent) => {
129+
const target = e.target as HTMLElement
130+
const isHeader = target.closest('.floating-panel-header')
131+
const isPanel = target === panelRef.current
132+
const isResizeHandle = target.closest('.resize-handle')
133+
134+
if (isHeader || isPanel) {
135+
e.preventDefault()
136+
setIsDragging(true)
137+
const rect = panelRef.current?.getBoundingClientRect()
138+
if (rect) {
139+
setDragOffset({
140+
x: e.clientX - rect.left,
141+
y: e.clientY - rect.top
142+
})
143+
}
144+
}
145+
}
146+
147+
const updatePanelPosition = (x: number, y: number) => {
148+
dragPosRef.current = { x, y }
149+
if (rafRef.current === null) {
150+
rafRef.current = requestAnimationFrame(() => {
151+
setPanelPosition({ ...dragPosRef.current })
152+
rafRef.current = null
153+
})
154+
}
155+
}
156+
157+
const updatePanelSize = (width: number, height: number) => {
158+
sizeRef.current = { width, height }
159+
if (rafRef.current === null) {
160+
rafRef.current = requestAnimationFrame(() => {
161+
setPanelSize({ ...sizeRef.current })
162+
rafRef.current = null
163+
})
164+
}
165+
}
166+
167+
const handleMouseMove = (e: MouseEvent) => {
168+
if (isDragging) {
169+
e.preventDefault()
170+
const newX = e.clientX - dragOffset.x
171+
const newY = e.clientY - dragOffset.y
172+
173+
const maxX = window.innerWidth - panelSize.width
174+
const maxY = window.innerHeight - panelSize.height
175+
176+
updatePanelPosition(Math.max(0, Math.min(newX, maxX)), Math.max(0, Math.min(newY, maxY)))
177+
} else if (isResizing) {
178+
e.preventDefault()
179+
let width = sizeRef.current.width
180+
let height = sizeRef.current.height
181+
182+
if (resizeDirection.includes('e')) {
183+
width = e.clientX - panelPosition.x
184+
}
185+
if (resizeDirection.includes('s')) {
186+
height = e.clientY - panelPosition.y
187+
}
188+
189+
width = Math.max(300, Math.min(width, window.innerWidth - panelPosition.x))
190+
height = Math.max(400, Math.min(height, window.innerHeight - panelPosition.y))
191+
192+
updatePanelSize(width, height)
193+
}
194+
}
195+
196+
const handleMouseUp = () => {
197+
setIsDragging(false)
198+
setIsResizing(false)
199+
setResizeDirection('')
200+
if (rafRef.current !== null) {
201+
cancelAnimationFrame(rafRef.current)
202+
rafRef.current = null
203+
}
204+
}
205+
206+
const handleResizeMouseDown = (direction: string) => (e: React.MouseEvent) => {
207+
e.preventDefault()
208+
e.stopPropagation()
209+
setIsResizing(true)
210+
setResizeDirection(direction)
211+
}
212+
213+
useEffect(() => {
214+
document.addEventListener('mousemove', handleMouseMove)
215+
document.addEventListener('mouseup', handleMouseUp)
216+
return () => {
217+
document.removeEventListener('mousemove', handleMouseMove)
218+
document.removeEventListener('mouseup', handleMouseUp)
219+
}
220+
}, [isDragging, isResizing, dragOffset, panelPosition, panelSize, resizeDirection])
221+
222+
if (!isVisible) {
223+
return (
224+
<>
225+
<style type="text/css">{devtoolCSS.toString()}</style>
226+
<button className="show-panel-btn" onClick={() => setIsVisible(true)}>
227+
Show XR Devtool
228+
</button>
229+
</>
230+
)
231+
}
232+
107233
return (
108234
<>
109235
<style type="text/css">{devtoolCSS.toString()}</style>
236+
110237
<div
111-
id="devtools"
112-
className="flex-no-wrap m-0 flex h-full h-full select-none flex-col overflow-hidden overflow-hidden bg-gray-900 text-xs text-gray-900"
238+
ref={panelRef}
239+
className={`floating-devtool-panel ${isMinimized ? 'minimized' : ''}`}
240+
style={{
241+
left: panelPosition.x,
242+
top: panelPosition.y,
243+
width: panelSize.width,
244+
height: panelSize.height,
245+
cursor: isDragging ? 'grabbing' : 'grab'
246+
}}
247+
onMouseDown={handleMouseDown}
113248
>
114-
<div className="flex-no-wrap z-50 flex h-10 select-none flex-row bg-gray-800 text-xs text-gray-900">
115-
<Button className="my-1 ml-auto mr-6 px-10" onClick={toggleXR} disabled={xrState.requestingSession.value}>
116-
{(xrActive ? 'Exit ' : 'Enter ') + (props.mode === 'immersive-ar' ? 'AR' : 'VR')}
117-
</Button>
118-
{props.mode === 'immersive-ar' && (
119-
<Button className="my-1 ml-auto mr-6 px-10" onClick={togglePlacement} disabled={!xrActive}>
120-
Place Scene
249+
<div className="floating-panel-header" style={{ cursor: isDragging ? 'grabbing' : 'grab' }}>
250+
<div className="text-sm font-medium text-white">XR Devtool Panel</div>
251+
<div className="flex gap-2">
252+
<button
253+
className="panel-control-btn bg-gray-600 text-white hover:bg-gray-500"
254+
onClick={handleMinimizePanel}
255+
title={isMinimized ? 'Maximize' : 'Minimize'}
256+
>
257+
{isMinimized ? '□' : '−'}
258+
</button>
259+
<Button
260+
className="bg-gray-600 px-3 py-1 text-xs text-white hover:bg-gray-500"
261+
onClick={toggleXR}
262+
disabled={xrState.requestingSession.value}
263+
>
264+
{(xrActive ? 'Exit ' : 'Enter ') + (props.mode === 'immersive-ar' ? 'AR' : 'VR')}
121265
</Button>
122-
)}
266+
{props.mode === 'immersive-ar' && (
267+
<Button
268+
className="bg-gray-600 px-3 py-1 text-xs text-white hover:bg-gray-500"
269+
onClick={togglePlacement}
270+
disabled={!xrActive}
271+
>
272+
Place Scene
273+
</Button>
274+
)}
275+
<button
276+
className="panel-control-btn bg-red-600 text-white hover:bg-red-700"
277+
onClick={handleClosePanel}
278+
title="Close"
279+
>
280+
×
281+
</button>
282+
</div>
123283
</div>
124-
{deviceState.value && <Devtool device={deviceState.value} />}
284+
285+
<div className="floating-panel-content" style={{ opacity: isMinimized ? 0 : 1 }}>
286+
<div className="floating-panel-scroll">
287+
{deviceState.value ? (
288+
<Devtool device={deviceState.value} />
289+
) : (
290+
<div className="flex h-full items-center justify-center text-gray-400">
291+
<div className="text-center">
292+
<div className="mx-auto mb-2 h-8 w-8 animate-spin rounded-full border-b-2 border-gray-400"></div>
293+
<div>Initializing XR Devtool...</div>
294+
</div>
295+
</div>
296+
)}
297+
</div>
298+
</div>
299+
300+
{!isMinimized && (
301+
<div ref={resizeHandleRef} className="resize-handle" onMouseDown={handleResizeMouseDown('se')} />
302+
)}
125303
</div>
126304
</>
127305
)

0 commit comments

Comments
 (0)