Page 1 of 1

Accessibility

Posted: Fri Sep 02, 2022 8:13 am
by another_commander
A new option has been implemented in the Game Options menu and it will hopefully be of help to users with color blindness.

Image

Three color blindness filters for Protanopia, Deuteranopia and Tritanopia have been made available when the detail level is set to "Shaders Enabled" or higher. Those filters are not just emulation of how colorblind people view the game; that would not be very helpful, would it? Instead, they use relative color brightness to enhance contrast between colors that are difficult to distinguish for people with color blindness. For example, for the protanopia and deuteranopia filters, reds become slightly brighter and greens become slightly darker. The result is, hopefully, color differences that are perceptible to anyone. It is all explained in this paper for anyone who would like to know a bit more on the subject.

One thing to note: When using the cloaking device with one of the colorblind modes enabled, the HUD is rendered in the same pass as the world. This is not the same behaviour as non-colorblind mode, but for now we'll have to go with it, as there is no shader utilization when rendering the HUD at the moment. I hope it will not be an issue, but we should probably consider adding an assistant shader for the HUD render pass in the future for cases like this.

The code will be committed to master later today if testing does not reveal any major blockers. Here is the patch against trunk for anyone who would like to test drive it.

Code: Select all

diff --git a/Resources/Config/descriptions.plist b/Resources/Config/descriptions.plist
index be667395..f5b5754a 100644
--- a/Resources/Config/descriptions.plist
+++ b/Resources/Config/descriptions.plist
@@ -446,6 +446,14 @@
 		)
 	);
 	
+	colorblind_mode = 
+	(
+		"None", 
+		"Protanopia",
+		"Deuteranopia",
+		"Tritanopia"
+	);
+	
 	// *** Planet names and other random names ***
 	// this string must always be the same length (98 characters). Digrams (pairs of letters) are selected from it to build names.
 	digrams = "ABOUSEITILETSTONLONUTHNOALLEXEGEZACEBISOUSESARMAINDIREA’ERATENBERALAVETIEDORQUANTEISRION";
@@ -1317,6 +1325,7 @@
 
 	"oolite-keydesc-key_pausebutton"			= "Pause";
 	"oolite-keydesc-key_show_fps"				= "Show FPS";
+	"oolite-keydesc-key_bloom_toggle"			= "Toggle bloom on/off";
 	"oolite-keydesc-key_mouse_control"			= "Mouse control";
 	"oolite-keydesc-key_mouse_control_roll"		= "Mouse control (roll)";
 	"oolite-keydesc-key_mouse_control_yaw"		= "Mouse control (yaw)";
@@ -1697,6 +1706,7 @@
 	"gameoptions-autosave-no"				= " Autosave: Off ";
 	"gameoptions-gamma-value"				= " Gamma: ";
 	"gameoptions-fov-value"					= " Field Of View: ";
+	"gameoptions-colorblind-mode"			= " Colorblind Mode: [colorblindModeDesc] ";
 	"gameoptions-sound-volume"				= " Sound Volume: ";
 	"gameoptions-sound-volume-mute"			= " Sound Volume: Mute ";
 	"gameoptions-volume-external-only"		= " Sound Volume: External Control Only ";
