/* Controller conditioning is independent of drawing and MAVLink transport. */ ((root)=>{ const clamp=value=>Math.max(-1,Math.min(1,Number.isFinite(value)?value:0)); function deadZone(value,zone=.08){ value=clamp(value);zone=Math.max(0,Math.min(.4,zone)); return Math.abs(value)<=zone?0:Math.sign(value)*(Math.abs(value)-zone)/(1-zone); } function defaults(pad){ return {turn:0,forward:1,vertical:pad?.axes?.length>=4?3:pad?.axes?.length>=3?2:-1, invertTurn:false,invertForward:true,invertVertical:true,deadZone:.08}; } function read(pad,config){ if(!pad?.connected||!pad.axes)return null; const axis=(index,invert)=>{ if(index===-1)return 0; if(!Number.isInteger(index)||index<0||index>=pad.axes.length||!Number.isFinite(pad.axes[index]))return null; return deadZone(pad.axes[index],config.deadZone)*(invert?-1:1); }; let turn=axis(config.turn,config.invertTurn),forward=axis(config.forward,config.invertForward),vertical=axis(config.vertical,config.invertVertical); if(turn===null||forward===null||vertical===null)return null; const length=Math.hypot(turn,forward);if(length>1){turn/=length;forward/=length} return {turn,forward,vertical}; } class Smoother{ constructor(){this.reset()} reset(){this.value=0;this.last=null;this.release=null} step(target,now){ target=clamp(target); const dt=this.last===null?0:Math.max(0,Math.min(.05,(now-this.last)/1000));this.last=now; if(target===0){ if(!this.release)this.release={at:now,start:this.value}; const t=Math.min(1,Math.max(0,(now-this.release.at)/360)),ease=t*t*(3-2*t); this.value=this.release.start*(1-ease); }else{ this.release=null; this.value+=(target-this.value)*(1-Math.exp(-dt/.1)); if(Math.abs(target-this.value)<.001)this.value=target; } return this.value; } } class Motion{ constructor(){this.axes={turn:new Smoother(),forward:new Smoother(),vertical:new Smoother()}} reset(){Object.values(this.axes).forEach(axis=>axis.reset())} step(target,now){ const demand=Object.fromEntries(Object.entries(this.axes).map(([key,axis])=>[key,axis.step(target[key],now)])); const length=Math.hypot(demand.turn,demand.forward);if(length>1){demand.turn/=length;demand.forward/=length} return demand; } } root.ControllerInput={deadZone,defaults,read,Motion}; if(typeof module!=='undefined')module.exports=root.ControllerInput; })(typeof window!=='undefined'?window:globalThis);