Пример #1
0
Colour Colour::hsvMod(glm::ivec3 _hsvDelta){
	glm::ivec3 hsv = rgbToHsv(*this) + _hsvDelta;
	hsv.x = glm::mod((float)hsv.x, 360.f);
	hsv.y = glm::min(100, glm::max(0, hsv.y));
	hsv.z = glm::min(100, glm::max(0, hsv.z));
	return Colour(hsvToRgb(hsv));
}
Пример #2
0
//-------------------------------------------------------------------
void
MyColormap::init()
{
	begin = 0;
	end = MY_COLORMAP_RES;
	bDrawVirtical = false;	// horizontal
	type = 0;
	
	int r, g, b;
	float coef = 360.0/MY_COLORMAP_RES;
	for(int i=0; i<MY_COLORMAP_RES; i++)
	{
		// go through hue cycle from 0 to 359
//		hsvToRgb( 360-i, 255, (int)(255*((i-lowerlimit)/360.0)), r, g, b );
		hsvToRgb(r, g, b, int(i*coef), 255, 255);
		data[i][0] = r;
		data[i][1] = g;
		data[i][2] = b;
		data[i][3] = 255;
	}

	glGenTextures(1, &tid);
	glBindTexture(GL_TEXTURE_1D, tid);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, MY_COLORMAP_RES, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
}
Пример #3
0
void
idToRgb(unsigned int id, unsigned char& r, unsigned char& g, unsigned char& b) {

	float h = fmod(static_cast<float>(id)*M_PI, 1.0);
	float s = 0.5 + fmod(static_cast<float>(id)*M_PI*2, 0.5);
	float v = (id == 0 ? 0.0 : 0.75 + fmod(static_cast<float>(id)*M_PI*3, 0.25));
	hsvToRgb(h, s, v, r, g, b);
}
Пример #4
0
ColorRGB *ProceduralTexture::generateCelluarTexture(int size, ColorHSV color, int hueRange){
	if (size<2)
		size = 2;
	int cellX = w/size+2;
	int cellY = h/size+2;
	int pointsX[cellX][cellY];
	int pointsY[cellX][cellY];
	ColorRGB cellColor[cellX][cellY];
	srand(seed);
	for (int i=0; i<cellX; i++)
		for (int j=0; j<cellY; j++){
			pointsX[i][j] = i*size+rand()%((int)(size*0.7))+size*0.15-size;
			pointsY[i][j] = j*size+rand()%((int)(size*0.7))+size*0.15-size;
		}
	color.h -= (hueRange/2);
	for (int i=0; i<cellX; i++)
		for (int j=0; j<cellY; j++){
			ColorHSV c = color;
			c.h += rand()%hueRange;
			cellColor[i][j] = hsvToRgb(c);
		}

	ColorRGB *img = new ColorRGB[n];
	for (int i=0; i<n; i++){
		int x = i%w;
		int y = i/w;
		int px = 0;
		int py = 0;
		int min = INT_MAX;
		int startX = x/size;
		int finishX = startX+3;
		for (; startX<finishX; startX++){
			int startY = y/size;
			int finishY = startY+3;
			for (; startY<finishY; startY++){
				if (startX<0 || startX>=cellX || startY<0 || startY>=cellY)
					continue;
				int d = distance(x, y, pointsX[startX][startY], pointsY[startX][startY]);
				if (d<min){
					px = startX;
					py = startY;
					min = d;
				}
			}
		}
		img[i] = cellColor[px][py];
	}
	return img;
}
void CycleHue(int huePoints)
{
    int hueGap;
    if ((lastHue - targetHue) > 0) hueGap = lastHue - targetHue;
    else hueGap = (lastHue - targetHue) * -1;
    if (hueGap > huePoints)
    {
        if (lastHue > targetHue) lastHue = lastHue - huePoints;
        else lastHue = lastHue + huePoints;
    }
    else
    {
        lastHue = targetHue;
        targetHue = initialHue;
        initialHue = lastHue;
    }
    //for the waveform to match the lights
    hsvToRgb(((float)lastHue / 65535.0f), 1.0f, 1.0f, rgb);
}
Пример #6
0
void LEDRainbowEffect::TransientLEDMode::update(void) {
  if (!Kaleidoscope.has_leds)
    return;

  uint16_t now = millis();
  if ((now - rainbow_last_update) < parent_->rainbow_update_delay) {
    return;
  } else {
    rainbow_last_update = now;
  }

  cRGB rainbow = hsvToRgb(rainbow_hue, rainbow_saturation, parent_->rainbow_value);

  rainbow_hue += rainbow_steps;
  if (rainbow_hue >= 255) {
    rainbow_hue -= 255;
  }
  ::LEDControl.set_all_leds_to(rainbow);
}
Пример #7
0
void ColorT<T>::set( ColorModel cm, const vec3 &v )
{
	switch( cm ) {
		case CM_HSV: {
			Colorf rgb = hsvToRgb( v );
			r = CHANTRAIT<T>::convert( rgb.r );
			g = CHANTRAIT<T>::convert( rgb.g );
			b = CHANTRAIT<T>::convert( rgb.b );
		}
		break;
		case CM_RGB:
			r = CHANTRAIT<T>::convert( v.x );
			g = CHANTRAIT<T>::convert( v.y );
			b = CHANTRAIT<T>::convert( v.z );
		break;
		default:
			throw;
	}
}
Пример #8
0
void ColorAT<T>::set( ColorModel cm, const vec4 &v )
{
	a = v.w;

	switch( cm ) {
		case CM_HSV: {
			Colorf rgb = hsvToRgb( vec3( v ) );
			r = CHANTRAIT<T>::convert( rgb.r );
			g = CHANTRAIT<T>::convert( rgb.g );
			b = CHANTRAIT<T>::convert( rgb.b );
		}
		break;
		case CM_RGB:
			r = CHANTRAIT<T>::convert( v.r );
			g = CHANTRAIT<T>::convert( v.g );
			b = CHANTRAIT<T>::convert( v.b );
		break;
		default:
			throw ImageIoExceptionIllegalColorModel();
	}
}
Пример #9
0
void Gradient::fillGradient(QRgb* buffer, int length, double* data) const
{
    double* temp = data ? data : new double[length * 3];
    double* t1 = temp;
    double* t2 = temp + length;
    double* t3 = temp + 2 * length;

    _spline[0].generateSpline(t1, length);
    _spline[1].generateSpline(t2, length);
    _spline[2].generateSpline(t3, length);

    if (_isHsv) {
        for (int i=0; i < length; i++)
            buffer[i] = hsvToRgb(t1[i], t2[i], t3[i]);
    } else {
        for (int i=0; i < length; i++)
            buffer[i] = qRgb((int)(t1[i] * 255.0), (int)(t2[i] * 255.0), (int)(t3[i] * 255.0));
    }

    if (!data)
        delete[] temp;
}
Пример #10
0
void LEDRainbowWaveEffect::TransientLEDMode::update(void) {
  if (!Kaleidoscope.has_leds)
    return;

  uint16_t now = millis();
  if ((now - rainbow_last_update) < parent_->rainbow_update_delay) {
    return;
  } else {
    rainbow_last_update = now;
  }

  for (int8_t i = 0; i < LED_COUNT; i++) {
    uint16_t key_hue = rainbow_hue + 16 * (i / 4);
    if (key_hue >= 255)          {
      key_hue -= 255;
    }
    cRGB rainbow = hsvToRgb(key_hue, rainbow_saturation, parent_->rainbow_value);
    ::LEDControl.setCrgbAt(i, rainbow);
  }
  rainbow_hue += rainbow_wave_steps;
  if (rainbow_hue >= 255) {
    rainbow_hue -= 255;
  }
}
//sets the colour to the given hue
void updateColor(float h) {

	//scale the saturation position by 2 and reflect around 1
	//s=abs((s*2.0)-1.0);
	//scale the saturation position by 2 and reflect around 1
	//v=abs((v*2.0)-1.0);

	float s = 1;
	float v = 1;

	float r=0;
	float g=0;
	float b=0;

	//find the rgb values	
	hsvToRgb(&r, &g, &b, h, s, v);

	//set the duty cycle
    duty[RED]=r*255; 
	duty[GREEN]=g*255;
	duty[BLUE]=b*255;

    
}
QColor ColorOperations::intToRgb(unsigned char val) {
  QColor hsv;
  hsv.setRgbF(intToHue(val), 1.0, 1.0, 1.0);

  return hsvToRgb(hsv);
}
Пример #13
0
/**
 * Funktion zum verteilten berechnen der 2D-Temperaturverteilung.
 * @param status_flag Zeiger auf Variable, die enthält ob der Thread beendet ist. (0 = Beendet)
 * @param value_img Liste für die Daten der Temperaturverteilung.
 * @param image Grafik für die Temperaturverteilung.
 * @param width Breite der Temperaturverteilungsgrafik.
 * @param height Höhe der Temperaturverteilungsgrafik.
 * @param startheight Starthöhe für diesen Thread in der Grafik.
 * @param delta_h Höhe des von diesem Thread zu berechnenden Streifens.
 * @param info Informationen über die Eigenschaften der zu berechnenden Ebene.
 * @param xvec X-Achse der Ebene.
 * @param yvec Y-Achse der Ebene.
 * @param v0 Mittelpunkt der Ebene.
 * @param bases Möglichst einfache Geometrien Geometrien der Materialien.
 * @param obj Das aktuelle Objekt.
 * @param sensor_data Die zu verwendenden Sensordaten.
 * @param use_last_tet Versuchen, die Interpolation durch vorgezogenes Testen des zuletzt verwendeten Tetraeders zu beschleunigen.
 * Diese Option ist verursacht Ungenauigkeiten und bietet zumeist wenig Performancegewinn.
 */