diff --git a/Resources/Shaders/oolite-final.fragment b/Resources/Shaders/oolite-final.fragment
index a55e8396..4e7de942 100644
--- a/Resources/Shaders/oolite-final.fragment
+++ b/Resources/Shaders/oolite-final.fragment
@@ -89,14 +89,11 @@ const mat3 RGBtoOpponentMat = mat3(0.2814, -0.0971, -0.0930, 0.6938, 0.1458,-0.2
 const mat3 OpponentToRGBMat = mat3(1.1677, 0.9014, 0.7214, -6.4315, 2.5970, 0.1257, -0.5044, 0.0159, 2.0517);
 
 //const int NONE = 0;
-const int PROTANOPIA = 2;
-const int DEUTERANOPIA = 3;
-const int TRITANOPIA = 4;
+const int PROTANOPIA = 1;
+const int DEUTERANOPIA = 2;
+const int TRITANOPIA = 3;
 
 int blindnessType = uPostFX;
-//const int blindnessType = PROTANOPIA;
-//const int blindnessType = DEUTERANOPIA;
-//const int blindnessType = TRITANOPIA;
 
 void blindnessFilter( out vec3 myoutput, in vec3 myinput )
 {
@@ -441,13 +438,13 @@ void main()
 	switch(uPostFX)
 	{
 		case 1:
-			hdrColor = cloakVision(hdrColor);
-			break;
 		case 2:
 		case 3:
-		case 4:
 			hdrColor = colorBlindness(hdrColor);
 			break;
+		case 4:
+			hdrColor = cloakVision(hdrColor);
+			break;
 		case 5:
 			hdrColor = grayscale(hdrColor);
 			break;
@@ -476,7 +473,7 @@ void main()
 	// gamma correction       
 	result = pow(result, vec3(1.0 / 2.2));
 	
-#if OO_DITHER	
+#if OO_DITHER
 	result += random(TexCoords + uTime) / 255.0;
 #endif
 	
diff --git a/src/Core/Entities/PlayerEntity.h b/src/Core/Entities/PlayerEntity.h
index 50023ea2..822313f0 100644
--- a/src/Core/Entities/PlayerEntity.h
+++ b/src/Core/Entities/PlayerEntity.h
@@ -195,6 +195,7 @@ enum
 	GUI_ROW_GAMEOPTIONS_GAMMA,
 #endif
 	GUI_ROW_GAMEOPTIONS_FOV,
+	GUI_ROW_GAMEOPTIONS_COLORBLINDMODE,
 	GUI_ROW_GAMEOPTIONS_SPACER_STICKMAPPER,
 	GUI_ROW_GAMEOPTIONS_STICKMAPPER,
 	GUI_ROW_GAMEOPTIONS_KEYMAPPER,
diff --git a/src/Core/Entities/PlayerEntity.m b/src/Core/Entities/PlayerEntity.m
index bd3638eb..0a881cc8 100644
--- a/src/Core/Entities/PlayerEntity.m
+++ b/src/Core/Entities/PlayerEntity.m
@@ -2071,6 +2071,8 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 	voice_gender_m = YES;
 	voice_no = [UNIVERSE setVoice:-1 withGenderM:voice_gender_m];
 #endif
+
+	[UNIVERSE setCurrentPostFX:[UNIVERSE colorblindMode]];
 	
 	[_customViews release];
 	_customViews = nil;
@@ -5986,7 +5988,7 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
 	if (![self hasCloakingDevice])  return;
 
 	[super deactivateCloakingDevice];
-	[UNIVERSE setCurrentPostFX:OO_POSTFX_NONE];
+	[UNIVERSE setCurrentPostFX:[UNIVERSE colorblindMode]];
 	[UNIVERSE addMessage:DESC(@"cloak-off") forCount:2];
 	[self playCloakingDeviceOff];
 }
@@ -8847,6 +8849,20 @@ static NSString *SliderString(NSInteger amountIn20ths)
 		[gui setText:[NSString stringWithFormat:@"%@%@ (%d%c) ", fovWordDesc, SliderString(fovTicks), (int)fov, 176 /*176 is the degrees symbol Unicode code point*/] forRow:GUI_ROW(GAME,FOV) align:GUI_ALIGN_CENTER];
 		[gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,FOV)];
 		
+		// color blind mode
+		int colorblindMode = [UNIVERSE colorblindMode];
+		NSString *colorblindModeDesc = [[[UNIVERSE descriptions] oo_arrayForKey: @"colorblind_mode"] oo_stringAtIndex:[UNIVERSE useShaders] ? colorblindMode : 0];
+		NSString *colorblindModeMsg = OOExpandKey(@"gameoptions-colorblind-mode", colorblindModeDesc);
+		[gui setText:colorblindModeMsg forRow:GUI_ROW(GAME,COLORBLINDMODE) align:GUI_ALIGN_CENTER];
+		if ([UNIVERSE useShaders])
+		{
+			[gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,COLORBLINDMODE)];
+		}
+		else
+		{
+			[gui setColor:[OOColor grayColor] forRow:GUI_ROW(GAME,COLORBLINDMODE)];
+		}
+		
 #if OOLITE_SPEECH_SYNTH
 		// Speech control
 		switch (isSpeechOn)
