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");