void render_thread(bool* status_flag, float* value_img, wxImage* image,
		int width, int height, int startheight, int delta_h,
		CutRender_info* info, Vector3D* xvec, Vector3D* yvec, Vector3D* v0,
		vector<tetgenio*>* bases, ObjectData* obj,
		vector<SensorPoint>* sensor_data, bool use_last_tet) {

	Interpolator interpolator;
	//Referenz auf den letzten für die Interpolation gewählten Tetraeder
	vector<SensorPoint*>* last_tet = new vector<SensorPoint*>;
	//Referenz auf den aktuell für die Interpolation gewählten Tetraeder
	vector<SensorPoint*>* new_tet = new vector<SensorPoint*>;
	//Die Visualisierungsinformationen
	Visualization_info* vis_info = wxGetApp().getVisualizationInfo();

	//Wurde der zuletzt verwendete Tetraeder initialisiert?
	bool last_tet_init = false;
	//Für alle Pixel im zu berechnenden Streifen...
	for (int x = 0; x < width; x++) {
		for (int y = startheight; y < startheight + delta_h; y++) {

			//Position des Pixels im 3D-Raum
			Vector3D* p = v0->copy();

			//Verschiebung auf der X-Achse der Ebene im vergleich zum Ebenenmittelpunkt
			Vector3D* part_x = xvec->copy();
			part_x->mult(
					x * info->mmperpixel / 1000.
							- width * info->mmperpixel / 2000);

			//Verschiebung auf der Y-Achse der Ebene im vergleich zum Ebenenmittelpunkt
			Vector3D* part_y = yvec->copy();
			part_y->mult(
					y * info->mmperpixel / 1000.
							- height * info->mmperpixel / 2000);

			//Berechnen der Position des Pixels im 3D-Raum
			p->add(part_x);
			p->add(part_y);
			delete part_x;
			delete part_y;

			//Initialisieren des Pixels
			image->SetAlpha(x, y, 0);
			image->SetRGB(x, y, 0, 0, 0);
			value_img[y * width + x] = -300;

			//Für alle Materialien des objekts...
			for (unsigned int m = 0; m < obj->getMaterials()->size(); m++) {
				//Das aktuelle Material
				ObjectData::MaterialData* mat = &obj->getMaterials()->at(m);

				//Befindet sich der Punkt im aktuellen Material?
				if (pointInsideMesh(p, bases->at(m),
						info->in_volume_algorithm)) {

					//Ermitteln des Wertes für den Punkt
					int status = 0;
					interpolator.setMode(mat->interpolation_mode);
					float value = (float) getPointValue(status, sensor_data,
							p->getXYZ(), &interpolator,
							(last_tet_init && use_last_tet) ? last_tet : NULL,
							use_last_tet ? new_tet : 0);

					//Tausch der Speicherorte des Tetraeders, in dem sich der Punkt in diesem und im vorherigen Durchlauf befand
					vector<SensorPoint*>* temp;
					temp = last_tet;
					last_tet = new_tet;
					new_tet = temp;
					last_tet_init = true;

					//Speichern des Wertes in der Temperaturverteilung
					value_img[y * width + x] = value;

					//Berechnen der Farbe zum Temperaturwert
					float inverse_hue = (value
							- vis_info->min_visualisation_temp)
							/ (vis_info->max_visualisation_temp
									+ vis_info->min_visualisation_temp);
					float* color = hsvToRgb(
							(1.0 - clampHue(inverse_hue)) * .666, 1, 1);

					//Speichern des Wertes in der Grafik
					image->SetRGB(x, y, (unsigned char) (color[0] * 255),
							(unsigned char) (color[1] * 255),
							(unsigned char) (color[2] * 255));
					delete color;
					image->SetAlpha(x, y, 255);
					break;
				}
			}
			delete p;
		}
	}
	delete new_tet;
	delete last_tet;

	//markieren des Threads als beendet
	*status_flag = 0;
}
Пример #14
0
Colour Colour::getRandomFromHsvMean(glm::ivec3 _mean, glm::ivec3 _halfRange){
	return hsvToRgb(glm::ivec3(_mean + glm::ivec3(sweet::NumberUtils::randomInt(-_halfRange.r, _halfRange.r), sweet::NumberUtils::randomInt(-_halfRange.g, _halfRange.g), sweet::NumberUtils::randomInt(-_halfRange.b, _halfRange.b))));
}
Пример #15
0
Colour Colour::getRandomFromHsvRange(glm::ivec3 _min, glm::ivec3 _max){
	return hsvToRgb(glm::ivec3(sweet::NumberUtils::randomInt(_min.r, _max.r), sweet::NumberUtils::randomInt(_min.g, _max.g), sweet::NumberUtils::randomInt(_min.b, _max.b)));
}
Пример #16
0
int main()
{
    printf("fader_funcs_tst\n");

    int led_start = 30;
    int led_end   = 60;
    int led_range = led_end - led_start;
    for( int i = 0; i< led_range; i++ ) { 
        int f = (256 * i) / led_range;  // scale range to 0-255
        int eased = ease8InOutApprox( f ) ; // get eased version
        int p = led_start + (eased * led_range)/256;
        printf("%d : %d : %d : %d\n", i, f, eased, p);
    }

    /*
    for( int p=ledpos_start; p < ledpos_end; p++ ) {
        int i = 256 * (p - ledpos_start)/ledpos_end;
        int easedledpos = p *  ease8InOutCubic( i ) / 256;
        printf("%d : %d : %d\n", i, p, easedledpos );
    }
    */
    exit(0);

    rgb_t ctmp;
    for( int i=0; i< 255; i++ ) {
        ctmp.h = i; ctmp.s=255; ctmp.v = 255;
        hsvToRgb( &ctmp );
        printf("%d: %d,%d,%d\n", i, ctmp.r, ctmp.g, ctmp.b );
    }

#if 0
    rgb_t ctmp; // = { 255,255,255 };
    //int ttmp = 15;
    //int ln = 5;
    int steps=10;

    ctmp.r=7; ctmp.g=100; ctmp.b=101;
    ledfader_setCurr( ctmp, 2);
    ctmp.r=203; ctmp.g=0; ctmp.b=0;
    steps = 1000;
    ledfader_setDest( ctmp, steps, 0 );
    for( int i=0; i< steps+1; i++) {
        ledfader_update();
    }
#endif

    /*
    ctmp.r=25; ctmp.g=250; ctmp.b=55;
    ledfader.setDest( &ctmp, 50, ln );

    for( int i=0; i< 55; i++) {
        ledfader.update();
    }

    ctmp.r=125; ctmp.g=2; ctmp.b=255;
    ledfader.setDest( &ctmp, 20, ln );

    for( int i=0; i< 25; i++) {
        ledfader.update();
    }

    ctmp.r=255; ctmp.g=200; ctmp.b=0;
    ledfader.setDest( &ctmp, 1000, ln );

    for( int i=0; i< 1010; i++) {
        ledfader.update();
    }
    */
}
/** Returns a pointer to a new image, based on 'surface'
    but with changed hue. Changing the hue means to "rotate"
    the color spectrum. You can read more about the HSV color
    model on the Internet.
    This method is used to change the color of a sprite
    (e.g. a car or another object). I suggest to make the
    basic images in a red color-spectrum and create all
    other colors from it.
    For examples, please visit the Trophy homepage developer
    corner (http://trophy.sourceforge.net)
    \param hue Changing of hue: 0-360
    \param saturation Changing of saturation: -100...100
    \param value Changing of value (Color intensity): -100...100
*/
CL_PixelBuffer
CAImageManipulation::changeHSV( CL_PixelBuffer pixBufOriginal,
                                int hue, int saturation, int value )
{
    CL_PixelBuffer pixbuf(pixBufOriginal.get_width(), pixBufOriginal.get_height(), pixBufOriginal.get_format());
    pixBufOriginal.convert(pixbuf);

    CL_TextureFormat pf = pixbuf.get_format();
    // Check that we handle this pixel format
    if(pf != CL_TextureFormat::cl_rgba8 && pf != CL_TextureFormat::cl_rgb8 &&
            pf != CL_TextureFormat::cl_rgba4 )
    {
        std::cout << "Unknow pixel format !" << pf << std::endl;
        return pixBufOriginal.copy();
    }

    // Calc size in bytes:
    //
    int bpp = pixBufOriginal.get_bytes_per_pixel();
    int size = pixbuf.get_width() * pixbuf.get_height() * bpp;

    pixbuf.lock(CL_BufferAccess::cl_access_read_write);

    unsigned char *data = (unsigned char*)pixbuf.get_data();

    // Change hue:
    //
    int r, g, b, a(0);
    int h, s, v;

    for(int i=0; i<size; i+=bpp )
    {
        if(pf == CL_TextureFormat::cl_rgba8)
        {
            a = data[i];
            b = data[i+1];
            g = data[i+2];
            r = data[i+3];
        } else if (pf == CL_TextureFormat::cl_rgb8)
        {
            std::cout << "TextureFormat : rgb888\n";
            b = data[i];
            g = data[i+1];
            r = data[i+2];
        } else if (pf == CL_TextureFormat::cl_rgba4)
        {
            std::cout << "TextureFormat : rgba4444\n";
            r = data[i] && 0x0F;
            g = (data[i] && 0xF0) >> 4;
            b = data[i+1] && 0x0F;
            a = (data[i+1] && 0xF0) >> 4;
        }

        if( a!=0 && (r!=g || r!=b || g!=b) ) 
        {
            rgbToHsv( r, g, b, &h, &s, &v );

            h += hue;
            s += saturation;
            v += value;
            if( h > 360 ) h -= 360;
            if( s > 255 ) s = 255;
            if( v > 255 ) v = 255;
            if( h < 0 ) h += 360;
            if( s < 0 ) s = 0;
            if( v < 0 ) v = 0;

            hsvToRgb( h, s, v, &r, &g, &b );

            if(pf == CL_TextureFormat::cl_rgba8)
            {
                data[i] = a;
                data[i+1] = b;
                data[i+2] = g;
                data[i+3] = r;
            } else if (pf == CL_TextureFormat::cl_rgb8)
            {
                data[i] = b;
                data[i+1] = g;
                data[i+2] = r;
            } else if (pf == CL_TextureFormat::cl_rgba4)
            {
                data[i] = r + (g << 4);
                data[i+1] = b + (a << 4);
            }
        }
    }
Пример #18
0
int main() {
	if (!initWindow(WINDOW_WIDTH, WINDOW_HEIGHT)) {
		std::cerr << "Failed to initialize window.\n";
		return 1;
	}

	int tex_width, tex_height;
	GLuint main_texture = loadTexture(&tex_width, &tex_height, "graphics.png");
	assert(main_texture != 0);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	GLuint shader_program = loadShaderProgram();
	glUseProgram(shader_program);

	GLuint u_view_matrix_location = glGetUniformLocation(shader_program, "u_view_matrix");
	GLfloat view_matrix[9] = {
		2.0f/WINDOW_WIDTH,  0.0f,              -1.0f,
		0.0f,              -2.0/WINDOW_HEIGHT,  1.0f,
		0.0f,               0.0f,               1.0f
	};
	glUniformMatrix3fv(u_view_matrix_location, 1, GL_TRUE, view_matrix);

	GLuint u_texture_location = glGetUniformLocation(shader_program, "u_texture");
	glUniform1i(u_texture_location, 0);

	CHECK_GL_ERROR;

	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, main_texture);

	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

	CHECK_GL_ERROR;

	SpriteBuffer sprite_buffer;
	sprite_buffer.tex_width = static_cast<float>(tex_width);
	sprite_buffer.tex_height = static_cast<float>(tex_height);

	GLuint vao_id;
	glGenVertexArrays(1, &vao_id);
	glBindVertexArray(vao_id);

	GLuint vbo_id;
	glGenBuffers(1, &vbo_id);
	glBindBuffer(GL_ARRAY_BUFFER, vbo_id);

	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexData), reinterpret_cast<void*>(offsetof(VertexData, pos_x)));
	glVertexAttribPointer(1, 2, GL_FLOAT, GL_TRUE,  sizeof(VertexData), reinterpret_cast<void*>(offsetof(VertexData, tex_s)));
	glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE,  sizeof(VertexData), reinterpret_cast<void*>(offsetof(VertexData, color)));
	for (int i = 0; i < 3; ++i)
		glEnableVertexAttribArray(i);

	GLuint ibo_id;
	glGenBuffers(1, &ibo_id);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_id);

	///////////////////////////
	// Initialize game state //
	///////////////////////////
	GameState game_state;
	RandomGenerator& rng = game_state.rng;
	rng.seed(123);

	{
		Paddle& p = game_state.paddle;
		p.pos_x = WINDOW_WIDTH / 2;
		p.pos_y = WINDOW_HEIGHT - 32;
		p.rotation = 0;
	}

	Sprite paddle_spr;
	paddle_spr.setImg(0, 0, 64, 16);

	Sprite gem_spr;
	gem_spr.setImg(0, 16, 16, 16);

	static const int GEM_SPAWN_INTERVAL = 60*5;
	int gem_spawn_timer = GEM_SPAWN_INTERVAL;

	CHECK_GL_ERROR;

	////////////////////
	// Main game loop //
	////////////////////
	bool running = true;
	while (running) {
		/* Update paddle */
		{
			Paddle& paddle = game_state.paddle;

			fixed24_8 paddle_speed(0);
			fixed8_24 rotation = 0;
			if (glfwGetKey(GLFW_KEY_LEFT)) {
				paddle_speed -= PADDLE_MOVEMENT_SPEED;
				rotation -= PADDLE_ROTATION_RATE;
			}
			if (glfwGetKey(GLFW_KEY_RIGHT)) {
				paddle_speed += PADDLE_MOVEMENT_SPEED;
				rotation += PADDLE_ROTATION_RATE;
			}

			if (rotation == 0) {
				paddle.rotation = stepTowards(paddle.rotation, fixed8_24(0), PADDLE_ROTATION_RETURN_RATE);
			} else {
				paddle.rotation = clamp(-PADDLE_MAX_ROTATION, paddle.rotation + rotation, PADDLE_MAX_ROTATION);
			}
			paddle.pos_x += paddle_speed;
		}

		/* Spawn new gems */
		if (--gem_spawn_timer == 0) {
			gem_spawn_timer = GEM_SPAWN_INTERVAL;

			Gem b;
			b.pos_x = randRange(rng, WINDOW_WIDTH * 1 / 6, WINDOW_WIDTH * 5 / 6);
			b.pos_y = -10;
			b.vel_x = b.vel_y = 0;
			b.score_value = Gem::INITIAL_VALUE;

			game_state.gems.push_back(b);
		}

		/* Update balls */
		for (unsigned int i = 0; i < game_state.gems.size(); ++i) {
			Gem& ball = game_state.gems[i];

			ball.vel_y += fixed16_16(0, 1, 8);

			ball.pos_x += fixed24_8(ball.vel_x);
			ball.pos_y += fixed24_8(ball.vel_y);

			collideBallWithBoundary(ball);
			for (unsigned int j = i + 1; j < game_state.gems.size(); ++j) {
				collideBallWithBall(ball, game_state.gems[j]);
			}
			collideBallWithPaddle(ball, game_state.paddle);
		}

		/* Clean up dead gems */
		remove_if(game_state.gems, [](const Gem& gem) {
			return gem.pos_y > WINDOW_HEIGHT + 128 && gem.vel_y > 0;
		});

		/* Draw scene */
		sprite_buffer.clear();
		
		paddle_spr.setPos(game_state.paddle.pos_x.integer(), game_state.paddle.pos_y.integer());
		sprite_buffer.append(paddle_spr, game_state.paddle.getSpriteMatrix());

		for (const Gem& gem : game_state.gems) {
			gem_spr.setPos(gem.pos_x.integer() - gem_spr.img_w / 2, gem.pos_y.integer() - gem_spr.img_h / 2);
			float r, g, b;
			hsvToRgb(mapScoreToHue(gem.score_value), 1.0f, 1.0f, &r, &g, &b);
			gem_spr.color = makeColor(uint8_t(r*255 + 0.5f), uint8_t(g*255 + 0.5f), uint8_t(b*255 + 0.5f), 255);
			sprite_buffer.append(gem_spr);
		}

		// HUD
		{
			static const int HUD_X_POS = 1;
			static const int HUD_Y_POS = 1;

			Sprite hud_spr;
			hud_spr.setImg(64, 0, 29, 11);
			hud_spr.setPos(HUD_X_POS, HUD_Y_POS);
			sprite_buffer.append(hud_spr);

			hud_spr.setImg(64, 12, 29, 11);
			hud_spr.setPos(HUD_X_POS, HUD_Y_POS + 13);
			sprite_buffer.append(hud_spr);

			std::string score_text = std::to_string(game_state.score);
			FontInfo font('0', 40, 24, 8, 12);
			drawText(HUD_X_POS + 31, HUD_Y_POS, score_text, sprite_buffer, font);
		}

		/* Submit sprites */
		// More superfluous drawcalls to change the GPU into high-performance mode? Sure, why not.
		glClear(GL_COLOR_BUFFER_BIT);
		for (int i = 0; i < 1000; ++i) {
			sprite_buffer.upload();
			sprite_buffer.draw();
		}

		for (const Sprite& spr : debug_sprites) {
			sprite_buffer.append(spr);
		}
		debug_sprites.clear();

		glClear(GL_COLOR_BUFFER_BIT);
		sprite_buffer.upload();
		sprite_buffer.draw();

		glfwSwapBuffers();
		running = running && glfwGetWindowParam(GLFW_OPENED);

		CHECK_GL_ERROR;
	}

	glfwCloseWindow();
	glfwTerminate();
}