diff --git a/src/Core/Entities/PlayerEntityControls.m b/src/Core/Entities/PlayerEntityControls.m
index bdf3fe32..2809f4b7 100644
--- a/src/Core/Entities/PlayerEntityControls.m
+++ b/src/Core/Entities/PlayerEntityControls.m
@@ -124,6 +124,7 @@ static BOOL				volumeControlPressed;
 static BOOL				gammaControlPressed;
 #endif
 static BOOL				fovControlPressed;
+static BOOL				colorblindModeControlPressed;
 static BOOL				shaderSelectKeyPressed;
 static BOOL				selectPressed;
 static BOOL				queryPressed;
@@ -3606,7 +3607,32 @@ static NSTimeInterval	time_last_frame;
 	else
 		fovControlPressed = NO;
 
-		
+	
+	// color blind mode
+	if ((guiSelectedRow == GUI_ROW(GAME,COLORBLINDMODE))&&(([self checkKeyPress:n_key_gui_arrow_right])||([self checkKeyPress:n_key_gui_arrow_left])))
+	{
+		if (!colorblindModeControlPressed)
+		{
+			int colorblindMode = [UNIVERSE colorblindMode];
+			if ([self checkKeyPress:n_key_gui_arrow_right])
+			{
+				[UNIVERSE setCurrentPostFX:[UNIVERSE nextColorblindMode:colorblindMode]];
+			}
+			else
+			{
+				[UNIVERSE setCurrentPostFX:[UNIVERSE prevColorblindMode:colorblindMode]];
+			}
+			colorblindMode = [UNIVERSE colorblindMode]; // get the updated value
+			NSString *colorblindModeDesc = [[[UNIVERSE descriptions] oo_arrayForKey: @"colorblind_mode"] oo_stringAtIndex:[UNIVERSE useShaders] ? colorblindMode : 0];
+			NSString *colorblindModeMsg = OOExpandKey(@"gameoptions-colorblind-mode", colorblindModeDesc);
+			[gui setText:colorblindModeMsg forRow:GUI_ROW(GAME,COLORBLINDMODE) align:GUI_ALIGN_CENTER];
+		}
+		colorblindModeControlPressed = YES;
+	}
+	else
+		colorblindModeControlPressed = NO;
+	
+	
 	if ((guiSelectedRow == GUI_ROW(GAME,WIREFRAMEGRAPHICS))&&(([self checkKeyPress:n_key_gui_arrow_right])||([self checkKeyPress:n_key_gui_arrow_left])))
 	{
 		if ([self checkKeyPress:n_key_gui_arrow_right] != [UNIVERSE wireframeGraphics])
@@ -3674,6 +3700,11 @@ static NSTimeInterval	time_last_frame;
 			[gui setKey:GUI_KEY_OK forRow:GUI_ROW(GAME,SHADEREFFECTS)];
 
 			timeLastKeyPress = script_time;
+			
+			// changing detail level may result in changes to other settings too
+			// (e.g. colorblind mode status), so refresh the page
+			[self setGuiToGameOptionsScreen];
+			[gui setSelectedRow:GUI_ROW(GAME,SHADEREFFECTS)];
 		}
 		shaderSelectKeyPressed = YES;
 	}
diff --git a/src/Core/Universe.h b/src/Core/Universe.h
index 0f356820..eeecb4c2 100644
--- a/src/Core/Universe.h
+++ b/src/Core/Universe.h
@@ -88,10 +88,10 @@ enum
 enum
 {
 	OO_POSTFX_NONE						= 0,
-	OO_POSTFX_CLOAK,
 	OO_POSTFX_COLORBLINDNESS_PROTAN,
 	OO_POSTFX_COLORBLINDNESS_DEUTER,
 	OO_POSTFX_COLORBLINDNESS_TRITAN,
+	OO_POSTFX_CLOAK,
 	OO_POSTFX_GRAYSCALE,
 	OO_POSTFX_OLDMOVIE,
 	OO_POSTFX_CRT,
@@ -371,6 +371,7 @@ enum
     GLuint					pingpongColorbuffers[2];
 	BOOL					_bloom;
 	int					_currentPostFX;
+	int					_colorblindMode;
 }
 
 - (BOOL) bloom;
