313 lines
11 KiB
C#
313 lines
11 KiB
C#
/*******************************************************************************************
|
|
*
|
|
* raylib [shapes] example - hilbert curve
|
|
*
|
|
* Example complexity rating: [★★★☆] 3/4
|
|
*
|
|
* Example originally created with raylib 5.6, last time updated with raylib 5.6
|
|
*
|
|
* Example contributed by Hamza RAHAL (@hmz-rhl) and reviewed by Ramon Santamaria (@raysan5)
|
|
*
|
|
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
|
|
* BSD-like license that allows static linking with closed source software
|
|
*
|
|
* Copyright (c) 2025 Hamza RAHAL (@hmz-rhl)
|
|
*
|
|
********************************************************************************************/
|
|
|
|
namespace Examples.Shapes;
|
|
|
|
public partial class HilbertCurve : IExample
|
|
{
|
|
private const int screenWidth = 800;
|
|
private const int screenHeight = 450;
|
|
|
|
public string Name => "Shapes / Hilbert Curve";
|
|
|
|
public string Title => "raylib [shapes] example - hilbert curve";
|
|
|
|
private int order;
|
|
private float size;
|
|
private int strokeCount;
|
|
private Vector2[] hilbertPath;
|
|
|
|
private int prevOrder;
|
|
private int prevSize; // NOTE: Size from slider is float but for comparison we use int
|
|
private int counter;
|
|
private float thick;
|
|
private bool animate;
|
|
|
|
public void Init()
|
|
{
|
|
order = 2;
|
|
size = (float)GetScreenHeight();
|
|
strokeCount = 0;
|
|
hilbertPath = LoadHilbertPath(order, size, out strokeCount);
|
|
|
|
prevOrder = order;
|
|
prevSize = (int)size;
|
|
counter = 0;
|
|
thick = 2.0f;
|
|
animate = true;
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
// Update
|
|
//----------------------------------------------------------------------------------
|
|
// Check if order or size have changed to regenerate
|
|
// NOTE: Size from slider is float but for comparison we use int
|
|
if ((prevOrder != order) || (prevSize != (int)size))
|
|
{
|
|
hilbertPath = LoadHilbertPath(order, size, out strokeCount);
|
|
|
|
if (animate)
|
|
{
|
|
counter = 0;
|
|
}
|
|
else
|
|
{
|
|
counter = strokeCount;
|
|
}
|
|
|
|
prevOrder = order;
|
|
prevSize = (int)size;
|
|
}
|
|
//----------------------------------------------------------------------------------
|
|
|
|
// Draw
|
|
//--------------------------------------------------------------------------
|
|
BeginDrawing();
|
|
|
|
ClearBackground(Color.RayWhite);
|
|
|
|
if (counter < strokeCount)
|
|
{
|
|
// Draw Hilbert path animation, one stroke every frame
|
|
for (int i = 1; i <= counter; i++)
|
|
{
|
|
DrawLineEx(hilbertPath[i], hilbertPath[i - 1], thick, ColorFromHSV(((float)i / strokeCount) * 360.0f, 1.0f, 1.0f));
|
|
}
|
|
|
|
counter += 1;
|
|
}
|
|
else
|
|
{
|
|
// Draw full Hilbert path
|
|
for (int i = 1; i < strokeCount; i++)
|
|
{
|
|
DrawLineEx(hilbertPath[i], hilbertPath[i - 1], thick, ColorFromHSV(((float)i / strokeCount) * 360.0f, 1.0f, 1.0f));
|
|
}
|
|
}
|
|
|
|
// Draw UI (minimal raygui-like controls, raygui is not bound in raylib-cs)
|
|
GuiCheckBox(new Rectangle(450, 50, 20, 20), "ANIMATE GENERATION ON CHANGE", ref animate);
|
|
GuiSpinner(new Rectangle(585, 100, 180, 30), "HILBERT CURVE ORDER: ", ref order, 2, 8);
|
|
GuiSlider(new Rectangle(524, 150, 240, 24), "THICKNESS: ", null, ref thick, 1.0f, 10.0f);
|
|
GuiSlider(new Rectangle(524, 190, 240, 24), "TOTAL SIZE: ", null, ref size, 10.0f, GetScreenHeight() * 1.5f);
|
|
|
|
EndDrawing();
|
|
//--------------------------------------------------------------------------
|
|
}
|
|
|
|
public void Unload()
|
|
{
|
|
}
|
|
|
|
//------------------------------------------------------------------------------------
|
|
// Module Functions Definition
|
|
//------------------------------------------------------------------------------------
|
|
// Load the whole Hilbert Path (including each U and their link)
|
|
private static Vector2[] LoadHilbertPath(int order, float size, out int strokeCount)
|
|
{
|
|
int N = 1 << order;
|
|
float len = size / N;
|
|
strokeCount = N * N;
|
|
|
|
Vector2[] hilbertPath = new Vector2[strokeCount];
|
|
|
|
for (int i = 0; i < strokeCount; i++)
|
|
{
|
|
hilbertPath[i] = ComputeHilbertStep(order, i);
|
|
hilbertPath[i].X = hilbertPath[i].X * len + len / 2.0f;
|
|
hilbertPath[i].Y = hilbertPath[i].Y * len + len / 2.0f;
|
|
}
|
|
|
|
return hilbertPath;
|
|
}
|
|
|
|
// Compute Hilbert path U positions
|
|
private static Vector2 ComputeHilbertStep(int order, int index)
|
|
{
|
|
// Hilbert points base pattern
|
|
Vector2[] hilbertPoints = new Vector2[4]
|
|
{
|
|
new Vector2(0, 0),
|
|
new Vector2(0, 1),
|
|
new Vector2(1, 1),
|
|
new Vector2(1, 0),
|
|
};
|
|
|
|
int hilbertIndex = index & 3;
|
|
Vector2 vect = hilbertPoints[hilbertIndex];
|
|
float temp = 0.0f;
|
|
int len = 0;
|
|
|
|
for (int j = 1; j < order; j++)
|
|
{
|
|
index = index >> 2;
|
|
hilbertIndex = index & 3;
|
|
len = 1 << j;
|
|
|
|
switch (hilbertIndex)
|
|
{
|
|
case 0:
|
|
{
|
|
temp = vect.X;
|
|
vect.X = vect.Y;
|
|
vect.Y = temp;
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
vect.X += len;
|
|
vect.Y += len;
|
|
}
|
|
break;
|
|
case 1:
|
|
vect.Y += len;
|
|
break;
|
|
case 3:
|
|
{
|
|
temp = len - 1 - vect.X;
|
|
vect.X = 2 * len - 1 - vect.Y;
|
|
vect.Y = temp;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return vect;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------------
|
|
// Minimal raygui-like widgets (plain raylib re-implementation)
|
|
//----------------------------------------------------------------------------------
|
|
private static void GuiCheckBox(Rectangle bounds, string text, ref bool active)
|
|
{
|
|
Vector2 mouse = GetMousePosition();
|
|
bool hover = CheckCollisionPointRec(mouse, bounds);
|
|
if (hover && IsMouseButtonPressed(MouseButton.Left))
|
|
{
|
|
active = !active;
|
|
}
|
|
|
|
DrawRectangleLinesEx(bounds, 1, hover ? Color.DarkBlue : Color.Gray);
|
|
if (active)
|
|
{
|
|
DrawRectangle((int)bounds.X + 4, (int)bounds.Y + 4, (int)bounds.Width - 8, (int)bounds.Height - 8, Color.DarkGray);
|
|
}
|
|
|
|
if (text != null)
|
|
{
|
|
DrawText(text, (int)(bounds.X + bounds.Width + 8), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
|
}
|
|
}
|
|
|
|
private static void GuiSpinner(Rectangle bounds, string text, ref int value, int minValue, int maxValue)
|
|
{
|
|
Vector2 mouse = GetMousePosition();
|
|
Rectangle left = new Rectangle(bounds.X, bounds.Y, bounds.Height, bounds.Height);
|
|
Rectangle right = new Rectangle(bounds.X + bounds.Width - bounds.Height, bounds.Y, bounds.Height, bounds.Height);
|
|
Rectangle mid = new Rectangle(bounds.X + bounds.Height, bounds.Y, bounds.Width - 2 * bounds.Height, bounds.Height);
|
|
|
|
if (CheckCollisionPointRec(mouse, left) && IsMouseButtonPressed(MouseButton.Left) && value > minValue)
|
|
{
|
|
value--;
|
|
}
|
|
|
|
if (CheckCollisionPointRec(mouse, right) && IsMouseButtonPressed(MouseButton.Left) && value < maxValue)
|
|
{
|
|
value++;
|
|
}
|
|
|
|
DrawRectangleRec(mid, Color.RayWhite);
|
|
DrawRectangleLinesEx(mid, 1, Color.Gray);
|
|
DrawRectangleRec(left, Color.LightGray);
|
|
DrawRectangleLinesEx(left, 1, Color.Gray);
|
|
DrawRectangleRec(right, Color.LightGray);
|
|
DrawRectangleLinesEx(right, 1, Color.Gray);
|
|
DrawText("-", (int)(left.X + left.Width / 2 - 2), (int)(left.Y + left.Height / 2 - 5), 10, Color.DarkGray);
|
|
DrawText("+", (int)(right.X + right.Width / 2 - 3), (int)(right.Y + right.Height / 2 - 5), 10, Color.DarkGray);
|
|
string vs = value.ToString();
|
|
DrawText(vs, (int)(mid.X + mid.Width / 2 - MeasureText(vs, 10) / 2), (int)(mid.Y + mid.Height / 2 - 5), 10, Color.DarkGray);
|
|
if (text != null)
|
|
{
|
|
DrawText(text, (int)bounds.X - MeasureText(text, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
|
}
|
|
}
|
|
|
|
private static void GuiSlider(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
|
|
{
|
|
Vector2 mouse = GetMousePosition();
|
|
bool hover = CheckCollisionPointRec(mouse, bounds);
|
|
if (hover && IsMouseButtonDown(MouseButton.Left))
|
|
{
|
|
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
|
|
if (value < minValue)
|
|
{
|
|
value = minValue;
|
|
}
|
|
|
|
if (value > maxValue)
|
|
{
|
|
value = maxValue;
|
|
}
|
|
}
|
|
|
|
DrawRectangleRec(bounds, Color.LightGray);
|
|
float pct = (value - minValue) / (maxValue - minValue);
|
|
float handleX = bounds.X + pct * bounds.Width;
|
|
DrawRectangle((int)(handleX - 5), (int)bounds.Y, 10, (int)bounds.Height, Color.DarkGray);
|
|
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
|
if (textLeft != null)
|
|
{
|
|
DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
|
}
|
|
|
|
if (textRight != null)
|
|
{
|
|
DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
|
}
|
|
}
|
|
|
|
public static int Main()
|
|
{
|
|
// Initialization
|
|
//--------------------------------------------------------------------------------------
|
|
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - hilbert curve");
|
|
|
|
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
var game = new HilbertCurve();
|
|
game.Init();
|
|
|
|
// Main game loop
|
|
while (!WindowShouldClose()) // Detect window close button or ESC key
|
|
{
|
|
game.Update();
|
|
}
|
|
|
|
game.Unload();
|
|
|
|
// De-Initialization
|
|
//--------------------------------------------------------------------------------------
|
|
CloseWindow(); // Close window and OpenGL context
|
|
//--------------------------------------------------------------------------------------
|
|
|
|
return 0;
|
|
}
|
|
}
|