Premultiplied alpha (#133)

* WIP premultiplied alpha

* WIP color API rework (doesn't build yet; lots of things left to convert)

* convert everything remaining to new Color api except stage*_event.c files

* convert the stages to new Color api. builds & runs now; still many rendering errors

* fix the bullet shader for premultiplied alpha

* fix masterspark, graphs and stage 1 fog clouds

* fix marisa_b and most of spellcards

* Add deprecation warnings for BLEND_ADD and PFLAG_DRAWADD

* fix a segfault in stage 6

undo accidental earlier change

* fix text_hud.frag.glsl

* fix scuttle bg and remaining stage3 BLEND_ADDs

* fix marisa laser opacity

* hacky fix for myon

The old implementation relied on alpha being stored inside p->color. In
premul alpha this doesn’t work and functions like color_set_opacity
can’t solve this i think.

So I tried messing around with it until it looked somewhat similar.

* fix marisa_b stars

* remove color_set_opacity i overlooked

* more plrmode blending changes

* fixup additive blending in stage 1

* various premultiplied alpha fixups for bosses and enemies

* stage 2 premul alpha fixups

* stage 4 premul alpha fixups

* stage 5 premul alpha fixups

* stage 6 premul alpha fixups

* make lasers also use the PMA blend mode

* remove PFLAG_DRAWADD and PFLAG_DRAWSUB

* fix remaining PMA issues in menus

* lame extraspell bg workaround

* fix item alpha

* make marisaA lasers look somewhat like in master

* fix marisaA bomb background fadeout

* fixup various r_color4 calls

* fix myon

* remove dead code

* fix use of BLEND_ADD in player death effect

* fix myon shot trails (broken on master as well)

* fix myon shot fade-in

* extend the sprite shaders custom parameter to a vec4

* fix youmuB stuff and make it look somewhat better.

the code looks even worse though.
This commit is contained in:
Andrei Alexeyev 2018-07-23 20:07:59 +03:00 committed by GitHub
parent 1b82c80070
commit 8490576810
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
83 changed files with 1334 additions and 1275 deletions

View file

@ -1,6 +1,7 @@
#version 330 core
#include "interface/standard.glslh"
#include "lib/util.glslh"
#define POINTS 120
// #define INTERPOLATE
@ -30,6 +31,7 @@ void main(void) {
vec4 c = vec4(1.0);
vec2 coord = vec2(texCoord.x, 1.0 - texCoord.y); // TODO: move to vertex shader
float s = get_sample(coord.x);
c.a = float(coord.y <= s);
c.a *= (0.2 + 0.8 * s);
c.a = 0.05 + 0.95 * c.a;
@ -43,5 +45,6 @@ void main(void) {
}
c = mix(c, vec4(1.0), 0.5 * pow(1.0 - abs(coord.y - 0.5), 32.0));
fragColor = c;
fragColor = color_mul_alpha(c);
}

View file

@ -26,7 +26,7 @@ ATTRIBUTE(7) mat4 spriteTexTransform;
ATTRIBUTE(11) vec4 spriteRGBA;
ATTRIBUTE(12) vec4 spriteTexRegion;
ATTRIBUTE(13) vec2 spriteDimensions;
ATTRIBUTE(14) float spriteCustomParam;
ATTRIBUTE(14) vec4 spriteCustomParams;
#endif
#ifdef FRAG_STAGE
@ -41,6 +41,6 @@ VARYING(2) vec2 texCoordOverlay;
VARYING(3) vec4 texRegion;
VARYING(4) vec4 color;
VARYING(5) vec2 dimensions;
VARYING(6) float customParam;
VARYING(6) vec4 customParams;
#endif

View file

@ -32,6 +32,6 @@ void main(void) {
#endif
#ifdef SPRITE_OUT_CUSTOM
customParam = spriteCustomParam;
customParams = spriteCustomParams;
#endif
}

View file

@ -4,7 +4,7 @@
#include "defs.glslh"
const float pi = 3.141592653589793;
const float pi = 3.141592653589793; // as supplied by google
vec2 dir(float a) {
return vec2(cos(a), sin(a));
@ -44,4 +44,8 @@ vec2 uv_to_region(vec4 region, vec2 uv) {
);
}
vec4 color_mul_alpha(vec4 c) {
return vec4(c.rgb * c.a, c.a);
}
#endif

View file

@ -4,6 +4,6 @@
void main(void) {
vec4 texel = texture(tex, texCoord);
fragColor.rgb = mix(color.rgb, vec3(0.0), texel.b);
fragColor.rgb = mix(color.rgb, vec3(0.0), texel.b)*texel.a;
fragColor.a = texel.a * color.a;
}

View file

@ -20,5 +20,5 @@ void main(void) {
vec4 color = mix(color0, color1, 0.5 + 0.5 * sin(color_phase + color_freq * uv_scaled.x));
fragColor = texture(tex, texCoord);
fragColor *= vec4(color.rgb, color.a * a);
fragColor *= color * a;
}

View file

@ -2,17 +2,25 @@
#include "lib/render_context.glslh"
#include "interface/standard.glslh"
#include "lib/util.glslh"
UNIFORM(1) float t;
UNIFORM(2) vec2 plrpos;
UNIFORM(3) float decay;
void main(void) {
vec2 r = texCoordRaw-plrpos;
r = vec2(atan(r.y,r.x)/3.1415*4.,length(r)-2.*t*(1.+decay*t));
r.x+=(1.+r.y)*t*5.*decay;
vec4 texel = texture(tex, r);
texel.a = 0.7*min(1.,t)*(1.-t*decay)*4.;
// This thing is supposed to produce a swirly texture centered
// around plrpos. To do it we have to map to polar coordinates
fragColor = texel*r_color;
vec2 x = texCoordRaw-plrpos;
float r = length(x) - 2 * t * (1 + decay *t);
float phi = atan(x.y, x.x) / pi * 4;
// give the angle some juicy r dependent offset to twist the swirl.
phi += (1 + r) * t * 5 * decay;
vec4 texel = texture(tex, vec2(phi,r));
float fade = 0.7 * min(1,t) * (1 - t * decay) * 4;
fragColor = texel * r_color * fade;
}

View file

@ -6,9 +6,22 @@
UNIFORM(1) float t;
void main(void) {
vec2 r = vec2(texCoord.x-0.5,1.0-texCoord.y);
float f = abs(r.x)-0.4*pow(r.y,0.3);
vec3 clr = vec3(0.8+0.2*sin(r.x*2.),0.8+0.2*cos(r.y*2.-t*0.1),0.8+0.2*sin((r.x-r.y)*1.4+t*0.2));
vec2 r = vec2(texCoord.x - 0.5, 1.0 - texCoord.y);
fragColor = vec4(clr,_smoothstep(-(f+0.01*sin(r.y*30.-t*0.7)*(1.-1./(t+1.))+0.05),0.05))+vec4(1.)*_smoothstep(-f-0.15-0.2/(1.+t*0.1),0.02);
// this is > 0 in the basic core region of the spark. to make the shape more interesting, it is modulated with additional terms later.
float coreRegion = 0.4 * pow(r.y, 0.3) - abs(r.x);
// random rainbow pattern moving with time
vec3 clr = vec3(0.7+0.3*sin(r.x*2.),0.7+0.3*cos(r.y*2.-t*0.1),0.7+0.3*sin((r.x-r.y)*1.4+t*0.2));
// _smoothstep(coreRegion, 0) would be a mask for the bare cone shape of the spark. By adding extra terms it is distorted.
// sin adds a wavy edge, the fraction with t takes care of fading in/out
float colorRegion = coreRegion - 0.01 * sin(r.y * 30 - t * 0.7) * (1-1/(t+1)) - 0.05;
float whiteRegion = coreRegion - 0.15 - 0.2 / (1 + t * 0.1);
fragColor = vec4(clr, 0) * _smoothstep(colorRegion, 0.05)
+ vec4(1, 1, 1, 0) * _smoothstep(whiteRegion, 0.02);
}

View file

@ -0,0 +1,9 @@
#version 330 core
#include "lib/render_context.glslh"
#include "interface/standard.glslh"
void main(void) {
fragColor = texture(tex, texCoord);
fragColor.a = max(fragColor.r, max(fragColor.g, fragColor.b));
}

View file

@ -0,0 +1,2 @@
glsl_objects = standard.vert max_to_alpha.frag

View file

@ -15,14 +15,17 @@ glsl_files = files(
'graph.frag.glsl',
'ingame_menu.frag.glsl',
'laser_generic.vert.glsl',
'marisa_hakkero.frag.glsl',
'marisa_laser.frag.glsl',
'maristar_bombbg.frag.glsl',
'masterspark.frag.glsl',
'max_to_alpha.frag.glsl',
'player_death.frag.glsl',
'spellcard_intro.frag.glsl',
'spellcard_outro.frag.glsl',
'spellcard_walloftext.frag.glsl',
'sprite_bullet.frag.glsl',
'sprite_bullet.vert.glsl',
'sprite_bullet_apple.frag.glsl',
'sprite_circleclipped_indicator.frag.glsl',
'sprite_circleclipped_indicator.vert.glsl',

View file

@ -10,8 +10,16 @@ void main(void) {
pos.y *= ratio;
float r = length(pos);
float phi = atan(pos.y,pos.x)+t/10.0;
float rmin = (1.+_smoothstep(t-.5)*sin(t*40.0+10.*phi))*step(0.,t)*t*1.5;
fragColor = mix(clr, vec4(1.0) - vec4(clr.rgb,0.), step(rmin-.1*sqrt(r),r));
fragColor.a = float(r < rmin);
// everything bigger than rmax is not drawn. The sine creates the
// spinning wavy border.
float rmax = 1 + _smoothstep(t - 0.5) * sin(t * 40 + 10 * phi);
// now make it grow with time.
rmax *= step(0, t) * t * 1.5;
// at the rim, invert colors
fragColor = mix(clr, vec4(1) - vec4(clr.rgb, 0), step(rmax-0.1*sqrt(r),r));
fragColor *= float(r < rmax);
}

View file

@ -10,8 +10,16 @@ void main(void) {
pos.y *= ratio;
float r = length(pos);
float phi = atan(pos.y,pos.x)+t/10.0;
float rmin = (1.+0.3*sin(t*20.0+10.*phi))*step(0.,t)*t*t*10.0;
fragColor = mix(clr, vec4(1.0) - vec4(clr.rgb,0.), step(r,rmin+t*(.1+0.1*sin(10.*r))));
// everything lesser than rmin is not drawn. The sine creates the
// spinning wavy border.
float rmin = (1 + 0.3 * sin(t * 20 + 10 * phi));
fragColor.a = float(r > rmin);
// now grow it
rmin *= step(0.,t) * t * t * 10;
// at the rim, invert colors
fragColor = mix(clr, vec4(1.0) - vec4(clr.rgb,0.), step(r, rmin + t * (0.1 + 0.1 * sin(10 * r))));
fragColor *= float(r > rmin);
}

View file

@ -4,17 +4,34 @@
#include "interface/spellcard.glslh"
void main(void) {
float n = 10.;
vec2 pos = (vec2(texCoordRaw)-origin*vec2(ratio,1.0))*n;
pos.x /= ratio;
pos = vec2(atan(pos.x,pos.y),length(pos)+n*t*0.1);
pos.x *= h/w;
pos.x += t*0.5*float(2*int(mod(pos.y,2.0))-1);
pos = mod(pos,vec2(1.0+(0.01/w),1.0));
pos *= vec2(w,h);
vec4 clr = texture(tex, pos);
clr.gb = clr.rr;
clr.a *= float(pos.x < w && pos.y < h)*(2.*t-t*t);
float n = 10; // rough number of rings of text
fragColor = clr;
vec2 pos = (vec2(texCoordRaw) - origin * vec2(ratio, 1.0)) * n;
pos.x /= ratio;
// Slightly distorted mapping to polar coordinates. I wrote this
// shortly after hearing a general relativity lecture...
pos = vec2(atan(pos.x,pos.y), length(pos) + n * t * 0.1);
// The idea is (apart from the aspect factors) to map the
// (phi, angle) coordinates to something that loops several
// times and contains margins.
// This is achieved using all those mods.
//
// Simplified example:
// f(x) = x for 0 < x < 2
// mod(f(x),1) then goes from 0 to 1 and from 0 to 1 again.
//
// If you use it as a texcoord you get two copies of the texture.
//
// The complicated example I dont understand either:
pos.x *= h/w;
pos.x += t * 0.5 * float(2 * int(mod(pos.y, 2)) - 1);
pos = mod(pos, vec2(1 + 0.01 / w, 1));
pos *= vec2(w,h);
vec4 texel = texture(tex, pos);
fragColor = vec4(texel.r) * float(pos.x < w && pos.y < h)*t*(2-t);
}

View file

@ -4,6 +4,5 @@
void main(void) {
vec4 texel = texture(tex, texCoord);
fragColor.rgb = mix(color.rgb, vec3(1.0), texel.b);
fragColor.a = texel.a * color.a;
fragColor = (texel.g * color + vec4(texel.b)) * (1 - customParams.r);
}

View file

@ -1 +1 @@
glsl_objects = sprite_default.vert sprite_bullet.frag
glsl_objects = sprite_bullet.vert sprite_bullet.frag

View file

@ -0,0 +1,7 @@
#version 330 core
#define SPRITE_OUT_COLOR
#define SPRITE_OUT_TEXCOORD
#define SPRITE_OUT_CUSTOM
#include "lib/sprite_default.vert.glslh"

View file

@ -6,7 +6,7 @@
UNIFORM(1) vec4 back_color;
void main(void) {
float fill = customParam;
float fill = customParams.r;
vec2 tc = texCoord;
vec4 texel = texture(tex, tc);
tc = texCoordRaw - vec2(0.5);

View file

@ -4,7 +4,5 @@
void main(void) {
vec4 texel = texture(tex, texCoord);
vec4 mixclr = 1.0-texel;
mixclr.a = texel.a;
fragColor = mixclr;
fragColor = vec4((1.0 - texel.rgb / max(0.01, texel.a)) * texel.a, 0);
}

View file

@ -18,7 +18,7 @@ void main(void) {
vec2 uv = texCoordRaw;
vec2 uv_orig = uv;
vec4 texel;
float deform = customParam;
float deform = customParams.r;
const float limit = 1.0;
const float step = 0.25;
@ -30,7 +30,7 @@ void main(void) {
uv = apply_deform(uv_orig, deform * i);
texel = texture(tex, uv_to_region(texRegion, uv));
float a = float(uv.x >= 0 && uv.x <= 1 && uv.y >= 0 && uv.y <= 1);
fragColor += vec4(color.rgb, color.a * texel.a * a);
fragColor += color * texel.a * a;
}
fragColor /= num;

View file

@ -13,11 +13,12 @@ ported from:
void main(void) {
vec4 texel = texture(tex, texCoord);
float charge = customParam;
float charge = customParams.r;
fragColor = vec4(0.0);
fragColor.rgb += texel.r * mix(vec3(color.r, 0.0, 0.0), vec3(0.0, 0.0, color.b), 0.75 * sqrt(charge));
fragColor.rgb += texel.r * mix(vec3(color.r, 0.0, 0.0), vec3(0.0, 0.0, color.b), sqrt(charge));
fragColor.g += texel.g * color.g;
fragColor.rgb += texel.b * mix(vec3(0.0, 0.0, color.b), vec3(2.0 * color.r, 0.0, 0.0), 0.75 * charge);
fragColor.a = texel.a * color.a;
fragColor *= customParams.g;
}

View file

@ -9,4 +9,5 @@ void main(void) {
fragColor.rgb += vec3(texel.r);
fragColor.rgb += texel.b * vec3(color.r*color.r, color.g*color.g, color.b*color.b);
fragColor.a = texel.a * color.a;
fragColor *= (1 - customParams.r);
}

View file

@ -1 +1 @@
glsl_objects = sprite_default.vert sprite_youmu_myon_shot.frag
glsl_objects = sprite_bullet.vert sprite_youmu_myon_shot.frag

View file

@ -3,5 +3,5 @@
#include "interface/sprite.glslh"
void main(void) {
fragColor = color * vec4(1, 1, 1, texture(tex, texCoord).r);
fragColor = color * vec4(texture(tex, texCoord).r);
}

View file

@ -10,7 +10,7 @@ void main(void) {
// Transform local glyph texture coordinates.
tc *= dimensions;
tc.x += 5 * sin(3 * tc_overlay.y + customParam);
tc.x += 5 * sin(3 * tc_overlay.y + customParams.r);
tc /= dimensions;
// Compute alpha multiplier used to chop off unwanted texels from the atlas.
@ -20,7 +20,7 @@ void main(void) {
vec2 tc_atlas = uv_to_region(texRegion, tc);
// Display the glyph.
fragColor = color * vec4(1, 1, 1, texture(tex, tc_atlas).r * a);
fragColor = color * vec4(texture(tex, tc_atlas).r * a);
// Visualize global overlay coordinates. You could use them to span a texture across all glyphs.
fragColor *= vec4(tc_overlay.x, tc_overlay.y, 0, 1);

View file

@ -27,8 +27,8 @@ void main(void) {
// Fragment shader needs to know the sprite dimensions so that it can denormalize texCoord for processing.
dimensions = spriteDimensions;
// Arbitrary custom parameter provided by the application. You can use it to pass e.g. times/frames.
customParam = spriteCustomParam;
// Arbitrary parameters provided by the application. You can use this to pass e.g. times/frames.
customParams = spriteCustomParams;
// Should be obvious.
color = spriteRGBA;

View file

@ -5,5 +5,7 @@
void main(void) {
vec4 texel = texture(tex, texCoord);
fragColor = vec4(color.rgb, color.a * texel.r) * mix(1.0, 0.8, texCoordOverlay.y);
float gradient = mix(1.0, 0.8, texCoordOverlay.y);
fragColor = color * texel.r * gradient;
fragColor.rgb *= gradient;
}

View file

@ -11,7 +11,7 @@ float tc_mask(vec2 tc) {
}
void main(void) {
float t = customParam;
float t = customParams.r;
vec2 tc = texCoord;
vec2 tc_overlay = texCoordOverlay;
vec2 f = tc_overlay - vec2(0.5);
@ -22,18 +22,16 @@ void main(void) {
float a = tc_mask(tc);
vec4 texel = texture(tex, uv_to_region(texRegion, tc));
vec4 textfrag = vec4(color.rgb, a * color.a * texel.r);
vec4 textfrag = color * texture(tex, uv_to_region(texRegion, tc)).r * a;
tc -= vec2(1) / dimensions;
a = tc_mask(tc);
texel = texture(tex, uv_to_region(texRegion, tc));
vec4 shadowfrag = vec4(vec3(0), a * color.a * texel.r);
vec4 shadowfrag = vec4(vec3(0), color.a) * texture(tex, uv_to_region(texRegion, tc)).r * a;
fragColor = textfrag;
fragColor = mix(shadowfrag, textfrag, sqrt(textfrag.a));
tc_overlay = clamp(tc_overlay,0.01,0.99); // The overlay coordinates are outside of [0,1] in the padding region, so we make sure there are no wrap around artifacts when a bit of text is distorted to this region.
fragColor.a *= clamp((texture(trans, tc_overlay).r + 0.5) * 2.5 * t-0.5, 0.0, 1.0);
fragColor *= clamp((texture(trans, tc_overlay).r + 0.5) * 2.5 * t-0.5, 0.0, 1.0);
}

View file

@ -2,26 +2,7 @@
#include "interface/standard.glslh"
vec4 replace_color(vec4 texel, vec4 neighbour) {
return mix(texel, neighbour, float(neighbour.a > texel.a));
}
void main(void) {
vec4 texel = textureLod(tex, texCoordRaw, 0);
vec4 new_texel = texel;
/*
* GL_LINEAR will sample even pixels with zero alpha.
* Those usually don't have any meaningful RGB data.
* This results in ugly dark borders around some sprites.
* As a workaround, we change the RGB values of such pixels to those of the most opaque nearby one.
*/
new_texel = replace_color(new_texel, textureLodOffset(tex, texCoordRaw, 0, ivec2( 0, 1)));
new_texel = replace_color(new_texel, textureLodOffset(tex, texCoordRaw, 0, ivec2( 1, 0)));
new_texel = replace_color(new_texel, textureLodOffset(tex, texCoordRaw, 0, ivec2( 0, -1)));
new_texel = replace_color(new_texel, textureLodOffset(tex, texCoordRaw, 0, ivec2(-1, 0)));
texel = mix(texel, vec4(new_texel.rgb, texel.a), float(texel.a <= 0.5));
fragColor = texel;
fragColor = textureLod(tex, texCoordRaw, 0);
fragColor.rgb *= fragColor.a;
}

View file

@ -31,6 +31,7 @@ Boss* create_boss(char *name, char *ani, char *dialog, complex pos) {
}
buf->birthtime = global.frames;
buf->zoomcolor = *RGBA(0.1, 0.2, 0.3, 1.0);
buf->ent.draw_layer = LAYER_BOSS;
buf->ent.draw_func = ent_draw_boss;
@ -39,12 +40,12 @@ Boss* create_boss(char *name, char *ani, char *dialog, complex pos) {
return buf;
}
void draw_boss_text(Alignment align, float x, float y, const char *text, const char *fnt, Color clr) {
void draw_boss_text(Alignment align, float x, float y, const char *text, const char *fnt, const Color *clr) {
ShaderProgram *sh_prev = r_shader_current();
r_shader("text_default");
text_draw(text, &(TextParams) {
.pos = { x + 1, y + 1 },
.color = derive_color(rgb(0, 0, 0), CLRMASK_A, clr),
.color = RGBA(0, 0, 0, clr->a),
.font = fnt,
.align = align,
});
@ -72,7 +73,7 @@ void spell_opening(Boss *b, int time) {
float scale = f+1.*(1-f)*(1-f)*(1-f);
r_mat_scale(scale,scale,1);
r_mat_rotate_deg(360*f,1,1,0);
draw_boss_text(ALIGN_RIGHT, strw/2*(1-f), 0, b->current->name, "standard", rgb(1, 1, 1));
draw_boss_text(ALIGN_RIGHT, strw/2*(1-f), 0, b->current->name, "standard", RGB(1, 1, 1));
r_mat_pop();
r_capability(RCAP_CULL_FACE, cullcap_saved);
@ -80,27 +81,34 @@ void spell_opening(Boss *b, int time) {
void draw_extraspell_bg(Boss *boss, int time) {
// overlay for all extra spells
// FIXME: Please replace this with something that doesn't look like shit.
r_blend(BLEND_ADD);
r_color4(0.2,0.1,0,0.7);
float opacity = 0.7;
r_color4(0.2 * opacity, 0.1 * opacity, 0, 0);
fill_viewport(sin(time) * 0.015, time / 50.0, 1, "stage3/wspellclouds");
r_color4(1,1,1,1);
r_color4(2000, 2000, 2000, 0);
r_blend(r_blend_compose(
BLENDFACTOR_SRC_ALPHA, BLENDFACTOR_ONE, BLENDOP_MIN,
BLENDFACTOR_SRC_COLOR, BLENDFACTOR_ONE, BLENDOP_MIN,
BLENDFACTOR_ZERO, BLENDFACTOR_ONE, BLENDOP_MIN
));
fill_viewport(cos(time) * 0.015, time / 70.0, 1, "stage4/kurumibg2");
fill_viewport(sin(time+2.1) * 0.015, time / 30.0, 1, "stage4/kurumibg2");
r_blend(BLEND_ALPHA);
fill_viewport(sin(time*1.1+2.1) * 0.015, time / 30.0, 1, "stage4/kurumibg2");
r_blend(BLEND_PREMUL_ALPHA);
r_color4(1, 1, 1, 1);
}
Color boss_healthbar_color(AttackType atype) {
switch(atype) {
default: case AT_Normal: return rgb(1.00, 1.00, 1.00);
case AT_Spellcard: return rgb(1.00, 0.65, 0.65);
case AT_SurvivalSpell: return rgb(0.50, 0.50, 1.00);
case AT_ExtraSpell: return rgb(1.00, 0.30, 0.20);
}
const Color* boss_healthbar_color(AttackType atype) {
static const Color colors[] = {
[AT_Normal] = { 1.00, 1.00, 1.00, 1.00 },
[AT_Move] = { 1.00, 1.00, 1.00, 1.00 },
[AT_Spellcard] = { 1.00, 0.65, 0.65, 1.00 },
[AT_SurvivalSpell] = { 0.50, 0.50, 1.00, 1.00 },
[AT_ExtraSpell] = { 1.00, 0.30, 0.20, 1.00 },
[AT_Immediate] = { 1.00, 1.00, 1.00, 1.00 },
};
assert(atype >= 0 && atype < sizeof(colors)/sizeof(*colors));
return colors + atype;
}
static StageProgress* get_spellstage_progress(Attack *a, StageInfo **out_stginfo, bool write) {
@ -175,14 +183,17 @@ static void BossGlow(Projectile *p, int t) {
float s = 1.0+t/(double)p->timeout*0.5;
float fade = 1 - (1.5 - s);
float deform = 5 - 10 * fade * fade;
Color c = p->color;
c.a = 0;
color_mul_scalar(&c, 1.5 - s);
r_draw_sprite(&(SpriteParams) {
.pos = { creal(p->pos), cimag(p->pos) },
.sprite_ptr = p->sprite,
.scale.both = s,
.blend = BLEND_ADD,
.color = derive_color(p->color, CLRMASK_A, rgba(0, 0, 0, 1.5 - s)),
.custom = deform,
.color = &c,
.shader_params = &(ShaderCustomParams){{ deform }},
.shader = "sprite_silhouette",
});
}
@ -195,7 +206,7 @@ static int boss_glow(Projectile *p, int t) {
return linear(p, t);
}
static Projectile* spawn_boss_glow(Boss *boss, Color clr, int timeout) {
static Projectile* spawn_boss_glow(Boss *boss, const Color *clr, int timeout) {
// XXX: memdup is required because the Sprite returned by animation_get_frame is only temporarily valid
return PARTICLE(
.sprite_ptr = memdup(aniplayer_get_frame(&boss->ani), sizeof(Sprite)),
@ -211,8 +222,8 @@ static Projectile* spawn_boss_glow(Boss *boss, Color clr, int timeout) {
}
static void spawn_particle_effects(Boss *boss) {
Color glowcolor = boss->glowcolor;
Color shadowcolor = boss->shadowcolor;
Color *glowcolor = &boss->glowcolor;
Color *shadowcolor = &boss->shadowcolor;
Attack *cur = boss->current;
bool is_spell = cur && ATTACK_IS_SPELL(cur->type) && !cur->endtime;
@ -222,28 +233,25 @@ static void spawn_particle_effects(Boss *boss) {
PARTICLE(
.sprite = "smoke",
.pos = cexp(I*global.frames),
.color = shadowcolor,
.color = RGBA(shadowcolor->r, shadowcolor->g, shadowcolor->b, 0.0),
.rule = enemy_flare,
.timeout = 180,
.draw_rule = Shrink,
.args = { 0, add_ref(boss), },
.angle = M_PI * 2 * frand(),
.flags = PFLAG_DRAWADD,
);
}
if(!(global.frames % (2 + 2 * is_extra)) && (is_spell || boss_is_dying(boss))) {
float glowstr = 0.5;
float a = (1.0 - glowstr) + glowstr * pow(psin(global.frames/15.0), 1.0);
spawn_boss_glow(boss, multiply_colors(glowcolor, rgb(a, a, a)), 24);
float a = (1.0 - glowstr) + glowstr * psin(global.frames/15.0);
spawn_boss_glow(boss, color_mul_scalar(COLOR_COPY(glowcolor), a), 24);
}
}
void draw_boss_background(Boss *boss) {
r_mat_push();
r_mat_translate(creal(boss->pos), cimag(boss->pos), 0);
r_blend(BLEND_ADD);
r_mat_rotate_deg(global.frames*4.0, 0, 0, -1);
float f = 0.8+0.1*sin(global.frames/8.0);
@ -253,9 +261,11 @@ void draw_boss_background(Boss *boss) {
f -= t*(t-0.7)/max(0.01, 1-t);
}
r_mat_scale(f,f,f);
draw_sprite_batched(0, 0, "boss_circle");
r_blend(BLEND_ALPHA);
r_mat_scale(f, f, 1);
r_draw_sprite(&(SpriteParams) {
.sprite = "boss_circle",
.color = RGBA(1, 1, 1, 0),
});
r_mat_pop();
}
@ -275,14 +285,14 @@ static void ent_draw_boss(EntityInterface *ent) {
boss_alpha = (1 - t) + 0.3;
}
r_color4(1,1-red,1-red/2,boss_alpha);
r_color(RGBA_MUL_ALPHA(1, 1-red, 1-red/2, boss_alpha));
draw_sprite_batched_p(creal(boss->pos), cimag(boss->pos) + 6*sin(global.frames/25.0), aniplayer_get_frame(&boss->ani));
r_color4(1,1,1,1);
r_color4(1, 1, 1, 1);
if(boss->current->type == AT_Move && global.frames - boss->current->starttime > 0 && boss_attack_is_final(boss, boss->current))
return;
draw_boss_text(ALIGN_LEFT, 10, 20, boss->name, "standard", rgb(1, 1, 1));
draw_boss_text(ALIGN_LEFT, 10, 20, boss->name, "standard", RGB(1, 1, 1));
if(!boss->current)
return;
@ -296,15 +306,15 @@ static void ent_draw_boss(EntityInterface *ent) {
Color textclr;
if(remaining < 6) {
textclr = rgb(1.0, 0.2, 0.2);
textclr = *RGB(1.0, 0.2, 0.2);
} else if(remaining < 11) {
textclr = rgb(1.0, 1.0, 0.2);
textclr = *RGB(1.0, 1.0, 0.2);
} else {
textclr = rgb(1.0, 1.0, 1.0);
textclr = *RGB(1.0, 1.0, 1.0);
}
snprintf(buf, sizeof(buf), "%.2f", remaining);
draw_boss_text(ALIGN_CENTER, VIEWPORT_W - 24, 10, buf, "standard", textclr);
draw_boss_text(ALIGN_CENTER, VIEWPORT_W - 24, 10, buf, "standard", &textclr);
StageProgress *p = get_spellstage_progress(boss->current, NULL, false);
if(p) {
@ -316,7 +326,7 @@ static void ent_draw_boss(EntityInterface *ent) {
draw_boss_text(ALIGN_RIGHT,
VIEWPORT_W + text_width(font, buf, 0) * pow(1 - a, 2),
35 + text_height(font, buf, 0),
buf, "small", rgba(1, 1, 1, a)
buf, "small", RGBA(1, 1, 1, a)
);
}
@ -379,7 +389,7 @@ static void ent_draw_boss(EntityInterface *ent) {
r_shader("sprite_default");
// remaining spells
r_color4(1,1,1,0.7);
r_color4(0.7, 0.7, 0.7, 0.7);
Sprite *star = get_sprite("star");
for(int x = 0, i = boss->acount-1; i > nextspell; i--) {
@ -420,15 +430,15 @@ void boss_rule_extra(Boss *boss, float alpha) {
PARTICLE(
.sprite = (frand() < v*0.3 || lt > 1) ? "stain" : "arc",
.pos = boss->pos + dir * (100 + 50 * psin(alpha*global.frames/10.0+2*i)) * alpha,
.color = rgb(
.color = RGBA(
1.0 - 0.5 * psina * v,
0.5 + 0.2 * psina * (1-v),
0.5 + 0.5 * psina * v
0.5 + 0.5 * psina * v,
0.0
),
.rule = linear,
.timeout = 30*lt,
.draw_rule = GrowFade,
.flags = PFLAG_DRAWADD,
.args = { vel * (1 - 2 * !(global.frames % 10)), 2.5 },
);
}
@ -496,7 +506,7 @@ static void boss_give_spell_bonus(Boss *boss, Attack *a, Player *plr) {
player_add_points(plr, total);
StageTextTable tbl;
stagetext_begin_table(&tbl, title, rgb(1, 1, 1), rgb(1, 1, 1), VIEWPORT_W/2, 0,
stagetext_begin_table(&tbl, title, RGB(1, 1, 1), RGB(1, 1, 1), VIEWPORT_W/2, 0,
ATTACK_END_DELAY_SPELL * 2, ATTACK_END_DELAY_SPELL / 2, ATTACK_END_DELAY_SPELL);
stagetext_table_add_numeric_nonzero(&tbl, "Clear bonus", clear_bonus);
stagetext_table_add_numeric_nonzero(&tbl, "Time bonus", time_bonus);
@ -691,19 +701,21 @@ void process_boss(Boss **pboss) {
float t = (global.frames - boss->current->endtime)/(float)BOSS_DEATH_DELAY + 1;
tsrand_fill(6);
Color *clr = RGBA_MUL_ALPHA(0.1 + sin(10*t), 0.1 + cos(10*t), 0.5, t);
clr->a = 0;
PARTICLE(
.sprite = "petal",
.pos = boss->pos,
.rule = asymptotic,
.draw_rule = Petal,
.color = rgba(0.1+sin(10*t),0.1+cos(10*t),0.5,t),
.color = clr,
.args = {
sign(anfrand(5))*(3+t*5*afrand(0))*cexp(I*M_PI*8*t),
5+I,
afrand(2) + afrand(3)*I,
afrand(4) + 360.0*I*afrand(1)
},
.flags = PFLAG_DRAWADD,
);
if(!extra) {
@ -716,7 +728,7 @@ void process_boss(Boss **pboss) {
if(t == 1) {
for(int i = 0; i < 10; ++i) {
spawn_boss_glow(boss, boss->glowcolor, 60 + 20 * i);
spawn_boss_glow(boss, &boss->glowcolor, 60 + 20 * i);
}
for(int i = 0; i < 256; i++) {
@ -846,12 +858,11 @@ void boss_start_attack(Boss *b, Attack *a) {
PARTICLE(
.sprite = "stain",
.pos = VIEWPORT_W/2 + VIEWPORT_W/4*anfrand(0)+I*VIEWPORT_H/2+I*anfrand(1)*30,
.color = rgb(0.2,0.3,0.4),
.color = RGBA(0.2, 0.3, 0.4, 0.0),
.rule = linear,
.timeout = 50,
.draw_rule = GrowFade,
.args = { sign(anfrand(2))*10*(1+afrand(3)) },
.flags = PFLAG_DRAWADD,
);
}

View file

@ -11,95 +11,86 @@
#include <stdio.h>
#include "color.h"
static const float conv = 1.0f / CLR_ONEVALUE;
#define COLOR_OP(c1, op, c2) do { \
(c1)->r = (c1)->r op (c2)->r; \
(c1)->g = (c1)->g op (c2)->g; \
(c1)->b = (c1)->b op (c2)->b; \
(c1)->a = (c1)->a op (c2)->a; \
} while(0);
#ifndef COLOR_INLINE
Color rgba(float r, float g, float b, float a) {
assert(!r || isnormal(r));
assert(!g || isnormal(g));
assert(!b || isnormal(b));
assert(!a || isnormal(a));
return RGBA(r, g, b, a);
Color* color_copy(Color *dst, const Color *src) {
*dst = *src;
return dst;
}
Color rgb(float r, float g, float b) {
assert(!r || isnormal(r));
assert(!g || isnormal(g));
assert(!b || isnormal(b));
return RGB(r, g, b);
Color* color_add(Color *clr, const Color *clr2) {
COLOR_OP(clr, +, clr2);
return clr;
}
#endif
void parse_color(Color clr, float *r, float *g, float *b, float *a) {
*r = (ColorComponent)((clr >> CLR_R) & CLR_CMASK) * conv;
*g = (ColorComponent)((clr >> CLR_G) & CLR_CMASK) * conv;
*b = (ColorComponent)((clr >> CLR_B) & CLR_CMASK) * conv;
*a = (ColorComponent)((clr >> CLR_A) & CLR_CMASK) * conv;
Color* color_sub(Color *clr, const Color *clr2) {
COLOR_OP(clr, -, clr2);
return clr;
}
void parse_color_array(Color clr, float array[4]) {
parse_color(clr, array, array+1, array+2, array+3);
Color* color_mul(Color *clr, const Color *clr2) {
COLOR_OP(clr, *, clr2);
return clr;
}
Color derive_color(Color src, Color mask, Color mod) {
return (src & ~mask) | (mod & mask);
Color* color_mul_alpha(Color *clr) {
clr->r *= clr->a;
clr->g *= clr->a;
clr->b *= clr->a;
return clr;
}
float color_component(Color clr, uint ofs) {
return (ColorComponent)((clr >> ofs) & CLR_CMASK) * conv;
Color* color_mul_scalar(Color *clr, float scalar) {
clr->r *= scalar;
clr->g *= scalar;
clr->b *= scalar;
clr->a *= scalar;
return clr;
}
Color multiply_colors(Color c1, Color c2) {
float c1a[4], c2a[4];
parse_color_array(c1, c1a);
parse_color_array(c2, c2a);
return rgba(c1a[0]*c2a[0], c1a[1]*c2a[1], c1a[2]*c2a[2], c1a[3]*c2a[3]);
Color* color_div(Color *clr, const Color *clr2) {
COLOR_OP(clr, /, clr2);
return clr;
}
Color add_colors(Color c1, Color c2) {
float c1a[4], c2a[4];
parse_color_array(c1, c1a);
parse_color_array(c2, c2a);
return rgba(c1a[0]+c2a[0], c1a[1]+c2a[1], c1a[2]+c2a[2], c1a[3]+c2a[3]);
Color* color_div_alpha(Color *clr) {
if(clr->a != 0) {
clr->r /= clr->a;
clr->g /= clr->a;
clr->b /= clr->a;
}
return clr;
}
Color subtract_colors(Color c1, Color c2) {
float c1a[4], c2a[4];
parse_color_array(c1, c1a);
parse_color_array(c2, c2a);
return rgba(c1a[0]-c2a[0], c1a[1]-c2a[1], c1a[2]-c2a[2], c1a[3]-c2a[3]);
Color* color_div_scalar(Color *clr, float scalar) {
clr->r /= scalar;
clr->g /= scalar;
clr->b /= scalar;
clr->a /= scalar;
return clr;
}
Color divide_colors(Color c1, Color c2) {
float c1a[4], c2a[4];
parse_color_array(c1, c1a);
parse_color_array(c2, c2a);
return rgba(c1a[0]/c2a[0], c1a[1]/c2a[1], c1a[2]/c2a[2], c1a[3]/c2a[3]);
Color* color_lerp(Color *clr, const Color *clr2, float a) {
float ia = 1 - a;
clr->r = clr->r * ia + clr2->r * a;
clr->g = clr->g * ia + clr2->g * a;
clr->b = clr->b * ia + clr2->b * a;
clr->a = clr->a * ia + clr2->a * a;
return clr;
}
Color mix_colors(Color c1, Color c2, double a) {
float c1a[4], c2a[4];
double f1 = a;
double f2 = 1 - f1;
parse_color_array(c1, c1a);
parse_color_array(c2, c2a);
return rgba(f1*c1a[0]+f2*c2a[0], f1*c1a[1]+f2*c2a[1], f1*c1a[2]+f2*c2a[2], f1*c1a[3]+f2*c2a[3]);
}
Color approach_color(Color src, Color dst, double delta) {
float c1a[4], c2a[4];
parse_color_array(src, c1a);
parse_color_array(dst, c2a);
return rgba(
c1a[0] + (c2a[0] - c1a[0]) * delta,
c1a[1] + (c2a[1] - c1a[1]) * delta,
c1a[2] + (c2a[2] - c1a[2]) * delta,
c1a[3] + (c2a[3] - c1a[3]) * delta
);
Color* color_approach(Color *clr, const Color *clr2, float delta) {
clr->r += (clr2->r - clr->r) * delta;
clr->g += (clr2->g - clr->g) * delta;
clr->b += (clr2->b - clr->b) * delta;
clr->a += (clr2->a - clr->a) * delta;
return clr;
}
static float hue_to_rgb(float v1, float v2, float vH) {
@ -126,11 +117,9 @@ static float hue_to_rgb(float v1, float v2, float vH) {
return v1;
}
Color hsla(float h, float s, float l, float a) {
float r, g, b;
Color* color_hsla(Color *clr, float h, float s, float l, float a) {
if(s == 0) {
r = g = b = l;
clr->r = clr->g = clr->b = l;
} else {
float v1, v2;
h = fmod(h, 1.0);
@ -143,20 +132,42 @@ Color hsla(float h, float s, float l, float a) {
v1 = 2.0 * l - v2;
r = hue_to_rgb(v1, v2, h + (1.0/3.0));
g = hue_to_rgb(v1, v2, h);
b = hue_to_rgb(v1, v2, h - (1.0/3.0));
clr->r = hue_to_rgb(v1, v2, h + (1.0/3.0));
clr->g = hue_to_rgb(v1, v2, h);
clr->b = hue_to_rgb(v1, v2, h - (1.0/3.0));
}
return rgba(r, g, b, a);
clr->a = a;
return clr;
}
Color hsl(float h, float s, float l) {
return hsla(h, s, l, 1.0);
Color* color_set_opacity(Color *clr, float opacity) {
// FIXME: is this correct?
if(clr->a != 0) {
opacity /= clr->a;
}
color_mul_scalar(clr, opacity);
return clr;
}
char* color_str(Color c) {
float r, g, b, a;
parse_color(c, &r, &g, &b, &a);
return strfmt("rgba(%f, %f, %f, %f) 0x%016"PRIxMAX, r, g, b, a, (uintmax_t)c);
bool color_equals(const Color *clr, const Color *clr2) {
return (
clr->r == clr2->r &&
clr->g == clr2->g &&
clr->b == clr2->b &&
clr->a == clr2->a
);
}
char* color_str(const Color *clr) {
return strfmt(
"RGBA(%f, %f, %f, %f) at %p",
clr->r,
clr->g,
clr->b,
clr->a,
(void*)clr
);
}

View file

@ -11,66 +11,74 @@
#include "util.h"
#define CLR_R ((Color)48)
#define CLR_G ((Color)32)
#define CLR_B ((Color)16)
#define CLR_A ((Color)0)
typedef struct Color {
float r, g, b, a;
} Color;
#define CLR_CMASK ((Color)0xffff)
#define CLR_ONEVALUE ((Color)0xff)
/*
* These macros return a pointer to a new Color instance, which is *block-scoped*,
* and has automatic storage-class.
*/
#define CLRMASK_R (CLR_CMASK << CLR_R)
#define CLRMASK_G (CLR_CMASK << CLR_G)
#define CLRMASK_B (CLR_CMASK << CLR_B)
#define CLRMASK_A (CLR_CMASK << CLR_A)
#define RGBA(r, g, b, a) (&(Color) { (r), (g), (b), (a) })
#define RGBA_MUL_ALPHA(r, g, b, a) color_mul_alpha(RGBA((r), (g), (b), (a)))
#define RGB(r, g, b) RGBA((r), (g), (b), 1)
typedef uint64_t Color;
typedef int16_t ColorComponent;
#define HSLA(h, s, l, a) color_hsla((&(Color) { 0 }), (h), (s), (l), (a))
#define HSLA_MUL_ALPHA(h, s, l, a) color_mul_alpha(HSLA((h), (s), (l), (a)))
#define HSL(h, s, l) HSLA((h), (s), (l), 1)
#ifndef COLOR_INLINE
#ifdef NDEBUG
#define COLOR_INLINE
#endif
#endif
#define COLOR_COPY(c) color_copy((&(Color) { 0 }), (c))
#ifdef COLOR_INLINE
#define rgba(r,g,b,a) RGBA(r,g,b,a)
#define rgb(r,g,b) RGB(r,g,b)
#else
Color rgba(float r, float g, float b, float a) attr_const;
Color rgb(float r, float g, float b) attr_const;
#endif
/*
* All of these modify the first argument in-place, and return it for convenience.
*/
Color hsla(float h, float s, float l, float a) attr_const;
Color hsl(float h, float s, float l) attr_const;
Color* color_copy(Color *dst, const Color *src)
attr_nonnull(1) attr_returns_nonnull;
void parse_color(Color clr, float *r, float *g, float *b, float *a) attr_nonnull(2, 3, 4, 5);
void parse_color_array(Color clr, float array[4]) attr_nonnull(2);
Color derive_color(Color src, Color mask, Color mod) attr_const;
Color multiply_colors(Color c1, Color c2) attr_const;
Color add_colors(Color c1, Color c2) attr_const;
Color subtract_colors(Color c1, Color c2) attr_const;
Color mix_colors(Color c1, Color c2, double a) attr_const;
Color approach_color(Color src, Color dst, double delta) attr_const;
float color_component(Color clr, uint ofs) attr_const;
char* color_str(Color c) attr_returns_nonnull;
Color* color_hsla(Color *clr, float h, float s, float l, float a)
attr_nonnull(1) attr_returns_nonnull;
#ifdef RGBA
#undef RGBA
#endif
Color* color_add(Color *clr, const Color *clr2)
attr_nonnull(1) attr_returns_nonnull;
#ifdef RGB
#undef RGB
#endif
Color* color_sub(Color *clr, const Color *clr2)
attr_nonnull(1) attr_returns_nonnull;
#define RGBA(r,g,b,a) \
((((Color)(ColorComponent)(CLR_ONEVALUE * (r)) & CLR_CMASK) << CLR_R) + \
(((Color)(ColorComponent)(CLR_ONEVALUE * (g)) & CLR_CMASK) << CLR_G) + \
(((Color)(ColorComponent)(CLR_ONEVALUE * (b)) & CLR_CMASK) << CLR_B) + \
(((Color)(ColorComponent)(CLR_ONEVALUE * (a)) & CLR_CMASK) << CLR_A))
Color* color_mul(Color *clr, const Color *clr2)
attr_nonnull(1) attr_returns_nonnull;
#define RGB(r,g,b) \
((((Color)(ColorComponent)(CLR_ONEVALUE * (r)) & CLR_CMASK) << CLR_R) + \
(((Color)(ColorComponent)(CLR_ONEVALUE * (g)) & CLR_CMASK) << CLR_G) + \
(((Color)(ColorComponent)(CLR_ONEVALUE * (b)) & CLR_CMASK) << CLR_B) + \
(CLR_ONEVALUE << CLR_A))
Color* color_mul_alpha(Color *clr)
attr_nonnull(1) attr_returns_nonnull;
Color* color_mul_scalar(Color *clr, float scalar)
attr_nonnull(1) attr_returns_nonnull;
Color* color_div(Color *clr, const Color *clr2)
attr_nonnull(1) attr_returns_nonnull;
Color* color_div_alpha(Color *clr)
attr_nonnull(1) attr_returns_nonnull;
Color* color_div_scalar(Color *clr, float scalar)
attr_nonnull(1) attr_returns_nonnull;
Color* color_lerp(Color *clr, const Color *clr2, float a)
attr_nonnull(1) attr_returns_nonnull;
Color* color_approach(Color *clr, const Color *clr2, float delta)
attr_nonnull(1) attr_returns_nonnull;
Color* color_set_opacity(Color *clr, float opacity)
attr_nonnull(1) attr_returns_nonnull;
/*
* End of color manipulation routines.
*/
bool color_equals(const Color *clr, const Color *clr2)
attr_nonnull(1, 2);
char* color_str(const Color *clr)
attr_nonnull(1) attr_returns_nonnull attr_nodiscard;

View file

@ -307,7 +307,7 @@ static void credits_draw_entry(CreditsEntry *e) {
render_pop();
*/
r_color4(1, 1, 1, fadein * fadeout);
r_color(RGBA_MUL_ALPHA(1, 1, 1, fadein * fadeout));
if(yukkuri) {
r_mat_translate(0, (-h_body) * 0.5, 0);

View file

@ -11,37 +11,46 @@
#include "difficulty.h"
#include "resource/resource.h"
const char* difficulty_name(Difficulty diff) {
switch(diff) {
case D_Easy: return "Easy";
case D_Normal: return "Normal";
case D_Hard: return "Hard";
case D_Lunatic: return "Lunatic";
case D_Extra: return "Extra";
default: return "Unknown";
typedef struct DiffDef {
const char *name;
const char *spr_name;
Color color;
} DiffDef;
static DiffDef diffs[] = {
{ "Easy", "difficulty/easy", { 0.5, 1.0, 0.5, 1.0 } },
{ "Normal", "difficulty/normal", { 0.5, 0.5, 1.0, 1.0 } },
{ "Hard", "difficulty/hard", { 1.0, 0.5, 0.5, 1.0 } },
{ "Lunatic", "difficulty/lunatic", { 1.0, 0.5, 1.0, 1.0 } },
// TODO: sprite for this
{ "Extra", "difficulty/lunatic", { 0.5, 1.0, 1.0, 1.0 } },
};
static inline DiffDef* get_diff_def(Difficulty diff) {
uint idx = diff - D_Easy;
if(idx < sizeof(diffs)/sizeof(*diffs)) {
return diffs + idx;
}
return NULL;
}
const char* difficulty_name(Difficulty diff) {
DiffDef *d = get_diff_def(diff);
return d ? d->name : "Unknown";
}
const char* difficulty_sprite_name(Difficulty diff) {
switch(diff) {
case D_Easy: return "difficulty/easy";
case D_Normal: return "difficulty/normal";
case D_Hard: return "difficulty/hard";
case D_Lunatic: return "difficulty/lunatic";
case D_Extra: return "difficulty/lunatic";
default: return "difficulty/unknown"; // This sprite is not supposed to exist.
}
DiffDef *d = get_diff_def(diff);
return d ? d->spr_name : "difficulty/unknown";
}
Color difficulty_color(Difficulty diff) {
switch(diff) {
case D_Easy: return rgb(0.5, 1.0, 0.5);
case D_Normal: return rgb(0.5, 0.5, 1.0);
case D_Hard: return rgb(1.0, 0.5, 0.5);
case D_Lunatic: return rgb(1.0, 0.5, 1.0);
case D_Extra: return rgb(0.5, 1.0, 1.0);
default: return rgb(0.5, 0.5, 0.5);
}
const Color* difficulty_color(Difficulty diff) {
static Color unknown_clr = { 0.5, 0.5, 0.5, 1.0 };
DiffDef *d = get_diff_def(diff);
return d ? &d->color : &unknown_clr;
}
void difficulty_preload(void) {

View file

@ -23,12 +23,12 @@ typedef enum {
#define NUM_SELECTABLE_DIFFICULTIES D_Lunatic
const char* difficulty_name(Difficulty diff)
attr_const attr_returns_nonnull;
attr_pure attr_returns_nonnull;
const char* difficulty_sprite_name(Difficulty diff)
attr_const attr_returns_nonnull;
attr_pure attr_returns_nonnull;
Color difficulty_color(Difficulty diff)
attr_const;
const