@@ -782,6 +783,9 @@ enum
 - (unsigned int) prevVoice:(unsigned int) index;
 - (unsigned int) setVoice:(unsigned int) index withGenderM:(BOOL) isMale;
 #endif
+- (int) nextColorblindMode:(int) index;
+- (int) prevColorblindMode:(int) index;
+- (int) colorblindMode;
 //
 ////
 
diff --git a/src/Core/Universe.m b/src/Core/Universe.m
index 5367d13e..d55fc867 100644
--- a/src/Core/Universe.m
+++ b/src/Core/Universe.m
@@ -295,9 +295,35 @@ static GLfloat	docked_light_specular[4]	= { DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEV
 		newCurrentPostFX = OO_POSTFX_NONE;
 	}
 	
+	if	(OO_POSTFX_NONE <= newCurrentPostFX && newCurrentPostFX <= OO_POSTFX_COLORBLINDNESS_TRITAN)
+	{
+		_colorblindMode = newCurrentPostFX;
+	}		
+	
 	_currentPostFX = newCurrentPostFX;
 }
 
+- (int) nextColorblindMode:(int) index
+{
+	if (++index > OO_POSTFX_COLORBLINDNESS_TRITAN)
+		index = OO_POSTFX_NONE;
+	
+	return index;
+}
+
+- (int) prevColorblindMode:(int) index
+{
+	if (--index < OO_POSTFX_NONE)
+		index = OO_POSTFX_COLORBLINDNESS_TRITAN;
+	
+	return index;
+}
+
+- (int) colorblindMode
+{
+	return _colorblindMode;
+}
+
 - (void) initTargetFramebufferWithViewSize:(NSSize)viewSize
 {
 	// have to do this because on my machine the default framebuffer is not zero
@@ -422,7 +448,7 @@ static GLfloat	docked_light_specular[4]	= { DOCKED_ILLUM_LEVEL, DOCKED_ILLUM_LEV
 	OOGL(glBindFramebuffer(GL_FRAMEBUFFER, defaultDrawFBO));
 	
 	_bloom = [self detailLevel] >= DETAIL_LEVEL_EXTRAS;
-	_currentPostFX = OO_POSTFX_NONE;
+	_currentPostFX = _colorblindMode = OO_POSTFX_NONE;
 
 	/* TODO: in OOEnvironmentCubeMap.m call these bind functions not with 0 but with "previousXxxID"s:
 	  - OOGL(glBindTexture(GL_TEXTURE_CUBE_MAP, 0));
@@ -4734,7 +4760,7 @@ static const OOMatrix	starboard_matrix =
 - (void) drawUniverse
 {
 	int currentPostFX = [self currentPostFX];
-	BOOL hudSeparateRenderPass =  [self useShaders] && (currentPostFX == OO_POSTFX_NONE || currentPostFX == OO_POSTFX_CLOAK);
+	BOOL hudSeparateRenderPass =  [self useShaders] && (currentPostFX == OO_POSTFX_NONE || (currentPostFX == OO_POSTFX_CLOAK && [self colorblindMode] == OO_POSTFX_NONE));
 	NSSize  viewSize = [gameView viewSize];
 	OOLog(@"universe.profile.draw", @"%@", @"Begin draw");
 	

Re: Accessibility

Posted: Thu Nov 16, 2023 9:26 am
by another_commander
A new accessibility feature to further assist people with colorblindness is scheduled to be pushed to master soon. This will change the shape of hostile ships' scanner blips from a square to an X when playing in any of the three colorblind modes. This way it will be even easier to pick enemies on the scanner and recognize them as such.

Example screenie:
Image
With the scanner zoomed in:
Image

PR #462 is currently up on github for anyone who wishes to experiment with it before it gets merged in. Test installers can be downloaded if you already have a github account from here

If all goes to plan we'll have this in during the weekend.