4 Commits

Author SHA1 Message Date
Ray
5e8118daf2 Update shaders_game_of_life.c 2025-12-10 09:46:06 +01:00
Ray
bc2057345b REVIEWED: GetRandomValue(), explained the new approach to get more uniform random values range 2025-12-10 09:30:18 +01:00
f2a900a60d [rcore] Fix modulo bias in GetRandomValue() (#5392)
* Fix modulo bias in GetRandomValue(); implement rejection sampling for uniformity

* Replace do-while with for-loop in GetRandomValue rejection sampling
2025-12-10 09:23:40 +01:00
Ray
3adfde42f7 REVIEWED: rlLoadTeexture(), max mipmap levels to use #5400 2025-12-10 09:21:33 +01:00
3 changed files with 64 additions and 43 deletions

View File

@ -60,6 +60,8 @@ int main(void)
//-------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------
const int screenWidth = 800; const int screenWidth = 800;
const int screenHeight = 450; const int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - game of life");
const int menuWidth = 100; const int menuWidth = 100;
const int windowWidth = screenWidth - menuWidth; const int windowWidth = screenWidth - menuWidth;
@ -80,10 +82,9 @@ int main(void)
{ "Puffer train", "puffer_train", { 0.1f, 0.5f } }, { "Glider Gun", "glider_gun", { 0.2f, 0.2f } }, { "Breeder", "breeder", { 0.1f, 0.5f } }, { "Puffer train", "puffer_train", { 0.1f, 0.5f } }, { "Glider Gun", "glider_gun", { 0.2f, 0.2f } }, { "Breeder", "breeder", { 0.1f, 0.5f } },
{ "Random", "", { 0.5f, 0.5f } } { "Random", "", { 0.5f, 0.5f } }
}; };
const int numberOfPresets = sizeof(presetPatterns) / sizeof(presetPatterns[0]);
const int numberOfPresets = sizeof(presetPatterns)/sizeof(presetPatterns[0]);
// Variable declaration
//--------------------------------------------------------------------------------------
int zoom = 1; int zoom = 1;
float offsetX = (worldWidth - windowWidth)/2.0f; // Centered on window float offsetX = (worldWidth - windowWidth)/2.0f; // Centered on window
float offsetY = (worldHeight - windowHeight)/2.0f; // Centered on window float offsetY = (worldHeight - windowHeight)/2.0f; // Centered on window
@ -97,8 +98,6 @@ int main(void)
bool buttonFaster = false; bool buttonFaster = false;
bool buttonSlower = false; bool buttonSlower = false;
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - game of life");
// Load shader // Load shader
Shader shdrGameOfLife = LoadShader(0, TextFormat("resources/shaders/glsl%i/game_of_life.fs", GLSL_VERSION)); Shader shdrGameOfLife = LoadShader(0, TextFormat("resources/shaders/glsl%i/game_of_life.fs", GLSL_VERSION));
@ -115,7 +114,7 @@ int main(void)
EndTextureMode(); EndTextureMode();
Image startPattern = LoadImage("resources/game_of_life/r_pentomino.png"); Image startPattern = LoadImage("resources/game_of_life/r_pentomino.png");
UpdateTextureRec(world2.texture, (Rectangle) { worldWidth / 2.0f, worldHeight / 2.0f, (float)(startPattern.width), (float)(startPattern.height) }, startPattern.data); UpdateTextureRec(world2.texture, (Rectangle){ worldWidth/2.0f, worldHeight/2.0f, (float)(startPattern.width), (float)(startPattern.height) }, startPattern.data);
UnloadImage(startPattern); UnloadImage(startPattern);
// Pointers to the two textures, to be swapped // Pointers to the two textures, to be swapped
@ -143,10 +142,8 @@ int main(void)
const float centerX = offsetX + (windowWidth/2.0f)/zoom; const float centerX = offsetX + (windowWidth/2.0f)/zoom;
const float centerY = offsetY + (windowHeight/2.0f)/zoom; const float centerY = offsetY + (windowHeight/2.0f)/zoom;
if (buttonZoomIn || (mouseWheelMove > 0.0f)) if (buttonZoomIn || (mouseWheelMove > 0.0f)) zoom *= 2;
zoom *= 2; if ((buttonZomOut || (mouseWheelMove < 0.0f)) && (zoom > 1)) zoom /= 2;
if ((buttonZomOut || (mouseWheelMove < 0.0f)) && (zoom > 1))
zoom /= 2;
offsetX = centerX - (windowWidth/2.0f)/zoom; offsetX = centerX - (windowWidth/2.0f)/zoom;
offsetY = centerY - (windowHeight/2.0f)/zoom; offsetY = centerY - (windowHeight/2.0f)/zoom;
} }
@ -156,7 +153,6 @@ int main(void)
if (buttonSlower) framesPerStep++; if (buttonSlower) framesPerStep++;
// Mouse management // Mouse management
//----------------------------------------------------------------------------------
if ((mode == MODE_RUN) || (mode == MODE_PAUSE)) if ((mode == MODE_RUN) || (mode == MODE_PAUSE))
{ {
FreeImageToDraw(&imageToDraw); // Free the image to draw: no longer needed in these modes FreeImageToDraw(&imageToDraw); // Free the image to draw: no longer needed in these modes
@ -177,10 +173,8 @@ int main(void)
const float offsetDecimalY = offsetY - floorf(offsetY); const float offsetDecimalY = offsetY - floorf(offsetY);
int sizeInWorldX = (int)(ceilf((float)(windowWidth + offsetDecimalX*zoom)/zoom)); int sizeInWorldX = (int)(ceilf((float)(windowWidth + offsetDecimalX*zoom)/zoom));
int sizeInWorldY = (int)(ceilf((float)(windowHeight + offsetDecimalY*zoom)/zoom)); int sizeInWorldY = (int)(ceilf((float)(windowHeight + offsetDecimalY*zoom)/zoom));
if (offsetX + sizeInWorldX >= worldWidth) if (offsetX + sizeInWorldX >= worldWidth) sizeInWorldX = worldWidth - (int)floorf(offsetX);
sizeInWorldX = worldWidth - (int)floorf(offsetX); if (offsetY + sizeInWorldY >= worldHeight) sizeInWorldY = worldHeight - (int)floorf(offsetY);
if (offsetY + sizeInWorldY >= worldHeight)
sizeInWorldY = worldHeight - (int)floorf(offsetY);
// Create image to draw if not created yet // Create image to draw if not created yet
if (imageToDraw == NULL) if (imageToDraw == NULL)
@ -192,6 +186,7 @@ int main(void)
EndTextureMode(); EndTextureMode();
imageToDraw = (Image*)RL_MALLOC(sizeof(Image)); imageToDraw = (Image*)RL_MALLOC(sizeof(Image));
*imageToDraw = LoadImageFromTexture(worldOnScreen.texture); *imageToDraw = LoadImageFromTexture(worldOnScreen.texture);
UnloadRenderTexture(worldOnScreen); UnloadRenderTexture(worldOnScreen);
} }
@ -201,32 +196,30 @@ int main(void)
{ {
int mouseX = (int)(mousePosition.x + offsetDecimalX*zoom)/zoom; int mouseX = (int)(mousePosition.x + offsetDecimalX*zoom)/zoom;
int mouseY = (int)(mousePosition.y + offsetDecimalY*zoom)/zoom; int mouseY = (int)(mousePosition.y + offsetDecimalY*zoom)/zoom;
if (mouseX >= sizeInWorldX) if (mouseX >= sizeInWorldX) mouseX = sizeInWorldX - 1;
mouseX = sizeInWorldX - 1; if (mouseY >= sizeInWorldY) mouseY = sizeInWorldY - 1;
if (mouseY >= sizeInWorldY) if (firstColor == -1) firstColor = (GetImageColor(*imageToDraw, mouseX, mouseY).r < 5)? 0 : 1;
mouseY = sizeInWorldY - 1;
if (firstColor == -1)
firstColor = (GetImageColor(*imageToDraw, mouseX, mouseY).r < 5)? 0 : 1;
const int prevColor = (GetImageColor(*imageToDraw, mouseX, mouseY).r < 5)? 0 : 1; const int prevColor = (GetImageColor(*imageToDraw, mouseX, mouseY).r < 5)? 0 : 1;
ImageDrawPixel(imageToDraw, mouseX, mouseY, (firstColor) ? BLACK : RAYWHITE); ImageDrawPixel(imageToDraw, mouseX, mouseY, (firstColor) ? BLACK : RAYWHITE);
if (prevColor != firstColor)
UpdateTextureRec(currentWorld->texture, (Rectangle){ floorf(offsetX), floorf(offsetY), (float)(sizeInWorldX), (float)(sizeInWorldY) }, imageToDraw->data); if (prevColor != firstColor) UpdateTextureRec(currentWorld->texture, (Rectangle){ floorf(offsetX), floorf(offsetY), (float)(sizeInWorldX), (float)(sizeInWorldY) }, imageToDraw->data);
} }
else else firstColor = -1;
firstColor = -1;
} }
// Load selected preset // Load selected preset
//----------------------------------------------------------------------------------
if (preset >= 0) if (preset >= 0)
{ {
Image pattern; Image pattern;
if (preset < numberOfPresets - 1) // Preset with pattern image lo load if (preset < numberOfPresets - 1) // Preset with pattern image lo load
{ {
pattern = LoadImage(TextFormat("resources/game_of_life/%s.png", presetPatterns[preset].fileName)); pattern = LoadImage(TextFormat("resources/game_of_life/%s.png", presetPatterns[preset].fileName));
BeginTextureMode(*currentWorld); BeginTextureMode(*currentWorld);
ClearBackground(RAYWHITE); ClearBackground(RAYWHITE);
EndTextureMode(); EndTextureMode();
UpdateTextureRec(currentWorld->texture, (Rectangle){ worldWidth*presetPatterns[preset].position.x - pattern.width/2.0f, UpdateTextureRec(currentWorld->texture, (Rectangle){ worldWidth*presetPatterns[preset].position.x - pattern.width/2.0f,
worldHeight*presetPatterns[preset].position.y - pattern.height/2.0f, worldHeight*presetPatterns[preset].position.y - pattern.height/2.0f,
(float)(pattern.width), (float)(pattern.height) }, pattern.data); (float)(pattern.width), (float)(pattern.height) }, pattern.data);
@ -240,9 +233,12 @@ int main(void)
{ {
ImageClearBackground(&pattern, RAYWHITE); ImageClearBackground(&pattern, RAYWHITE);
for (int x = 0; x < pattern.width; x++) for (int x = 0; x < pattern.width; x++)
{
for (int y = 0; y < pattern.height; y++) for (int y = 0; y < pattern.height; y++)
if (GetRandomValue(0, 100) < 15) {
ImageDrawPixel(&pattern, x, y, BLACK); if (GetRandomValue(0, 100) < 15) ImageDrawPixel(&pattern, x, y, BLACK);
}
}
UpdateTextureRec(currentWorld->texture, UpdateTextureRec(currentWorld->texture,
(Rectangle){ (float)(pattern.width*i), (float)(pattern.height*j), (Rectangle){ (float)(pattern.width*i), (float)(pattern.height*j),
(float)(pattern.width), (float)(pattern.height) }, pattern.data); (float)(pattern.width), (float)(pattern.height) }, pattern.data);
@ -251,26 +247,25 @@ int main(void)
} }
UnloadImage(pattern); UnloadImage(pattern);
mode = MODE_PAUSE; mode = MODE_PAUSE;
offsetX = worldWidth * presetPatterns[preset].position.x - windowWidth/zoom/2.0f; offsetX = worldWidth*presetPatterns[preset].position.x - windowWidth/zoom/2.0f;
offsetY = worldHeight * presetPatterns[preset].position.y - windowHeight/zoom/2.0f; offsetY = worldHeight*presetPatterns[preset].position.y - windowHeight/zoom/2.0f;
} }
// Check window draw inside world limits // Check window draw inside world limits
if (offsetX < 0) offsetX = 0; if (offsetX < 0) offsetX = 0;
if (offsetY < 0) offsetY = 0; if (offsetY < 0) offsetY = 0;
if (offsetX > worldWidth - (float)(windowWidth)/zoom) if (offsetX > worldWidth - (float)(windowWidth)/zoom) offsetX = worldWidth - (float)(windowWidth)/zoom;
offsetX = worldWidth - (float)(windowWidth)/zoom; if (offsetY > worldHeight - (float)(windowHeight)/zoom) offsetY = worldHeight - (float)(windowHeight)/zoom;
if (offsetY > worldHeight - (float)(windowHeight)/zoom)
offsetY = worldHeight - (float)(windowHeight)/zoom;
// Rectangles for drawing texture portion to screen // Rectangles for drawing texture portion to screen
//----------------------------------------------------------------------------------
const Rectangle textureSourceToScreen = { offsetX, offsetY, (float)windowWidth/zoom, (float)windowHeight/zoom }; const Rectangle textureSourceToScreen = { offsetX, offsetY, (float)windowWidth/zoom, (float)windowHeight/zoom };
//----------------------------------------------------------------------------------
// Draw to texture // Draw to texture
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
if ((mode == MODE_RUN) && ((frame % framesPerStep) == 0)) if ((mode == MODE_RUN) && ((frame%framesPerStep) == 0))
{ {
// Swap worlds // Swap worlds
RenderTexture2D *tempWorld = currentWorld; RenderTexture2D *tempWorld = currentWorld;
@ -284,10 +279,12 @@ int main(void)
EndShaderMode(); EndShaderMode();
EndTextureMode(); EndTextureMode();
} }
//----------------------------------------------------------------------------------
// Draw to screen // Draw to screen
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
BeginDrawing(); BeginDrawing();
DrawTexturePro(currentWorld->texture, textureSourceToScreen, textureOnScreen, (Vector2){ 0, 0 }, 0.0f, WHITE); DrawTexturePro(currentWorld->texture, textureSourceToScreen, textureOnScreen, (Vector2){ 0, 0 }, 0.0f, WHITE);
DrawLine(windowWidth, 0, windowWidth, screenHeight, (Color){ 218, 218, 218, 255 }); DrawLine(windowWidth, 0, windowWidth, screenHeight, (Color){ 218, 218, 218, 255 });
@ -301,8 +298,7 @@ int main(void)
DrawText("Presets", 710, 58, 8, GRAY); DrawText("Presets", 710, 58, 8, GRAY);
preset = -1; preset = -1;
for (int i = 0; i < numberOfPresets; i++) for (int i = 0; i < numberOfPresets; i++)
if (GuiButton((Rectangle){ 710.0f, 70.0f + 18*i, 80.0f, 16.0f }, presetPatterns[i].name)) if (GuiButton((Rectangle){ 710.0f, 70.0f + 18*i, 80.0f, 16.0f }, presetPatterns[i].name)) preset = i;
preset = i;
GuiToggleGroup((Rectangle){ 710, 258, 80, 16 }, "Run\nPause\nDraw", &mode); GuiToggleGroup((Rectangle){ 710, 258, 80, 16 }, "Run\nPause\nDraw", &mode);
@ -314,8 +310,6 @@ int main(void)
buttonFaster = GuiButton((Rectangle){ 710, 382, 80, 16 }, "Faster"); buttonFaster = GuiButton((Rectangle){ 710, 382, 80, 16 }, "Faster");
buttonSlower = GuiButton((Rectangle){ 710, 400, 80, 16 }, "Slower"); buttonSlower = GuiButton((Rectangle){ 710, 400, 80, 16 }, "Slower");
//------------------------------------------------------------------------------
DrawFPS(712, 426); DrawFPS(712, 426);
EndDrawing(); EndDrawing();

View File

@ -1742,8 +1742,33 @@ int GetRandomValue(int min, int max)
{ {
TRACELOG(LOG_WARNING, "Invalid GetRandomValue() arguments, range should not be higher than %i", RAND_MAX); TRACELOG(LOG_WARNING, "Invalid GetRandomValue() arguments, range should not be higher than %i", RAND_MAX);
} }
// NOTE: This one-line approach produces a non-uniform distribution,
// as stated by Donald Knuth in the book The Art of Programming, so
// using below approach for more uniform results
//value = (rand()%(abs(max - min) + 1) + min);
value = (rand()%(abs(max - min) + 1) + min); // More uniform range solution
int range = (max - min) + 1;
// Degenerate/overflow case: fall back to min (same behavior as "always min" instead of UB)
if (range <= 0) value = min;
else
{
// Rejection sampling to get a uniform integer in [min, max]
unsigned long c = (unsigned long)RAND_MAX + 1UL; // number of possible rand() results
unsigned long m = (unsigned long)range; // size of the target interval
unsigned long t = c - (c%m); // largest multiple of m <= c
unsigned long r = 0;
for (;;)
{
r = (unsigned long)rand();
if (r < t) break; // Only accept values within the fair region
}
value = min + (int)(r%m);
}
#endif #endif
return value; return value;
} }

View File

@ -3384,10 +3384,12 @@ unsigned int rlLoadTexture(const void *data, int width, int height, int format,
#if defined(GRAPHICS_API_OPENGL_33) #if defined(GRAPHICS_API_OPENGL_33)
if (mipmapCount > 1) if (mipmapCount > 1)
{ {
// Activate Trilinear filtering if mipmaps are available // Activate trilinear filtering if mipmaps are available
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipmapCount); // Required for user-defined mip count
// Define thee maximum number of mipmap levels to be used, 0 is default texture size
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, mipmapCount - 1);
} }
#endif #endif