feat: improve thumbnails, placement, and editor responsiveness (#258)
* wip: backport monorepo PRs 278 280 281 283 284 * refactor(editor): align floorplan panel with monorepo split version
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
"@react-three/drei": "^10",
|
||||
"@react-three/fiber": "^9",
|
||||
"react": "^18 || ^19",
|
||||
"three": "^0.183"
|
||||
"three": "^0.184"
|
||||
},
|
||||
"dependencies": {
|
||||
"polygon-clipping": "^0.15.7",
|
||||
@@ -36,7 +36,7 @@
|
||||
"@pascal/typescript-config": "*",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/three": "^0.183.0",
|
||||
"@types/three": "^0.184.0",
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { useThree } from '@react-three/fiber'
|
||||
import { useLayoutEffect } from 'react'
|
||||
|
||||
type FrameLimiterProps = {
|
||||
fps?: number
|
||||
}
|
||||
|
||||
const FrameLimiter: React.FC<FrameLimiterProps> = ({ fps = 50 }) => {
|
||||
const { advance, set, frameloop: initFrameloop } = useThree()
|
||||
|
||||
useLayoutEffect(() => {
|
||||
let elapsed = 0
|
||||
let then = 0
|
||||
let i = 0
|
||||
let raf: number | null = null
|
||||
const interval = 1000 / fps
|
||||
|
||||
function tick(t: DOMHighResTimeStamp) {
|
||||
raf = requestAnimationFrame(tick)
|
||||
elapsed = t - then
|
||||
if (elapsed > interval) {
|
||||
advance(i)
|
||||
i += elapsed / 1000 - (elapsed % interval) / 1000
|
||||
then = t - (elapsed % interval)
|
||||
}
|
||||
}
|
||||
|
||||
set({ frameloop: 'never' })
|
||||
raf = requestAnimationFrame(tick)
|
||||
|
||||
return () => {
|
||||
if (raf) {
|
||||
cancelAnimationFrame(raf)
|
||||
}
|
||||
set({ frameloop: initFrameloop })
|
||||
}
|
||||
}, [fps, advance, set, initFrameloop])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default FrameLimiter
|
||||
@@ -23,6 +23,7 @@ import { ScanSystem } from '../../systems/scan/scan-system'
|
||||
import { WallCutout } from '../../systems/wall/wall-cutout'
|
||||
import { ZoneSystem } from '../../systems/zone/zone-system'
|
||||
import { SceneRenderer } from '../renderers/scene-renderer'
|
||||
import FrameLimiter from './frame-limiter'
|
||||
import { Lights } from './lights'
|
||||
import { PerfMonitor } from './perf-monitor'
|
||||
import PostProcessing from './post-processing'
|
||||
@@ -69,18 +70,27 @@ extend(THREE as any)
|
||||
* - Driver crash or GPU reset
|
||||
* - Browser security policy kills the context
|
||||
*/
|
||||
type WebGPUDeviceLossInfo = {
|
||||
reason?: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
type WebGPUDeviceLike = {
|
||||
lost: Promise<WebGPUDeviceLossInfo>
|
||||
}
|
||||
|
||||
function GPUDeviceWatcher() {
|
||||
const gl = useThree((s) => s.gl)
|
||||
|
||||
useEffect(() => {
|
||||
const backend = (gl as any).backend
|
||||
const device: GPUDevice | undefined = backend?.device
|
||||
const device = backend?.device as WebGPUDeviceLike | undefined
|
||||
|
||||
if (!device) return
|
||||
|
||||
device.lost.then((info) => {
|
||||
device.lost.then((info: WebGPUDeviceLossInfo) => {
|
||||
console.error(
|
||||
`[viewer] WebGPU device lost: reason="${info.reason}", message="${info.message}". ` +
|
||||
`[viewer] WebGPU device lost: reason="${info.reason ?? 'unknown'}", message="${info.message ?? ''}". ` +
|
||||
'The page must be reloaded to recover the GPU context.',
|
||||
)
|
||||
})
|
||||
@@ -106,6 +116,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
||||
camera={{ position: [50, 50, 50], fov: 50 }}
|
||||
className={`transition-colors duration-700 ${theme === 'dark' ? 'bg-[#1f2433]' : 'bg-[#fafafa]'}`}
|
||||
dpr={[1, 1.5]}
|
||||
frameloop="never"
|
||||
gl={async (props) => {
|
||||
const renderer = new THREE.WebGPURenderer(props as any)
|
||||
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
||||
@@ -127,6 +138,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
||||
enabled: true,
|
||||
}}
|
||||
>
|
||||
<FrameLimiter fps={50} />
|
||||
{/* <AnimatedBackground isDark={theme === 'dark'} /> */}
|
||||
<ViewerCamera />
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ const PostProcessingPasses = () => {
|
||||
// loop fights the direct-render fallback path. Short-circuit here so
|
||||
// `useFrame` uses the direct `renderer.render(scene, camera)` path
|
||||
// exclusively and never attempts the TSL pipeline.
|
||||
const hasWebGPU = typeof navigator !== 'undefined' && typeof navigator.gpu !== 'undefined'
|
||||
const hasWebGPU = typeof navigator !== 'undefined' && 'gpu' in navigator
|
||||
if (!hasWebGPU) {
|
||||
console.warn(
|
||||
'[viewer] WebGPU unavailable — rendering without post-processing (SSGI, outlines, denoise).',
|
||||
|
||||
Reference in New Issue
Block a user