From eff682bc05b0954baf17c447203ad864c26a035f Mon Sep 17 00:00:00 2001 From: TrueZhuanjia Date: Thu, 9 Oct 2025 09:58:57 +0800 Subject: [PATCH] feat: minimum stick offset for gyro -> stick mapping --- VoidLink/Base.lproj/iPad.storyboard | 30 ++++++++++++++++--- VoidLink/Base.lproj/iPhone.storyboard | 30 ++++++++++++++++--- VoidLink/Input/CustomOSC/OSCProfile.h | 1 + VoidLink/Input/CustomOSC/OSCProfile.m | 3 ++ VoidLink/Input/MotionHandler.swift | 10 ++++++- VoidLink/Input/OnScreenWidgetView.swift | 5 ++-- VoidLink/MenuSectionView.m | 2 +- .../ViewControllers/SettingsViewController.h | 3 ++ .../ViewControllers/SettingsViewController.m | 21 +++++++++++-- .../StreamFrameViewController.m | 2 +- VoidLink/mul.lproj/iPad.xcstrings | 30 +++++++++++++++++++ VoidLink/mul.lproj/iPhone.xcstrings | 30 +++++++++++++++++++ 12 files changed, 152 insertions(+), 15 deletions(-) diff --git a/VoidLink/Base.lproj/iPad.storyboard b/VoidLink/Base.lproj/iPad.storyboard index 3095c8bf..2d94ccce 100644 --- a/VoidLink/Base.lproj/iPad.storyboard +++ b/VoidLink/Base.lproj/iPad.storyboard @@ -1339,6 +1339,26 @@ + + + + + + + + + + + + + + @@ -1380,6 +1400,8 @@ + + @@ -2185,7 +2207,7 @@ - + @@ -2194,13 +2216,13 @@ - + - + - + diff --git a/VoidLink/Base.lproj/iPhone.storyboard b/VoidLink/Base.lproj/iPhone.storyboard index 6c2b2fea..af94c953 100644 --- a/VoidLink/Base.lproj/iPhone.storyboard +++ b/VoidLink/Base.lproj/iPhone.storyboard @@ -2071,6 +2071,26 @@ + + + + + + + + + + + + + + @@ -2112,6 +2132,8 @@ + + @@ -2212,19 +2234,19 @@ - + - + - + - + diff --git a/VoidLink/Input/CustomOSC/OSCProfile.h b/VoidLink/Input/CustomOSC/OSCProfile.h index d81a31f4..21036ba1 100644 --- a/VoidLink/Input/CustomOSC/OSCProfile.h +++ b/VoidLink/Input/CustomOSC/OSCProfile.h @@ -40,6 +40,7 @@ typedef NS_ENUM(NSInteger, MapGyroTo) { @property (nonatomic, assign) CGFloat accelSensitivityX; @property (nonatomic, assign) CGFloat accelSensitivityY; @property (nonatomic, assign) CGFloat accelSensitivityZ; +@property (nonatomic, assign) double gyroToStickMinOffset; - (id) initWithName:(NSString*)name buttonStates:(NSMutableArray*)buttonStates isSelected:(BOOL)isSelected; diff --git a/VoidLink/Input/CustomOSC/OSCProfile.m b/VoidLink/Input/CustomOSC/OSCProfile.m index 937d7f26..249859bd 100644 --- a/VoidLink/Input/CustomOSC/OSCProfile.m +++ b/VoidLink/Input/CustomOSC/OSCProfile.m @@ -41,6 +41,7 @@ [encoder encodeFloat:self.accelSensitivityX forKey:@"accelSensitivityX"]; [encoder encodeFloat:self.accelSensitivityY forKey:@"accelSensitivityY"]; [encoder encodeFloat:self.accelSensitivityZ forKey:@"accelSensitivityZ"]; + [encoder encodeDouble:self.gyroToStickMinOffset forKey:@"gyroToStickMinOffset"]; } - (id) initWithCoder:(NSCoder*)decoder { @@ -57,6 +58,7 @@ self.accelSensitivityX = [decoder containsValueForKey:@"accelSensitivityX"] ? [decoder decodeFloatForKey:@"accelSensitivityX"] : 1.0; self.accelSensitivityY = [decoder containsValueForKey:@"accelSensitivityY"] ? [decoder decodeFloatForKey:@"accelSensitivityY"] : 1.0; self.accelSensitivityZ = [decoder containsValueForKey:@"accelSensitivityZ"] ? [decoder decodeFloatForKey:@"accelSensitivityZ"] : 1.0; + self.gyroToStickMinOffset = [decoder containsValueForKey:@"gyroToStickMinOffset"] ? [decoder decodeDoubleForKey:@"gyroToStickMinOffset"] : 0; } return self; @@ -76,6 +78,7 @@ copy.accelSensitivityX = self.accelSensitivityX; copy.accelSensitivityY = self.accelSensitivityY; copy.accelSensitivityZ = self.accelSensitivityZ; + copy.gyroToStickMinOffset = self.gyroToStickMinOffset; return copy; } diff --git a/VoidLink/Input/MotionHandler.swift b/VoidLink/Input/MotionHandler.swift index 0086b6ee..07266ceb 100644 --- a/VoidLink/Input/MotionHandler.swift +++ b/VoidLink/Input/MotionHandler.swift @@ -47,6 +47,7 @@ import CoreMotion sharedInstance.sensitvityYaw = sharedInstance.oscProfile.gyroSensitivityYaw sharedInstance.sensitvityPitch = sharedInstance.oscProfile.gyroSensitivityPitch sharedInstance.sensitvityRoll = sharedInstance.oscProfile.gyroSensitivityRoll + sharedInstance.gyroToStickMinOffset = sharedInstance.oscProfile.gyroToStickMinOffset return sharedInstance } @@ -77,6 +78,7 @@ import CoreMotion @objc public var gyroBiasX: Double = 0 @objc public var gyroBiasY: Double = 0 @objc public var gyroBiasZ: Double = 0 + @objc public var gyroToStickMinOffset:Double = 0 private var yawBias:Double = 0 private var pitchBias:Double = 0 private var rollBias:Double = 0 @@ -242,18 +244,24 @@ import CoreMotion pitch = pitchSource*sensitvityPitch*30 LiSendMouseMoveEvent(Int16(yaw),Int16(pitch)) } - + if oscProfile.mapGyroTo == MapGyroTo.mapGyroToControllerStick { if oscProfile.yawPitchToRightStick { yaw = rightStickTouchInputX + gyroInputToStickInput(input:yawSource*sensitvityYaw*10) yaw = self.clampStickInput(input: yaw) + yaw = (yaw >= 0 ? 1.0 : -1.0) * gyroToStickMinOffset + (self.stickMaxOffset - gyroToStickMinOffset) * (yaw/self.stickMaxOffset) + pitch = rightStickTouchInputY - gyroInputToStickInput(input:pitchSource*sensitvityPitch*10) pitch = self.clampStickInput(input: pitch) + pitch = (pitch >= 0 ? 1.0 : -1.0) * gyroToStickMinOffset + (self.stickMaxOffset - gyroToStickMinOffset) * (pitch/self.stickMaxOffset) + self.onScreenControls.sendRightStickTouchPadEvent(yaw, pitch) } if oscProfile.rollToLeftStick { roll = leftStickTouchInputX + gyroInputToStickInput(input:rollSource*sensitvityRoll*10) roll = self.clampStickInput(input: roll) + roll = (roll >= 0 ? 1.0 : -1.0) * gyroToStickMinOffset + (self.stickMaxOffset - gyroToStickMinOffset) * (roll/self.stickMaxOffset) + self.onScreenControls.sendLeftStickTouchPadEvent(roll, leftStickTouchInputY) } } diff --git a/VoidLink/Input/OnScreenWidgetView.swift b/VoidLink/Input/OnScreenWidgetView.swift index c00bc7d6..4a1d067b 100644 --- a/VoidLink/Input/OnScreenWidgetView.swift +++ b/VoidLink/Input/OnScreenWidgetView.swift @@ -1191,12 +1191,13 @@ import UIKit var targetX = self.touchInputToStickInput(input: inputX) var targetY = -self.touchInputToStickInput(input: inputY) // vertical input must be inverted - targetX = (targetX >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetX/self.stickMaxOffset) - targetY = (targetY >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetY/self.stickMaxOffset) + let mixRightStickInputToGyro = (oscProfile.mapGyroTo == MapGyroTo.mapGyroToControllerStick && oscProfile.yawPitchToRightStick) if !mixRightStickInputToGyro || (self.mixInputDelegate?.gyroMixInputStarted() != true) { + targetX = (targetX >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetX/self.stickMaxOffset) + targetY = (targetY >= 0 ? 1.0 : -1.0) * self.minStickOffset + (self.stickMaxOffset - self.minStickOffset) * (targetY/self.stickMaxOffset) self.onScreenControls.sendRightStickTouchPadEvent(targetX, targetY) } self.mixInputDelegate?.mixRightStickAndGyroInput(x: targetX, y: targetY) diff --git a/VoidLink/MenuSectionView.m b/VoidLink/MenuSectionView.m index d9046b79..833d97fb 100644 --- a/VoidLink/MenuSectionView.m +++ b/VoidLink/MenuSectionView.m @@ -278,7 +278,7 @@ static BOOL overridePersistedFoldState = YES; NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; BOOL persistedFoldState = [defaults objectForKey:self.identifier] ? [defaults boolForKey:self.identifier] : YES; - [self setExpanded: MenuSectionView.overridePersistedFoldState ? YES : persistedFoldState]; + [self setExpanded: MenuSectionView.overridePersistedFoldState ? _expandable : persistedFoldState]; } - (void)removeSubStackView:(UIStackView *)stackView { diff --git a/VoidLink/ViewControllers/SettingsViewController.h b/VoidLink/ViewControllers/SettingsViewController.h index 85fb5f2a..07abf8e2 100644 --- a/VoidLink/ViewControllers/SettingsViewController.h +++ b/VoidLink/ViewControllers/SettingsViewController.h @@ -163,6 +163,9 @@ @property (strong, nonatomic) IBOutlet UIStackView *rollSensitivityStack; @property (strong, nonatomic) IBOutlet UISlider *rollSensitivitySlider; +@property (strong, nonatomic) IBOutlet UIStackView *gyroToStickMinOffsetStack; +@property (strong, nonatomic) IBOutlet UISlider *gyroToStickMinOffsetSlider; + @property (strong, nonatomic) IBOutlet UIStackView *testStack; @property (nonatomic, strong) MicHandler *micHandler; diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 76d75a00..1240548e 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -362,6 +362,8 @@ BOOL isCustomResolution(int resolutionSelected) { [self pitchSensitivitySliderMoved:self.pitchSensitivitySlider]; [self.rollSensitivitySlider setValue:[self map_SliderValue_fromVelocFactor:oscProfile.gyroSensitivityRoll]]; [self rollSensitivitySliderMoved:self.rollSensitivitySlider]; + [self.gyroToStickMinOffsetSlider setValue:(uint16_t)oscProfile.gyroToStickMinOffset]; + [self gyroMinStickOffsetSliderMoved:self.gyroToStickMinOffsetSlider]; } - (void)saveMotionControlConfigs{ @@ -375,7 +377,9 @@ BOOL isCustomResolution(int resolutionSelected) { && oscProfile.rollToLeftStick == self.rollToLeftStickSwitch.isOn && (int16_t)(oscProfile.gyroSensitivityYaw*100) == (int16_t)yawSensitivityPercent && (int16_t)(oscProfile.gyroSensitivityPitch*100) == (int16_t)pitchSensitivityPercent - && (int16_t)(oscProfile.gyroSensitivityRoll*100) == (int16_t)rollSensitivityPercent); + && (int16_t)(oscProfile.gyroSensitivityRoll*100) == (int16_t)rollSensitivityPercent + && (int16_t)(oscProfile.gyroToStickMinOffset) == (int16_t)self.gyroToStickMinOffsetSlider.value); + if(!configNotChanged){ oscProfile.mapGyroTo = self.mapGyroToSelector.selectedSegmentIndex; oscProfile.yawPitchToRightStick = self.yawPitchToRightStickSwitch.isOn; @@ -383,6 +387,7 @@ BOOL isCustomResolution(int resolutionSelected) { oscProfile.gyroSensitivityYaw = yawSensitivityPercent/100; oscProfile.gyroSensitivityPitch = pitchSensitivityPercent/100; oscProfile.gyroSensitivityRoll = rollSensitivityPercent/100; + oscProfile.gyroToStickMinOffset = (int16_t)self.gyroToStickMinOffsetSlider.value; [oscProfileMan replaceSelectedProfileWith:oscProfile overwriteDefault:YES]; } } @@ -714,6 +719,7 @@ BOOL isCustomResolution(int resolutionSelected) { [self addDynamicLabelForStack:self.yawSensitivityStack]; [self addDynamicLabelForStack:self.pitchSensitivityStack]; [self addSetting:self.rollSensitivityStack ofId:@"rollSensitivityStack" withInfoTag:NO withDynamicLabel:YES to:motionControlSection]; + [self addSetting:self.gyroToStickMinOffsetStack ofId:@"gyroToStickMinOffsetStack" withInfoTag:NO withDynamicLabel:YES to:motionControlSection]; [motionControlSection addToParentStack:_parentStack]; MenuSectionView *gesturesSection = [[MenuSectionView alloc] init]; @@ -1816,6 +1822,7 @@ BOOL isCustomResolution(int resolutionSelected) { self->motionControlSection.expandable = [self isCustomOswEnabled]; + [self->motionControlSection setExpanded:[self isCustomOswEnabled]]; __weak typeof(self) weakSelf = self; self->motionControlSection.lockedSectionHandler = ^{ [CountdownAlertController showAlertIn:weakSelf @@ -1841,6 +1848,8 @@ BOOL isCustomResolution(int resolutionSelected) { [self.yawSensitivitySlider addTarget:self action:@selector(yawSensitivitySliderMoved:) forControlEvents:UIControlEventValueChanged]; [self.pitchSensitivitySlider addTarget:self action:@selector(pitchSensitivitySliderMoved:) forControlEvents:UIControlEventValueChanged]; [self.rollSensitivitySlider addTarget:self action:@selector(rollSensitivitySliderMoved:) forControlEvents:UIControlEventValueChanged]; + + [self.gyroToStickMinOffsetSlider addTarget:self action:@selector(gyroMinStickOffsetSliderMoved:) forControlEvents:UIControlEventValueChanged]; //Motion control settings @@ -2045,10 +2054,13 @@ BOOL isCustomResolution(int resolutionSelected) { [self setHidden:false forStack:_yawPitchSensitivityStack]; [self setHidden:true forStack:_rollSensitivityStack]; } + + [self setHidden:!mapGyroToControllerStickEnabled forStack:self.gyroToStickMinOffsetStack]; if(mapGyroToControllerStickEnabled){ [self yawPitchToRightStickSwitchFlipped:self.yawPitchToRightStickSwitch]; [self rollToLeftStickSwitchFlipped:self.rollToLeftStickSwitch]; } + if(sender.selectedSegmentIndex == driftCorrection){ [CountdownAlertController showAlertIn:self @@ -2060,7 +2072,7 @@ BOOL isCustomResolution(int resolutionSelected) { completion:^{ if(CountdownAlertController.actionCancelled){ [self.mapGyroToSelector setSelectedSegmentIndex:self->oscProfile.mapGyroTo]; - NSLog(@"actionCancelled"); + [self mapGyroToChanged:self.mapGyroToSelector]; } else{ MotionHandler* motionHandler = [MotionHandler sharedInstance]; @@ -2079,6 +2091,7 @@ BOOL isCustomResolution(int resolutionSelected) { countdown:6 completion:^{ [self.mapGyroToSelector setSelectedSegmentIndex:self->oscProfile.mapGyroTo]; + [self mapGyroToChanged:self.mapGyroToSelector]; }]; } }]; @@ -2107,6 +2120,10 @@ BOOL isCustomResolution(int resolutionSelected) { [self findDynamicLabelFromStack:_rollSensitivityStack].text = [NSString stringWithFormat:@" %d%% ", (int16_t)[self map_velocFactorDisplay_fromSliderValue:sender.value]]; } +- (void)gyroMinStickOffsetSliderMoved:(UISlider* )sender{ + [self findDynamicLabelFromStack:_gyroToStickMinOffsetStack].text = [NSString stringWithFormat:@" %d ", (int16_t)sender.value]; +} + - (void)invokeOscLayout{ // init CustomOSC stuff /* sets a reference to the correct 'LayoutOnScreenControlsViewController' depending on whether the user is on an iPhone or iPad */ diff --git a/VoidLink/ViewControllers/StreamFrameViewController.m b/VoidLink/ViewControllers/StreamFrameViewController.m index 73441199..57ad4a0c 100644 --- a/VoidLink/ViewControllers/StreamFrameViewController.m +++ b/VoidLink/ViewControllers/StreamFrameViewController.m @@ -436,7 +436,7 @@ _motionHandler = [MotionHandler sharedInstance]; _motionHandler.gyroBiasX = _settings.gyroBiasX.doubleValue; _motionHandler.gyroBiasY = _settings.gyroBiasY.doubleValue; - _motionHandler.gyroBiasZ = _settings.gyroBiasZ.doubleValue; + _motionHandler.gyroBiasZ = _settings.gyroBiasZ.doubleValue; _streamView.onScreenControls.instanceReceiverDelegate = _motionHandler; [_streamView.onScreenControls sendInstance]; diff --git a/VoidLink/mul.lproj/iPad.xcstrings b/VoidLink/mul.lproj/iPad.xcstrings index ab379863..2022de41 100644 --- a/VoidLink/mul.lproj/iPad.xcstrings +++ b/VoidLink/mul.lproj/iPad.xcstrings @@ -2257,6 +2257,36 @@ } } }, + "HqA-an-KEe.text" : { + "comment" : "Class = \"UILabel\"; text = \"Minimum Stick Offset\"; ObjectID = \"HqA-an-KEe\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Minimum Stick Offset" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "摇杆输出最小偏移量" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "摇杆输出最小偏移量" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "摇杆输出最小偏移量" + } + } + } + }, "iel-xl-xd9.text" : { "comment" : "Class = \"UILabel\"; text = \"Profile:\"; ObjectID = \"iel-xl-xd9\";", "extractionState" : "extracted_with_value", diff --git a/VoidLink/mul.lproj/iPhone.xcstrings b/VoidLink/mul.lproj/iPhone.xcstrings index 08ee2268..71229c12 100644 --- a/VoidLink/mul.lproj/iPhone.xcstrings +++ b/VoidLink/mul.lproj/iPhone.xcstrings @@ -847,6 +847,36 @@ } } }, + "52u-T9-1Hv.text" : { + "comment" : "Class = \"UILabel\"; text = \"Minimum Stick Offset\"; ObjectID = \"52u-T9-1Hv\";", + "extractionState" : "extracted_with_value", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Minimum Stick Offset" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "摇杆输出最小偏移量" + } + }, + "zh-Hant" : { + "stringUnit" : { + "state" : "translated", + "value" : "摇杆输出最小偏移量" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "摇杆输出最小偏移量" + } + } + } + }, "53H-ST-OtY.segmentTitles[0]" : { "comment" : "Class = \"UISegmentedControl\"; 53H-ST-OtY.segmentTitles[0] = \"Balanced\"; ObjectID = \"53H-ST-OtY\";", "extractionState" : "extracted_with_value",