Files
gen1recomp/src/core/Sensors.lua
T
bryanthaboi d622e83a8e shaderfix
2026-08-25 09:16:59 -04:00

186 lines
6.7 KiB
Lua

-- Accelerometer and gyroscope reads for the tilt-driven shader presets, plus
-- screen-rotation compensation. love.sensor does not exist in LOVE 11.5 on any
-- platform, so the working path is raw FFI into the SDL2 LOVE already links,
-- the same technique src/core/Orientation.lua uses. What is and is not verified
-- on real hardware is written down in docs/shaderfx.md.
local Sensors = {}
-- Test seams: a no-device harness injects synthetic readings.
local overrides = {}
local orientationOverride = nil
function Sensors.setOverride(kind, x, y, z)
overrides[kind] = { x, y, z }
end
function Sensors.clearOverride(kind)
overrides[kind] = nil
end
function Sensors.clearAllOverrides()
overrides = {}
orientationOverride = nil
end
-- "landscape" | "landscapeFlipped" | "portrait" | "portraitFlipped" | nil
-- (nil = ask SDL for real). Test seam for rotateForScreen below.
function Sensors.setOrientationOverride(o)
orientationOverride = o
end
-- ---- love.sensor path (kept for a future LOVE 12 upgrade; always nil today).
local function safeHasSensor(kind)
if not love or not love.sensor then return false end
local ok, has = pcall(love.sensor.hasSensor, kind)
return ok and has == true
end
local function loveSensorRead(kind)
if not safeHasSensor(kind) then return nil end
local ok, x, y, z = pcall(love.sensor.getData, kind)
if not ok then return nil end
return x or 0, y or 0, z or 0
end
-- ---- raw SDL2 FFI path (the one that actually works on LÖVE 11.5).
local SDL_SENSOR_ACCEL, SDL_SENSOR_GYRO = 1, 2
local SDL_INIT_SENSOR = 0x00008000
local KIND_TO_SDL_TYPE = { accelerometer = SDL_SENSOR_ACCEL, gyroscope = SDL_SENSOR_GYRO }
local sdlCdefOk = nil
local sdlSensorHandles = {} -- kind -> SDL_Sensor* (nil once confirmed absent)
local sdlSensorTried = {}
-- ffi.load("SDL2") first, needed on desktop where SDL2 is a separate library,
-- then bare ffi.C, needed on Android where love-android links SDL2 statically
-- into libmain.so and there is no standalone libSDL2.so to attach to by name
-- (Orientation.lua's own sdlFfi resolves the same way on real hardware, #592/#716).
local sdlLib = nil
local function sdlFfi()
local okFfi, ffi = pcall(require, "ffi")
if not okFfi then return nil end
if sdlCdefOk == nil then
sdlCdefOk = pcall(ffi.cdef, [[
typedef struct SDL_Window SDL_Window;
typedef struct SDL_Sensor SDL_Sensor;
int SDL_InitSubSystem(unsigned int flags);
int SDL_NumSensors(void);
int SDL_SensorGetDeviceType(int device_index);
SDL_Sensor *SDL_SensorOpen(int device_index);
void SDL_SensorUpdate(void);
int SDL_SensorGetData(SDL_Sensor *sensor, float *data, int num_values);
SDL_Window *SDL_GL_GetCurrentWindow(void);
int SDL_GetWindowDisplayIndex(SDL_Window *window);
int SDL_GetDisplayOrientation(int displayIndex);
]])
if sdlCdefOk then
local okLoad, lib = pcall(ffi.load, "SDL2")
lib = okLoad and lib or ffi.C
local okSyms = pcall(function()
return lib.SDL_InitSubSystem, lib.SDL_NumSensors, lib.SDL_SensorGetDeviceType,
lib.SDL_SensorOpen, lib.SDL_SensorUpdate, lib.SDL_SensorGetData,
lib.SDL_GL_GetCurrentWindow, lib.SDL_GetWindowDisplayIndex,
lib.SDL_GetDisplayOrientation
end)
sdlLib = okSyms and lib or false
end
end
if not sdlCdefOk or not sdlLib then return nil end
return sdlLib
end
-- Opens and caches once, and returns nil permanently once a real attempt finds
-- none, so a no-hardware desktop run pays this cost once rather than per frame.
local function sdlSensorHandle(lib, kind)
if sdlSensorTried[kind] then return sdlSensorHandles[kind] end
sdlSensorTried[kind] = true
local wantType = KIND_TO_SDL_TYPE[kind]
if not wantType then return nil end
local okInit = pcall(lib.SDL_InitSubSystem, SDL_INIT_SENSOR)
if not okInit then return nil end
local okCount, count = pcall(lib.SDL_NumSensors)
if not okCount or count <= 0 then return nil end
for i = 0, count - 1 do
local okType, t = pcall(lib.SDL_SensorGetDeviceType, i)
if okType and t == wantType then
local okOpen, handle = pcall(lib.SDL_SensorOpen, i)
if okOpen and handle ~= nil then
sdlSensorHandles[kind] = handle
return handle
end
end
end
return nil
end
local function sdlSensorRead(kind)
local lib = sdlFfi()
if not lib then return nil end
local handle = sdlSensorHandle(lib, kind)
if not handle then return nil end
pcall(lib.SDL_SensorUpdate)
local data = require("ffi").new("float[3]")
local okGet, n = pcall(lib.SDL_SensorGetData, handle, data, 3)
if not okGet or n ~= 0 then return nil end
return data[0], data[1], data[2]
end
-- ---- screen-rotation compensation (x/y only; z is perpendicular to the screen
-- and unaffected by an in-plane rotation). Mobile-only: a desktop monitor is
-- legitimately, permanently "landscape" to SDL_GetDisplayOrientation.
local function isMobile()
if not love or not love.system or not love.system.getOS then return false end
local os = love.system.getOS()
return os == "Android" or os == "iOS"
end
local function sdlOrientation()
if not isMobile() then return nil end
local lib = sdlFfi()
if not lib then return nil end
local okWin, win = pcall(lib.SDL_GL_GetCurrentWindow)
if not okWin or win == nil then return nil end
local okIdx, idx = pcall(lib.SDL_GetWindowDisplayIndex, win)
if not okIdx or idx < 0 then return nil end
local okOr, o = pcall(lib.SDL_GetDisplayOrientation, idx)
if not okOr then return nil end
-- SDL_DisplayOrientation: 0=UNKNOWN, 1=LANDSCAPE, 2=LANDSCAPE_FLIPPED,
-- 3=PORTRAIT, 4=PORTRAIT_FLIPPED.
if o == 1 then return "landscape"
elseif o == 2 then return "landscapeFlipped"
elseif o == 3 then return "portrait"
elseif o == 4 then return "portraitFlipped"
else return nil end
end
-- Remaps raw device-frame (x,y) into "as currently displayed" (x,y). portrait,
-- and unknown/undetectable (desktop always lands here), is a no-op.
local function rotateForScreen(x, y)
local o = orientationOverride or sdlOrientation()
if o == "landscape" then return -y, x
elseif o == "landscapeFlipped" then return y, -x
elseif o == "portraitFlipped" then return -x, -y
else return x, y end
end
-- Returns x, y, z for "accelerometer" | "gyroscope". {0,0,0} when overridden
-- that way, no hardware/module is available, or an underlying call errors.
-- x/y are rotated to be screen-relative; z passes through unchanged.
function Sensors.read(kind)
local override = overrides[kind]
if override then
local x, y = rotateForScreen(override[1], override[2])
return x, y, override[3]
end
local x, y, z = loveSensorRead(kind)
if not x then x, y, z = sdlSensorRead(kind) end
if not x then return 0, 0, 0 end
x, y = rotateForScreen(x, y)
return x, y, z
end
return Sensors