diff --git a/src/components/MouseAndKeyboardHandler.tsx b/src/components/MouseAndKeyboardHandler.tsx
index 635b471b..f83b7bad 100644
--- a/src/components/MouseAndKeyboardHandler.tsx
+++ b/src/components/MouseAndKeyboardHandler.tsx
@@ -58,8 +58,8 @@ const MIN_SPEED_ADJUSTMENT = 2;
const MAX_SPEED_ADJUSTMENT = 11;
const DRAG_THRESHOLD = 3; // px of movement before it counts as a drag
-/** Shared mouse/look sensitivity used across all modes (.mis, .rec, live). */
-export const MOUSE_SENSITIVITY = 0.002;
+/** Hardcoded drag sensitivity (not affected by user setting). */
+const DRAG_SENSITIVITY = 0.002;
export const ARROW_LOOK_SPEED = 1; // radians/sec
function quantizeSpeed(speedMultiplier: number): number {
@@ -92,8 +92,13 @@ export function MouseAndKeyboardHandler() {
};
}, []);
- const { speedMultiplier, setSpeedMultiplier, invertScroll, invertDrag } =
- useControls();
+ const {
+ speedMultiplier,
+ setSpeedMultiplier,
+ mouseSensitivity,
+ invertScroll,
+ invertDrag,
+ } = useControls();
const { onInput, mode } = useInputContext();
const [subscribe, getKeys] = useKeyboardControls();
const camera = useThree((state) => state.camera);
@@ -104,6 +109,7 @@ export function MouseAndKeyboardHandler() {
const getInvertScroll = useEffectEvent(() => invertScroll);
const getInvertDrag = useEffectEvent(() => invertDrag);
const getMode = useEffectEvent(() => mode);
+ const getMouseSensitivity = useEffectEvent(() => mouseSensitivity);
// Accumulated mouse deltas between frames.
const mouseDeltaYaw = useRef(0);
@@ -145,8 +151,9 @@ export function MouseAndKeyboardHandler() {
const handleMouseMove = (e: MouseEvent) => {
if (controlsRef.current?.isLocked) {
// Pointer is locked: accumulate raw deltas.
- mouseDeltaYaw.current += e.movementX * MOUSE_SENSITIVITY;
- mouseDeltaPitch.current += e.movementY * MOUSE_SENSITIVITY;
+ const sens = getMouseSensitivity();
+ mouseDeltaYaw.current += e.movementX * sens;
+ mouseDeltaPitch.current += e.movementY * sens;
return;
}
@@ -165,8 +172,8 @@ export function MouseAndKeyboardHandler() {
// (decreasing yaw), opposite of fly mode.
const orbitFlip = getMode() === "follow" ? -1 : 1;
const dragSign = (getInvertDrag() ? 1 : -1) * orbitFlip;
- mouseDeltaYaw.current += dragSign * e.movementX * MOUSE_SENSITIVITY;
- mouseDeltaPitch.current += dragSign * e.movementY * MOUSE_SENSITIVITY;
+ mouseDeltaYaw.current += dragSign * e.movementX * DRAG_SENSITIVITY;
+ mouseDeltaPitch.current += dragSign * e.movementY * DRAG_SENSITIVITY;
};
const handleMouseUp = () => {
diff --git a/src/components/SettingsProvider.tsx b/src/components/SettingsProvider.tsx
index acf2653d..11d7cf5e 100644
--- a/src/components/SettingsProvider.tsx
+++ b/src/components/SettingsProvider.tsx
@@ -45,6 +45,8 @@ type DebugContext = {
type ControlsContext = {
speedMultiplier: number;
setSpeedMultiplier: StateSetter;
+ mouseSensitivity: number;
+ setMouseSensitivity: StateSetter;
touchMode: TouchMode;
setTouchMode: StateSetter;
invertScroll: boolean;
@@ -58,6 +60,10 @@ type ControlsContext = {
export const MIN_SPEED_MULTIPLIER = 0.01;
export const MAX_SPEED_MULTIPLIER = 1;
+export const DEFAULT_MOUSE_SENSITIVITY = 16 / 8000; // 0.002
+export const MIN_MOUSE_SENSITIVITY = 1 / 8000;
+export const MAX_MOUSE_SENSITIVITY = 64 / 8000;
+
const SettingsContext = createContext(null);
const DebugContext = createContext(null);
const ControlsContext = createContext(null);
@@ -66,6 +72,7 @@ type PersistedSettings = {
fogEnabled?: boolean;
highQualityFog?: boolean;
speedMultiplier?: number;
+ mouseSensitivity?: number;
fov?: number;
audioEnabled?: boolean;
animationEnabled?: boolean;
@@ -95,6 +102,9 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
const [fogEnabled, setFogEnabled] = useState(true);
const [highQualityFog, setHighQualityFog] = useState(false);
const [speedMultiplier, setSpeedMultiplier] = useState(0.15);
+ const [mouseSensitivity, setMouseSensitivity] = useState(
+ DEFAULT_MOUSE_SENSITIVITY,
+ );
const [fov, setFov] = useState(90);
const [audioEnabled, setAudioEnabled] = useState(false);
const [audioVolume, setAudioVolume] = useState(0.75);
@@ -170,6 +180,8 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
() => ({
speedMultiplier,
setSpeedMultiplier,
+ mouseSensitivity,
+ setMouseSensitivity,
touchMode,
setTouchMode,
invertScroll,
@@ -182,6 +194,7 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
[
speedMultiplier,
setSpeedMultiplier,
+ mouseSensitivity,
touchMode,
setTouchMode,
invertScroll,
@@ -226,6 +239,14 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
),
);
}
+ if (savedSettings.mouseSensitivity != null) {
+ setMouseSensitivity(
+ Math.max(
+ MIN_MOUSE_SENSITIVITY,
+ Math.min(MAX_MOUSE_SENSITIVITY, savedSettings.mouseSensitivity),
+ ),
+ );
+ }
if (savedSettings.fov != null) {
setFov(savedSettings.fov);
}
@@ -270,6 +291,7 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
fogEnabled,
highQualityFog,
speedMultiplier,
+ mouseSensitivity,
fov,
audioEnabled,
animationEnabled,
@@ -298,6 +320,7 @@ export function SettingsProvider({ children }: { children: ReactNode }) {
fogEnabled,
highQualityFog,
speedMultiplier,
+ mouseSensitivity,
fov,
audioEnabled,
animationEnabled,