|
64 | 64 |
|
65 | 65 | -- PRIVATE |
66 | 66 |
|
67 | | -local function freecamFrame (deltaTime) |
68 | | - freecamMouseApply(deltaTime) |
| 67 | +local function freecamFrame () |
69 | 68 | -- work out an angle in radians based on the number of pixels the cursor has moved (ever) |
70 | 69 | local cameraAngleX = rotX |
71 | 70 | local cameraAngleY = rotY |
@@ -218,103 +217,48 @@ local function freecamFrame (deltaTime) |
218 | 217 | setCameraMatrix ( camPosX, camPosY, camPosZ, camTargetX, camTargetY, camTargetZ, 0, options.fov ) |
219 | 218 | end |
220 | 219 |
|
221 | | --- Internal state (module-level) |
222 | | -local mouseFrameDelay = 0 -- frames to ignore after cursor/window toggles |
223 | | -local accumDX, accumDY = 0, 0 -- accumulated raw mouse deltas (pixels) |
224 | | -local lastEventTick = 0 |
225 | | -local PI, RAD = math.pi, math.pi / 180 |
226 | | - |
227 | | --- Tunables (adjust to taste) |
228 | | -local RESUME_FRAMES = 5 -- frames to wait after cursor/window active |
229 | | -local DEADZONE_PX = 0.15 -- ignore tiny jitters |
230 | | -local MAX_EVENT_DELTA = 200 -- clamp a single event spike (px) |
231 | | -local APPLY_K_60FPS = 0.42 -- how quickly to drain accumulator at 60fps (0..1) |
232 | | - |
233 | | --- Normalize angle to [-PI, PI] |
234 | | -local function normPI(a) |
235 | | - a = a % (2 * PI) |
236 | | - if a > PI then |
237 | | - a = a - 2 * PI |
| 220 | +local function freecamMouse (cX,cY,aX,aY) |
| 221 | + --ignore mouse movement if the cursor or MTA window is on |
| 222 | + --and do not resume it until at least 5 frames after it is toggled off |
| 223 | + --(prevents cursor mousemove data from reaching this handler) |
| 224 | + if isCursorShowing() or isMTAWindowActive() or (not isMTAWindowFocused()) then |
| 225 | + mouseFrameDelay = 5 |
| 226 | + return |
| 227 | + elseif mouseFrameDelay > 0 then |
| 228 | + mouseFrameDelay = mouseFrameDelay - 1 |
| 229 | + return |
238 | 230 | end |
239 | | - return a |
240 | | -end |
241 | | - |
242 | | -function freecamMouse(cX, cY, aX, aY) |
243 | | - -- Gate input when the UI is up / focus lost |
244 | | - if isCursorShowing() or isMTAWindowActive() or (not isMTAWindowFocused()) then |
245 | | - mouseFrameDelay = RESUME_FRAMES |
246 | | - accumDX, accumDY = 0, 0 |
247 | | - return |
248 | | - elseif mouseFrameDelay > 0 then |
249 | | - mouseFrameDelay = mouseFrameDelay - 1 |
250 | | - accumDX, accumDY = 0, 0 |
251 | | - return |
252 | | - end |
253 | 231 |
|
254 | | - -- How far from screen center? |
255 | | - local dx = (aX - width * 0.5) |
256 | | - local dy = (aY - height * 0.5) |
| 232 | + -- how far have we moved the mouse from the screen center? |
| 233 | + aX = aX - width / 2 |
| 234 | + aY = aY - height / 2 |
257 | 235 |
|
258 | | - -- Optional invert |
259 | | - if options.invertMouseLook then |
260 | | - dy = -dy |
261 | | - end |
262 | | - |
263 | | - -- Deadzone + spike clamp |
264 | | - if math.abs(dx) < DEADZONE_PX then |
265 | | - dx = 0 |
266 | | - end |
267 | | - if math.abs(dy) < DEADZONE_PX then |
268 | | - dy = 0 |
269 | | - end |
270 | | - if dx > MAX_EVENT_DELTA then |
271 | | - dx = MAX_EVENT_DELTA |
272 | | - elseif dx < -MAX_EVENT_DELTA then |
273 | | - dx = -MAX_EVENT_DELTA |
274 | | - end |
275 | | - if dy > MAX_EVENT_DELTA then |
276 | | - dy = MAX_EVENT_DELTA |
277 | | - elseif dy < -MAX_EVENT_DELTA then |
278 | | - dy = -MAX_EVENT_DELTA |
| 236 | + --invert the mouse look if specified |
| 237 | + if options.invertMouseLook then |
| 238 | + aY = -aY |
279 | 239 | end |
280 | 240 |
|
281 | | - -- Accumulate; application to rot happens in freecamMouseApply() every frame |
282 | | - accumDX = accumDX + dx |
283 | | - accumDY = accumDY + dy |
284 | | - lastEventTick = getTickCount() |
285 | | -end |
| 241 | + rotX = rotX + aX * options.mouseSensitivity * 0.01745 |
| 242 | + rotY = rotY - aY * options.mouseSensitivity * 0.01745 |
286 | 243 |
|
287 | | -function freecamMouseApply(deltaTime) |
288 | | - -- If UI pops up mid-frame, bail early |
289 | | - if isCursorShowing() or isMTAWindowActive() or (not isMTAWindowFocused()) then |
290 | | - mouseFrameDelay = RESUME_FRAMES |
291 | | - accumDX, accumDY = 0, 0 |
292 | | - return |
293 | | - end |
| 244 | + local PI = math.pi |
| 245 | + if rotX > PI then |
| 246 | + rotX = rotX - 2 * PI |
| 247 | + elseif rotX < -PI then |
| 248 | + rotX = rotX + 2 * PI |
| 249 | + end |
294 | 250 |
|
295 | | - -- Frame-rate–independent smoothing: convert APPLY_K_60FPS to current deltaTime |
296 | | - -- factor = 1 - (1 - k)^(deltaTime * 60ms^-1) |
297 | | - local factor = 1 - ((1 - APPLY_K_60FPS) ^ math.max(deltaTime / 16.666, 0.001)) |
298 | | - |
299 | | - -- Take a smooth chunk out of the accumulator |
300 | | - local useDX = accumDX * factor |
301 | | - local useDY = accumDY * factor |
302 | | - accumDX = accumDX - useDX |
303 | | - accumDY = accumDY - useDY |
304 | | - |
305 | | - -- Convert pixels -> radians (sensitivity is in degrees/pixel) |
306 | | - local rpp = (options.mouseSensitivity or 1) * RAD |
307 | | - |
308 | | - -- Apply to camera (note Y is typically "pitch" and inverted vs screen Y) |
309 | | - rotX = normPI(rotX + useDX * rpp) |
310 | | - rotY = rotY - useDY * rpp |
311 | | - |
312 | | - -- Clamp pitch to avoid gimbal lock / upside-down strafing |
313 | | - local limit = PI / 2.05 |
314 | | - if rotY < -limit then |
315 | | - rotY = -limit |
316 | | - elseif rotY > limit then |
317 | | - rotY = limit |
| 251 | + if rotY > PI then |
| 252 | + rotY = rotY - 2 * PI |
| 253 | + elseif rotY < -PI then |
| 254 | + rotY = rotY + 2 * PI |
| 255 | + end |
| 256 | + -- limit the camera to stop it going too far up or down - PI/2 is the limit, but we can't let it quite reach that or it will lock up |
| 257 | + -- and strafeing will break entirely as the camera loses any concept of what is 'up' |
| 258 | + if rotY < -PI / 2.05 then |
| 259 | + rotY = -PI / 2.05 |
| 260 | + elseif rotY > PI / 2.05 then |
| 261 | + rotY = PI / 2.05 |
318 | 262 | end |
319 | 263 | end |
320 | 264 |
|
|
0 commit comments