253 lines
7.2 KiB
TypeScript
253 lines
7.2 KiB
TypeScript
![]() |
import { useRef, useEffect, useState } from 'react'
|
||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||
|
|
||
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||
|
import { useStore } from 'useStore'
|
||
|
import {
|
||
|
DEBUG_SHOW_BOTH_SCENES,
|
||
|
ReactCameraProperties,
|
||
|
sceneInfra,
|
||
|
} from './sceneInfra'
|
||
|
import { throttle } from 'lib/utils'
|
||
|
|
||
|
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
|
||
|
const [isCamMoving, setIsCamMoving] = useState(false)
|
||
|
const [isTween, setIsTween] = useState(false)
|
||
|
|
||
|
const { state } = useModelingContext()
|
||
|
|
||
|
useEffect(() => {
|
||
|
sceneInfra.setIsCamMovingCallback((isMoving, isTween) => {
|
||
|
setIsCamMoving(isMoving)
|
||
|
setIsTween(isTween)
|
||
|
})
|
||
|
}, [])
|
||
|
|
||
|
if (DEBUG_SHOW_BOTH_SCENES || !isCamMoving)
|
||
|
return { hideClient: false, hideServer: false }
|
||
|
let hideServer = state.matches('Sketch') || state.matches('Sketch no face')
|
||
|
if (isTween) {
|
||
|
hideServer = false
|
||
|
}
|
||
|
|
||
|
return { hideClient: !hideServer, hideServer }
|
||
|
}
|
||
|
|
||
|
export const ClientSideScene = ({
|
||
|
cameraControls,
|
||
|
}: {
|
||
|
cameraControls: ReturnType<
|
||
|
typeof useGlobalStateContext
|
||
|
>['settings']['context']['cameraControls']
|
||
|
}) => {
|
||
|
const canvasRef = useRef<HTMLDivElement>(null)
|
||
|
const { state, send } = useModelingContext()
|
||
|
const { hideClient, hideServer } = useShouldHideScene()
|
||
|
const { setHighlightRange } = useStore((s) => ({
|
||
|
setHighlightRange: s.setHighlightRange,
|
||
|
highlightRange: s.highlightRange,
|
||
|
}))
|
||
|
|
||
|
// Listen for changes to the camera controls setting
|
||
|
// and update the client-side scene's controls accordingly.
|
||
|
useEffect(() => {
|
||
|
sceneInfra.setInteractionGuards(cameraMouseDragGuards[cameraControls])
|
||
|
}, [cameraControls])
|
||
|
useEffect(() => {
|
||
|
sceneInfra.updateOtherSelectionColors(
|
||
|
state?.context?.selectionRanges?.otherSelections || []
|
||
|
)
|
||
|
}, [state?.context?.selectionRanges?.otherSelections])
|
||
|
|
||
|
useEffect(() => {
|
||
|
if (!canvasRef.current) return
|
||
|
const canvas = canvasRef.current
|
||
|
canvas.appendChild(sceneInfra.renderer.domElement)
|
||
|
sceneInfra.animate()
|
||
|
sceneInfra.setHighlightCallback(setHighlightRange)
|
||
|
canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false)
|
||
|
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
|
||
|
canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false)
|
||
|
sceneInfra.setSend(send)
|
||
|
return () => {
|
||
|
canvas?.removeEventListener('mousemove', sceneInfra.onMouseMove)
|
||
|
canvas?.removeEventListener('mousedown', sceneInfra.onMouseDown)
|
||
|
canvas?.removeEventListener('mouseup', sceneInfra.onMouseUp)
|
||
|
}
|
||
|
}, [])
|
||
|
|
||
|
return (
|
||
|
<div
|
||
|
ref={canvasRef}
|
||
|
className={`absolute inset-0 h-full w-full transition-all duration-300 ${
|
||
|
hideClient ? 'opacity-0' : 'opacity-100'
|
||
|
} ${hideServer ? 'bg-black' : ''} ${
|
||
|
!hideClient && !hideServer && state.matches('Sketch')
|
||
|
? 'bg-black/80'
|
||
|
: ''
|
||
|
}`}
|
||
|
></div>
|
||
|
)
|
||
|
}
|
||
|
|
||
|
const throttled = throttle((a: ReactCameraProperties) => {
|
||
|
if (a.type === 'perspective' && a.fov) {
|
||
|
sceneInfra.dollyZoom(a.fov)
|
||
|
}
|
||
|
}, 1000 / 15)
|
||
|
|
||
|
export const CamDebugSettings = () => {
|
||
|
const [camSettings, setCamSettings] = useState<ReactCameraProperties>({
|
||
|
type: 'perspective',
|
||
|
fov: 12,
|
||
|
position: [0, 0, 0],
|
||
|
quaternion: [0, 0, 0, 1],
|
||
|
})
|
||
|
const [fov, setFov] = useState(12)
|
||
|
|
||
|
useEffect(() => {
|
||
|
sceneInfra.setReactCameraPropertiesCallback(setCamSettings)
|
||
|
}, [sceneInfra])
|
||
|
useEffect(() => {
|
||
|
if (camSettings.type === 'perspective' && camSettings.fov) {
|
||
|
setFov(camSettings.fov)
|
||
|
}
|
||
|
}, [(camSettings as any)?.fov])
|
||
|
|
||
|
return (
|
||
|
<div>
|
||
|
<h3>cam settings</h3>
|
||
|
perspective cam
|
||
|
<input
|
||
|
type="checkbox"
|
||
|
checked={camSettings.type === 'perspective'}
|
||
|
onChange={(e) => {
|
||
|
if (camSettings.type === 'perspective') {
|
||
|
sceneInfra.useOrthographicCamera()
|
||
|
} else {
|
||
|
sceneInfra.usePerspectiveCamera()
|
||
|
}
|
||
|
}}
|
||
|
/>
|
||
|
{camSettings.type === 'perspective' && (
|
||
|
<input
|
||
|
type="range"
|
||
|
min="4"
|
||
|
max="90"
|
||
|
step={0.5}
|
||
|
value={fov}
|
||
|
onChange={(e) => {
|
||
|
setFov(parseFloat(e.target.value))
|
||
|
|
||
|
throttled({
|
||
|
...camSettings,
|
||
|
fov: parseFloat(e.target.value),
|
||
|
})
|
||
|
}}
|
||
|
className="w-full cursor-pointer pointer-events-auto"
|
||
|
/>
|
||
|
)}
|
||
|
{camSettings.type === 'perspective' && (
|
||
|
<div>
|
||
|
<span>fov</span>
|
||
|
<input
|
||
|
type="number"
|
||
|
value={camSettings.fov}
|
||
|
className="text-black w-16"
|
||
|
onChange={(e) => {
|
||
|
sceneInfra.setCam({
|
||
|
...camSettings,
|
||
|
fov: parseFloat(e.target.value),
|
||
|
})
|
||
|
}}
|
||
|
/>
|
||
|
</div>
|
||
|
)}
|
||
|
{camSettings.type === 'orthographic' && (
|
||
|
<>
|
||
|
<div>
|
||
|
<span>fov</span>
|
||
|
<input
|
||
|
type="number"
|
||
|
value={camSettings.zoom}
|
||
|
className="text-black w-16"
|
||
|
onChange={(e) => {
|
||
|
sceneInfra.setCam({
|
||
|
...camSettings,
|
||
|
zoom: parseFloat(e.target.value),
|
||
|
})
|
||
|
}}
|
||
|
/>
|
||
|
</div>
|
||
|
</>
|
||
|
)}
|
||
|
<div>
|
||
|
Position
|
||
|
<ul className="flex">
|
||
|
<li>
|
||
|
<span className="pl-2 pr-1">x:</span>
|
||
|
<input
|
||
|
type="number"
|
||
|
step={5}
|
||
|
data-testid="cam-x-position"
|
||
|
value={camSettings.position[0]}
|
||
|
className="text-black w-16"
|
||
|
onChange={(e) => {
|
||
|
sceneInfra.setCam({
|
||
|
...camSettings,
|
||
|
position: [
|
||
|
parseFloat(e.target.value),
|
||
|
camSettings.position[1],
|
||
|
camSettings.position[2],
|
||
|
],
|
||
|
})
|
||
|
}}
|
||
|
/>
|
||
|
</li>
|
||
|
<li>
|
||
|
<span className="pl-2 pr-1">y:</span>
|
||
|
<input
|
||
|
type="number"
|
||
|
step={5}
|
||
|
data-testid="cam-y-position"
|
||
|
value={camSettings.position[1]}
|
||
|
className="text-black w-16"
|
||
|
onChange={(e) => {
|
||
|
sceneInfra.setCam({
|
||
|
...camSettings,
|
||
|
position: [
|
||
|
camSettings.position[0],
|
||
|
parseFloat(e.target.value),
|
||
|
camSettings.position[2],
|
||
|
],
|
||
|
})
|
||
|
}}
|
||
|
/>
|
||
|
</li>
|
||
|
<li>
|
||
|
<span className="pl-2 pr-1">z:</span>
|
||
|
<input
|
||
|
type="number"
|
||
|
step={5}
|
||
|
data-testid="cam-z-position"
|
||
|
value={camSettings.position[2]}
|
||
|
className="text-black w-16"
|
||
|
onChange={(e) => {
|
||
|
sceneInfra.setCam({
|
||
|
...camSettings,
|
||
|
position: [
|
||
|
camSettings.position[0],
|
||
|
camSettings.position[1],
|
||
|
parseFloat(e.target.value),
|
||
|
],
|
||
|
})
|
||
|
}}
|
||
|
/>
|
||
|
</li>
|
||
|
</ul>
|
||
|
</div>
|
||
|
</div>
|
||
|
)
|
||
|
}
|