chore: clean recommit
This commit is contained in:
parent
8024c6ac40
commit
60ad2e7fb1
122 changed files with 23950 additions and 323 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -63,6 +63,7 @@ project.lock.json
|
|||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
dotnet-tools.json
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
|
|
|
|||
263
Examples/Audio/AmpEnvelope.cs
Normal file
263
Examples/Audio/AmpEnvelope.cs
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [audio] example - amp envelope
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Arbinda Rizki Muhammad (@arbipink) 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) 2026 Arbinda Rizki Muhammad (@arbipink)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Audio;
|
||||
|
||||
public unsafe partial class AmpEnvelope : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int BUFFER_SIZE = 4096;
|
||||
private const int SAMPLE_RATE = 44100;
|
||||
|
||||
// Wave state
|
||||
private enum ADSRState
|
||||
{
|
||||
Idle,
|
||||
Attack,
|
||||
Decay,
|
||||
Sustain,
|
||||
Release
|
||||
}
|
||||
|
||||
// Grouping all ADSR parameters and state into a struct
|
||||
private struct Envelope
|
||||
{
|
||||
public float AttackTime;
|
||||
public float DecayTime;
|
||||
public float SustainLevel;
|
||||
public float ReleaseTime;
|
||||
public float CurrentValue;
|
||||
public ADSRState State;
|
||||
}
|
||||
|
||||
public string Name => "Audio / Amp Envelope";
|
||||
|
||||
public string Title => "raylib [audio] example - amp envelope";
|
||||
|
||||
private float[] buffer;
|
||||
private AudioStream stream;
|
||||
private float audioTime;
|
||||
private Envelope env;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
InitAudioDevice();
|
||||
|
||||
// Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE
|
||||
SetAudioStreamBufferSizeDefault(BUFFER_SIZE);
|
||||
buffer = new float[BUFFER_SIZE];
|
||||
|
||||
// Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono)
|
||||
stream = LoadAudioStream(SAMPLE_RATE, 32, 1);
|
||||
|
||||
// Init Phase
|
||||
audioTime = 0.0f;
|
||||
|
||||
// Initialize the struct
|
||||
env = new Envelope
|
||||
{
|
||||
AttackTime = 1.0f,
|
||||
DecayTime = 1.0f,
|
||||
SustainLevel = 0.5f,
|
||||
ReleaseTime = 1.0f,
|
||||
CurrentValue = 0.0f,
|
||||
State = ADSRState.Idle
|
||||
};
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyPressed(KeyboardKey.Space)) env.State = ADSRState.Attack;
|
||||
|
||||
if (IsKeyReleased(KeyboardKey.Space) && (env.State != ADSRState.Idle)) env.State = ADSRState.Release;
|
||||
|
||||
if (IsAudioStreamProcessed(stream))
|
||||
{
|
||||
if ((env.State != ADSRState.Idle) || (env.CurrentValue > 0.0f))
|
||||
{
|
||||
for (int i = 0; i < BUFFER_SIZE; i++)
|
||||
{
|
||||
UpdateEnvelope(ref env);
|
||||
FillAudioBuffer(i, buffer, env.CurrentValue, ref audioTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clear buffer if silent to avoid looping noise
|
||||
for (int i = 0; i < BUFFER_SIZE; i++) buffer[i] = 0;
|
||||
audioTime = 0.0f;
|
||||
}
|
||||
|
||||
fixed (float* bufferPtr = buffer)
|
||||
{
|
||||
UpdateAudioStream(stream, bufferPtr, BUFFER_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsAudioStreamPlaying(stream)) PlayAudioStream(stream);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// NOTE: raygui is not bound in raylib-cs, so the sliders below are left as reference.
|
||||
// The envelope keeps its default parameters (Attack/Decay/Release = 1.0s, Sustain = 0.5).
|
||||
//GuiSliderBar(new Rectangle( 100, 60, 400, 30 ), "Attack (s)", TextFormat("%2.2fs", env.AttackTime), ref env.AttackTime, 0.1f, 3.0f);
|
||||
//GuiSliderBar(new Rectangle( 100, 100, 400, 30 ), "Decay (s)", TextFormat("%2.2fs", env.DecayTime), ref env.DecayTime, 0.1f, 3.0f);
|
||||
//GuiSliderBar(new Rectangle( 100, 140, 400, 30 ), "Sustain", TextFormat("%2.2f", env.SustainLevel), ref env.SustainLevel, 0.0f, 1.0f);
|
||||
//GuiSliderBar(new Rectangle( 100, 180, 400, 30 ), "Release (s)", TextFormat("%2.2fs", env.ReleaseTime), ref env.ReleaseTime, 0.1f, 3.0f);
|
||||
|
||||
DrawADSRGraph(ref env, new Rectangle(100, 250, 400, 100));
|
||||
|
||||
DrawCircleV(new Vector2(520, 350 - (env.CurrentValue * 100)), 5, Color.Maroon);
|
||||
DrawText($"Current Gain: {env.CurrentValue:F2}", 535, (int)(345 - (env.CurrentValue * 100)), 10, Color.Maroon);
|
||||
|
||||
DrawText("Press SPACE to PLAY the sound!", 200, 400, 20, Color.LightGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadAudioStream(stream);
|
||||
CloseAudioDevice();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//------------------------------------------------------------------------------------
|
||||
private static void FillAudioBuffer(int i, float[] buffer, float envelopeValue, ref float audioTime)
|
||||
{
|
||||
int frequency = 440;
|
||||
buffer[i] = envelopeValue * MathF.Sin(2.0f * MathF.PI * frequency * audioTime);
|
||||
audioTime += (1.0f / SAMPLE_RATE);
|
||||
}
|
||||
|
||||
private static void UpdateEnvelope(ref Envelope env)
|
||||
{
|
||||
// Calculate the time delta for ONE sample (1/44100)
|
||||
float sampleTime = 1.0f / SAMPLE_RATE;
|
||||
|
||||
switch (env.State)
|
||||
{
|
||||
case ADSRState.Attack:
|
||||
{
|
||||
env.CurrentValue += (1.0f / env.AttackTime) * sampleTime;
|
||||
if (env.CurrentValue >= 1.0f)
|
||||
{
|
||||
env.CurrentValue = 1.0f;
|
||||
env.State = ADSRState.Decay;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ADSRState.Decay:
|
||||
{
|
||||
env.CurrentValue -= ((1.0f - env.SustainLevel) / env.DecayTime) * sampleTime;
|
||||
if (env.CurrentValue <= env.SustainLevel)
|
||||
{
|
||||
env.CurrentValue = env.SustainLevel;
|
||||
env.State = ADSRState.Sustain;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ADSRState.Sustain:
|
||||
{
|
||||
env.CurrentValue = env.SustainLevel;
|
||||
}
|
||||
break;
|
||||
case ADSRState.Release:
|
||||
{
|
||||
env.CurrentValue -= (env.SustainLevel / env.ReleaseTime) * sampleTime;
|
||||
if (env.CurrentValue <= 0.001f) // Use a small threshold to avoid infinite tail
|
||||
{
|
||||
env.CurrentValue = 0.0f;
|
||||
env.State = ADSRState.Idle;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawADSRGraph(ref Envelope env, Rectangle bounds)
|
||||
{
|
||||
DrawRectangleRec(bounds, Fade(Color.LightGray, 0.3f));
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
|
||||
// Fixed visual width for sustain stage since it's an amplitude not a time value
|
||||
float sustainWidth = 1.0f;
|
||||
|
||||
// Total time to visualize (sum of A, D, R + a padding for Sustain)
|
||||
float totalTime = env.AttackTime + env.DecayTime + sustainWidth + env.ReleaseTime;
|
||||
|
||||
float scaleX = bounds.Width / totalTime;
|
||||
float scaleY = bounds.Height;
|
||||
|
||||
Vector2 start = new Vector2(bounds.X, bounds.Y + bounds.Height);
|
||||
Vector2 peak = new Vector2(start.X + (env.AttackTime * scaleX), bounds.Y);
|
||||
Vector2 sustain = new Vector2(peak.X + (env.DecayTime * scaleX), bounds.Y + (1.0f - env.SustainLevel) * scaleY);
|
||||
Vector2 rel = new Vector2(sustain.X + (sustainWidth * scaleX), sustain.Y);
|
||||
Vector2 end = new Vector2(rel.X + (env.ReleaseTime * scaleX), bounds.Y + bounds.Height);
|
||||
|
||||
DrawLineV(start, peak, Color.SkyBlue);
|
||||
DrawLineV(peak, sustain, Color.Blue);
|
||||
DrawLineV(sustain, rel, Color.DarkBlue);
|
||||
DrawLineV(rel, end, Color.Orange);
|
||||
|
||||
DrawText("ADSR Visualizer", (int)bounds.X, (int)bounds.Y - 20, 10, Color.DarkGray);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [audio] example - amp envelope");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new AmpEnvelope();
|
||||
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;
|
||||
}
|
||||
}
|
||||
159
Examples/Audio/MixedProcessor.cs
Normal file
159
Examples/Audio/MixedProcessor.cs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [audio] example - mixed processor
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 4.2, last time updated with raylib 4.2
|
||||
*
|
||||
* Example contributed by hkc (@hatkidchan) 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) 2023-2025 hkc (@hatkidchan)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Audio;
|
||||
|
||||
[ExcludeFromBrowser("AttachAudioMixedProcessor callback is unreliable on the wasm audio backend")]
|
||||
public unsafe partial class MixedProcessor : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Audio / Mixed Processor";
|
||||
|
||||
public string Title => "raylib [audio] example - mixed processor";
|
||||
|
||||
private static float exponent = 1.0f; // Audio exponentiation value
|
||||
private static readonly float[] averageVolume = new float[400]; // Average volume history
|
||||
|
||||
private Music music;
|
||||
private Sound sound;
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Audio processing function
|
||||
//------------------------------------------------------------------------------------
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static void ProcessAudio(void* buffer, uint frames)
|
||||
{
|
||||
float* samples = (float*)buffer; // Samples internally stored as <float>s
|
||||
float average = 0.0f; // Temporary average volume
|
||||
|
||||
for (uint frame = 0; frame < frames; frame++)
|
||||
{
|
||||
float* left = &samples[frame * 2 + 0];
|
||||
float* right = &samples[frame * 2 + 1];
|
||||
|
||||
*left = MathF.Pow(MathF.Abs(*left), exponent) * ((*left < 0.0f) ? -1.0f : 1.0f);
|
||||
*right = MathF.Pow(MathF.Abs(*right), exponent) * ((*right < 0.0f) ? -1.0f : 1.0f);
|
||||
|
||||
average += MathF.Abs(*left) / frames; // accumulating average volume
|
||||
average += MathF.Abs(*right) / frames;
|
||||
}
|
||||
|
||||
// Moving history to the left
|
||||
for (int i = 0; i < 399; i++) averageVolume[i] = averageVolume[i + 1];
|
||||
|
||||
averageVolume[399] = average; // Adding last average value
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
InitAudioDevice(); // Initialize audio device
|
||||
|
||||
exponent = 1.0f;
|
||||
Array.Clear(averageVolume, 0, averageVolume.Length);
|
||||
|
||||
AttachAudioMixedProcessor(&ProcessAudio);
|
||||
|
||||
music = LoadMusicStream("resources/audio/country.mp3");
|
||||
sound = LoadSound("resources/audio/coin.wav");
|
||||
|
||||
PlayMusicStream(music);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateMusicStream(music); // Update music buffer with new stream data
|
||||
|
||||
// Modify processing variables
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyPressed(KeyboardKey.Left)) exponent -= 0.05f;
|
||||
if (IsKeyPressed(KeyboardKey.Right)) exponent += 0.05f;
|
||||
|
||||
if (exponent <= 0.5f) exponent = 0.5f;
|
||||
if (exponent >= 3.0f) exponent = 3.0f;
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Space)) PlaySound(sound);
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
DrawText("MUSIC SHOULD BE PLAYING!", 255, 150, 20, Color.LightGray);
|
||||
|
||||
DrawText($"EXPONENT = {exponent:F2}", 215, 180, 20, Color.LightGray);
|
||||
|
||||
DrawRectangle(199, 199, 402, 34, Color.LightGray);
|
||||
for (int i = 0; i < 400; i++)
|
||||
{
|
||||
DrawLine(201 + i, 232 - (int)(averageVolume[i] * 32), 201 + i, 232, Color.Maroon);
|
||||
}
|
||||
DrawRectangleLines(199, 199, 402, 34, Color.Gray);
|
||||
|
||||
DrawText("PRESS SPACE TO PLAY OTHER SOUND", 200, 250, 20, Color.LightGray);
|
||||
DrawText("USE LEFT AND RIGHT ARROWS TO ALTER DISTORTION", 140, 280, 20, Color.LightGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadMusicStream(music); // Unload music stream buffers from RAM
|
||||
|
||||
DetachAudioMixedProcessor(&ProcessAudio); // Disconnect audio processor
|
||||
|
||||
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [audio] example - mixed processor");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new MixedProcessor();
|
||||
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;
|
||||
}
|
||||
}
|
||||
182
Examples/Audio/RawStream.cs
Normal file
182
Examples/Audio/RawStream.cs
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [audio] example - raw stream
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 1.6, last time updated with raylib 6.0
|
||||
*
|
||||
* Example created by Ramon Santamaria (@raysan5) and reviewed by James Hofmann (@triplefox)
|
||||
*
|
||||
* 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) 2015-2026 Ramon Santamaria (@raysan5) and James Hofmann (@triplefox)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Audio;
|
||||
|
||||
public unsafe partial class RawStream : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int BUFFER_SIZE = 4096;
|
||||
private const int SAMPLE_RATE = 44100;
|
||||
|
||||
public string Name => "Audio / Raw Stream";
|
||||
|
||||
public string Title => "raylib [audio] example - raw stream";
|
||||
|
||||
public int TargetFps => 30;
|
||||
|
||||
private float[] buffer;
|
||||
private AudioStream stream;
|
||||
private float pan;
|
||||
private int sineFrequency;
|
||||
private int newSineFrequency;
|
||||
private int sineIndex;
|
||||
private double sineStartTime;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
InitAudioDevice();
|
||||
|
||||
// Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE
|
||||
SetAudioStreamBufferSizeDefault(BUFFER_SIZE);
|
||||
buffer = new float[BUFFER_SIZE];
|
||||
|
||||
// Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono)
|
||||
stream = LoadAudioStream(SAMPLE_RATE, 32, 1);
|
||||
pan = 0.0f;
|
||||
SetAudioStreamPan(stream, pan);
|
||||
PlayAudioStream(stream);
|
||||
|
||||
sineFrequency = 440;
|
||||
newSineFrequency = 440;
|
||||
sineIndex = 0;
|
||||
sineStartTime = 0.0;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
if (IsKeyDown(KeyboardKey.Up))
|
||||
{
|
||||
newSineFrequency += 10;
|
||||
if (newSineFrequency > 12500) newSineFrequency = 12500;
|
||||
}
|
||||
|
||||
if (IsKeyDown(KeyboardKey.Down))
|
||||
{
|
||||
newSineFrequency -= 10;
|
||||
if (newSineFrequency < 20) newSineFrequency = 20;
|
||||
}
|
||||
|
||||
if (IsKeyDown(KeyboardKey.Left))
|
||||
{
|
||||
pan -= 0.01f;
|
||||
if (pan < -1.0f) pan = -1.0f;
|
||||
SetAudioStreamPan(stream, pan);
|
||||
}
|
||||
|
||||
if (IsKeyDown(KeyboardKey.Right))
|
||||
{
|
||||
pan += 0.01f;
|
||||
if (pan > 1.0f) pan = 1.0f;
|
||||
SetAudioStreamPan(stream, pan);
|
||||
}
|
||||
|
||||
if (IsAudioStreamProcessed(stream))
|
||||
{
|
||||
for (int i = 0; i < BUFFER_SIZE; i++)
|
||||
{
|
||||
int wl = SAMPLE_RATE / sineFrequency;
|
||||
buffer[i] = MathF.Sin(2 * MathF.PI * sineIndex / wl);
|
||||
sineIndex++;
|
||||
|
||||
if (sineIndex >= wl)
|
||||
{
|
||||
sineFrequency = newSineFrequency;
|
||||
sineIndex = 0;
|
||||
sineStartTime = GetTime();
|
||||
}
|
||||
}
|
||||
|
||||
fixed (float* bufferPtr = buffer)
|
||||
{
|
||||
UpdateAudioStream(stream, bufferPtr, BUFFER_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
DrawText($"sine frequency: {sineFrequency}", screenWidth - 220, 10, 20, Color.Red);
|
||||
DrawText($"pan: {pan:F2}", screenWidth - 220, 30, 20, Color.Red);
|
||||
DrawText("Up/down to change frequency", 10, 10, 20, Color.DarkGray);
|
||||
DrawText("Left/right to pan", 10, 30, 20, Color.DarkGray);
|
||||
|
||||
int windowStart = (int)((GetTime() - sineStartTime) * SAMPLE_RATE);
|
||||
int windowSize = (int)(0.1f * SAMPLE_RATE);
|
||||
int wavelength = SAMPLE_RATE / sineFrequency;
|
||||
|
||||
// Draw a sine wave with the same frequency as the one being sent to the audio stream
|
||||
for (int i = 0; i < screenWidth; i++)
|
||||
{
|
||||
int t0 = windowStart + i * windowSize / screenWidth;
|
||||
int t1 = windowStart + (i + 1) * windowSize / screenWidth;
|
||||
Vector2 startPos = new Vector2(i, 250 + 50 * MathF.Sin(2 * MathF.PI * t0 / wavelength));
|
||||
Vector2 endPos = new Vector2(i + 1, 250 + 50 * MathF.Sin(2 * MathF.PI * t1 / wavelength));
|
||||
DrawLineV(startPos, endPos, Color.Red);
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadAudioStream(stream); // Close raw audio stream and delete buffers from RAM
|
||||
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [audio] example - raw stream");
|
||||
|
||||
SetTargetFPS(30);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new RawStream();
|
||||
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;
|
||||
}
|
||||
}
|
||||
114
Examples/Audio/SoundMulti.cs
Normal file
114
Examples/Audio/SoundMulti.cs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [audio] example - sound multi
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.0, last time updated with raylib 5.0
|
||||
*
|
||||
* Example contributed by Jeffery Myers (@JeffM2501) 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) 2023-2025 Jeffery Myers (@JeffM2501)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Audio;
|
||||
|
||||
public partial class SoundMulti : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MAX_SOUNDS = 10;
|
||||
|
||||
public string Name => "Audio / Sound Multi";
|
||||
|
||||
public string Title => "raylib [audio] example - sound multi";
|
||||
|
||||
private Sound[] soundArray = new Sound[MAX_SOUNDS];
|
||||
private int currentSound;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
InitAudioDevice(); // Initialize audio device
|
||||
|
||||
// Load audio file into the first slot as the 'source' sound,
|
||||
// this sound owns the sample data
|
||||
soundArray[0] = LoadSound("resources/audio/sound.wav");
|
||||
|
||||
// Load an alias of the sound into slots 1-9. These do not own the sound data, but can be played
|
||||
for (int i = 1; i < MAX_SOUNDS; i++) soundArray[i] = LoadSoundAlias(soundArray[0]);
|
||||
|
||||
currentSound = 0; // Set the sound list to the start
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyPressed(KeyboardKey.Space))
|
||||
{
|
||||
PlaySound(soundArray[currentSound]); // Play the next open sound slot
|
||||
currentSound++; // Increment the sound slot
|
||||
|
||||
// If the sound slot is out of bounds, go back to 0
|
||||
if (currentSound >= MAX_SOUNDS) currentSound = 0;
|
||||
|
||||
// NOTE: Another approach would be to look at the list for the first sound
|
||||
// that is not playing and use that slot
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
DrawText("Press SPACE to PLAY a WAV sound!", 200, 180, 20, Color.LightGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
for (int i = 1; i < MAX_SOUNDS; i++) UnloadSoundAlias(soundArray[i]); // Unload sound aliases
|
||||
UnloadSound(soundArray[0]); // Unload source sound data
|
||||
|
||||
CloseAudioDevice(); // Close audio device
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [audio] example - sound multi");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new SoundMulti();
|
||||
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;
|
||||
}
|
||||
}
|
||||
154
Examples/Audio/SoundPositioning.cs
Normal file
154
Examples/Audio/SoundPositioning.cs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [audio] example - sound positioning
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.5
|
||||
*
|
||||
* Example contributed by Le Juez Victor (@Bigfoot71) 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 Le Juez Victor (@Bigfoot71)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Audio;
|
||||
|
||||
public partial class SoundPositioning : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Audio / Sound Positioning";
|
||||
|
||||
public string Title => "raylib [audio] example - sound positioning";
|
||||
|
||||
public bool CursorDisabled => true;
|
||||
|
||||
private Sound sound;
|
||||
private Camera3D camera;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
InitAudioDevice();
|
||||
|
||||
sound = LoadSound("resources/audio/coin.wav");
|
||||
|
||||
camera = new Camera3D
|
||||
{
|
||||
Position = new Vector3(0, 5, 5),
|
||||
Target = new Vector3(0, 0, 0),
|
||||
Up = new Vector3(0, 1, 0),
|
||||
FovY = 60,
|
||||
Projection = CameraProjection.Perspective
|
||||
};
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Free);
|
||||
|
||||
float th = (float)GetTime();
|
||||
|
||||
Vector3 spherePos = new Vector3(
|
||||
5.0f * MathF.Cos(th),
|
||||
0.0f,
|
||||
5.0f * MathF.Sin(th)
|
||||
);
|
||||
|
||||
SetSoundPosition(camera, sound, spherePos, 1.0f);
|
||||
|
||||
if (!IsSoundPlaying(sound)) PlaySound(sound);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
DrawGrid(10, 2);
|
||||
DrawSphere(spherePos, 0.5f, Color.Red);
|
||||
EndMode3D();
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadSound(sound);
|
||||
CloseAudioDevice(); // Close audio device
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//------------------------------------------------------------------------------------
|
||||
// Set sound 3d position
|
||||
private static void SetSoundPosition(Camera3D listener, Sound sound, Vector3 position, float maxDist)
|
||||
{
|
||||
// Calculate direction vector and distance between listener and sound source
|
||||
Vector3 direction = Vector3.Subtract(position, listener.Position);
|
||||
float distance = direction.Length();
|
||||
|
||||
// Apply logarithmic distance attenuation and clamp between 0-1
|
||||
float attenuation = 1.0f / (1.0f + (distance / maxDist));
|
||||
attenuation = Math.Clamp(attenuation, 0.0f, 1.0f);
|
||||
|
||||
// Calculate normalized vectors for spatial positioning
|
||||
Vector3 normalizedDirection = Vector3.Normalize(direction);
|
||||
Vector3 forward = Vector3.Normalize(Vector3.Subtract(listener.Target, listener.Position));
|
||||
Vector3 right = Vector3.Normalize(Vector3.Cross(listener.Up, forward));
|
||||
|
||||
// Reduce volume for sounds behind the listener
|
||||
float dotProduct = Vector3.Dot(forward, normalizedDirection);
|
||||
if (dotProduct < 0.0f) attenuation *= (1.0f + dotProduct * 0.5f);
|
||||
|
||||
// Set stereo panning based on sound position relative to listener
|
||||
float pan = 0.5f + 0.5f * Vector3.Dot(normalizedDirection, right);
|
||||
|
||||
// Apply final sound properties
|
||||
SetSoundVolume(sound, attenuation);
|
||||
SetSoundPan(sound, pan);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [audio] example - sound positioning");
|
||||
|
||||
DisableCursor();
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new SoundPositioning();
|
||||
game.Init();
|
||||
|
||||
// Main game loop
|
||||
while (!WindowShouldClose())
|
||||
{
|
||||
game.Update();
|
||||
}
|
||||
|
||||
game.Unload();
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
CloseWindow(); // Close window and OpenGL context
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
306
Examples/Audio/StreamCallback.cs
Normal file
306
Examples/Audio/StreamCallback.cs
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [audio] example - stream callback
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example created by Dan Hoang (@dan-hoang) and reviewed by Ramon Santamaria (@raysan5)
|
||||
*
|
||||
* NOTE: Example sends a wave to the audio device,
|
||||
* user gets the choice of four waves: sine, square, triangle, and sawtooth
|
||||
* A stream is set up to play to the audio device; stream is hooked to a callback that
|
||||
* generates a wave, that is determined by user choice
|
||||
*
|
||||
* 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) 2026 Dan Hoang (@dan-hoang)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Audio;
|
||||
|
||||
[ExcludeFromBrowser("SetAudioStreamCallback is unreliable on the wasm audio backend")]
|
||||
public unsafe partial class StreamCallback : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int BUFFER_SIZE = 4096;
|
||||
private const int SAMPLE_RATE = 44100;
|
||||
|
||||
// Wave type
|
||||
private enum WaveType
|
||||
{
|
||||
Sine,
|
||||
Square,
|
||||
Triangle,
|
||||
Sawtooth
|
||||
}
|
||||
|
||||
public string Name => "Audio / Stream Callback";
|
||||
|
||||
public string Title => "raylib [audio] example - stream callback";
|
||||
|
||||
public int TargetFps => 30;
|
||||
|
||||
private static int waveFrequency = 440;
|
||||
private static int newWaveFrequency = 440;
|
||||
private static int waveIndex = 0;
|
||||
|
||||
// Buffer to keep the last second of uploaded audio,
|
||||
// part of which will be drawn on the screen
|
||||
private static readonly float[] buffer = new float[SAMPLE_RATE];
|
||||
|
||||
private static readonly string[] waveTypesAsString = { "sine", "square", "triangle", "sawtooth" };
|
||||
|
||||
private AudioStream stream;
|
||||
private WaveType waveType;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
InitAudioDevice();
|
||||
|
||||
waveFrequency = 440;
|
||||
newWaveFrequency = 440;
|
||||
waveIndex = 0;
|
||||
Array.Clear(buffer, 0, buffer.Length);
|
||||
|
||||
// Set the number of samples the stream will keep in memory at a time to BUFFER_SIZE
|
||||
SetAudioStreamBufferSizeDefault(BUFFER_SIZE);
|
||||
|
||||
// Init raw audio stream (sample rate: 44100, sample size: 32bit-float, channels: 1-mono)
|
||||
stream = LoadAudioStream(SAMPLE_RATE, 32, 1);
|
||||
PlayAudioStream(stream);
|
||||
|
||||
// Configure it so that the callback for waveType is called whenever stream is out of samples
|
||||
waveType = WaveType.Sine;
|
||||
SetWaveCallback();
|
||||
}
|
||||
|
||||
// Attach the callback matching the current waveType to the stream
|
||||
private void SetWaveCallback()
|
||||
{
|
||||
switch (waveType)
|
||||
{
|
||||
case WaveType.Sine: SetAudioStreamCallback(stream, &SineCallback); break;
|
||||
case WaveType.Square: SetAudioStreamCallback(stream, &SquareCallback); break;
|
||||
case WaveType.Triangle: SetAudioStreamCallback(stream, &TriangleCallback); break;
|
||||
case WaveType.Sawtooth: SetAudioStreamCallback(stream, &SawtoothCallback); break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyDown(KeyboardKey.Up))
|
||||
{
|
||||
newWaveFrequency += 10;
|
||||
if (newWaveFrequency > 12500) newWaveFrequency = 12500;
|
||||
}
|
||||
|
||||
if (IsKeyDown(KeyboardKey.Down))
|
||||
{
|
||||
newWaveFrequency -= 10;
|
||||
if (newWaveFrequency < 20) newWaveFrequency = 20;
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Left))
|
||||
{
|
||||
if (waveType == WaveType.Sine) waveType = WaveType.Sawtooth;
|
||||
else if (waveType == WaveType.Square) waveType = WaveType.Sine;
|
||||
else if (waveType == WaveType.Triangle) waveType = WaveType.Square;
|
||||
else waveType = WaveType.Triangle;
|
||||
|
||||
SetWaveCallback();
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Right))
|
||||
{
|
||||
if (waveType == WaveType.Sine) waveType = WaveType.Square;
|
||||
else if (waveType == WaveType.Square) waveType = WaveType.Triangle;
|
||||
else if (waveType == WaveType.Triangle) waveType = WaveType.Sawtooth;
|
||||
else waveType = WaveType.Sine;
|
||||
|
||||
SetWaveCallback();
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
DrawText($"frequency: {newWaveFrequency}", screenWidth - 220, 10, 20, Color.Red);
|
||||
DrawText($"wave type: {waveTypesAsString[(int)waveType]}", screenWidth - 220, 30, 20, Color.Red);
|
||||
DrawText("Up/down to change frequency", 10, 10, 20, Color.DarkGray);
|
||||
DrawText("Left/right to change wave type", 10, 30, 20, Color.DarkGray);
|
||||
|
||||
// Draw the last 10 ms of uploaded audio
|
||||
for (int i = 0; i < screenWidth; i++)
|
||||
{
|
||||
Vector2 startPos = new Vector2(i, 250 - 50 * buffer[WaveSampleIndex(i)]);
|
||||
Vector2 endPos = new Vector2(i + 1, 250 - 50 * buffer[WaveSampleIndex(i + 1)]);
|
||||
DrawLineV(startPos, endPos, Color.Red);
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadAudioStream(stream); // Close raw audio stream and delete buffers from RAM
|
||||
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
|
||||
}
|
||||
|
||||
// Maps a screen column to a sample index in the last 10 ms of the buffer. The final column
|
||||
// maps one sample past the end of the buffer; upstream C reads out of bounds there, so we
|
||||
// clamp to the last valid sample (visually identical, but safe under C# bounds checking).
|
||||
private static int WaveSampleIndex(int column)
|
||||
{
|
||||
int index = SAMPLE_RATE - SAMPLE_RATE / 100 + column * SAMPLE_RATE / 100 / screenWidth;
|
||||
return index < SAMPLE_RATE ? index : SAMPLE_RATE - 1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//------------------------------------------------------------------------------------
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static void SineCallback(void* framesOut, uint frameCount)
|
||||
{
|
||||
int fc = (int)frameCount;
|
||||
int wavelength = SAMPLE_RATE / waveFrequency;
|
||||
float* frames = (float*)framesOut;
|
||||
|
||||
// Synthesize the sine wave
|
||||
for (int i = 0; i < fc; i++)
|
||||
{
|
||||
frames[i] = MathF.Sin(2 * MathF.PI * waveIndex / wavelength);
|
||||
|
||||
waveIndex++;
|
||||
|
||||
if (waveIndex >= wavelength)
|
||||
{
|
||||
waveFrequency = newWaveFrequency;
|
||||
waveIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Save the synthesized samples for later drawing
|
||||
for (int i = 0; i < SAMPLE_RATE - fc; i++) buffer[i] = buffer[i + fc];
|
||||
for (int i = 0; i < fc; i++) buffer[SAMPLE_RATE - fc + i] = frames[i];
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static void SquareCallback(void* framesOut, uint frameCount)
|
||||
{
|
||||
int fc = (int)frameCount;
|
||||
int wavelength = SAMPLE_RATE / waveFrequency;
|
||||
float* frames = (float*)framesOut;
|
||||
|
||||
// Synthesize the square wave
|
||||
for (int i = 0; i < fc; i++)
|
||||
{
|
||||
frames[i] = (waveIndex < wavelength / 2) ? 1 : -1;
|
||||
waveIndex++;
|
||||
|
||||
if (waveIndex >= wavelength)
|
||||
{
|
||||
waveFrequency = newWaveFrequency;
|
||||
waveIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Save the synthesized samples for later drawing
|
||||
for (int i = 0; i < SAMPLE_RATE - fc; i++) buffer[i] = buffer[i + fc];
|
||||
for (int i = 0; i < fc; i++) buffer[SAMPLE_RATE - fc + i] = frames[i];
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static void TriangleCallback(void* framesOut, uint frameCount)
|
||||
{
|
||||
int fc = (int)frameCount;
|
||||
int wavelength = SAMPLE_RATE / waveFrequency;
|
||||
float* frames = (float*)framesOut;
|
||||
|
||||
// Synthesize the triangle wave
|
||||
for (int i = 0; i < fc; i++)
|
||||
{
|
||||
frames[i] = (waveIndex < wavelength / 2) ? (-1 + 2.0f * waveIndex / (wavelength / 2)) : (1 - 2.0f * (waveIndex - wavelength / 2) / (wavelength / 2));
|
||||
waveIndex++;
|
||||
|
||||
if (waveIndex >= wavelength)
|
||||
{
|
||||
waveFrequency = newWaveFrequency;
|
||||
waveIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Save the synthesized samples for later drawing
|
||||
for (int i = 0; i < SAMPLE_RATE - fc; i++) buffer[i] = buffer[i + fc];
|
||||
for (int i = 0; i < fc; i++) buffer[SAMPLE_RATE - fc + i] = frames[i];
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static void SawtoothCallback(void* framesOut, uint frameCount)
|
||||
{
|
||||
int fc = (int)frameCount;
|
||||
int wavelength = SAMPLE_RATE / waveFrequency;
|
||||
float* frames = (float*)framesOut;
|
||||
|
||||
// Synthesize the sawtooth wave
|
||||
for (int i = 0; i < fc; i++)
|
||||
{
|
||||
frames[i] = -1 + 2.0f * waveIndex / wavelength;
|
||||
waveIndex++;
|
||||
|
||||
if (waveIndex >= wavelength)
|
||||
{
|
||||
waveFrequency = newWaveFrequency;
|
||||
waveIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Save the synthesized samples for later drawing
|
||||
for (int i = 0; i < SAMPLE_RATE - fc; i++) buffer[i] = buffer[i + fc];
|
||||
for (int i = 0; i < fc; i++) buffer[SAMPLE_RATE - fc + i] = frames[i];
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [audio] example - stream callback");
|
||||
|
||||
SetTargetFPS(30);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new StreamCallback();
|
||||
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;
|
||||
}
|
||||
}
|
||||
222
Examples/Audio/StreamEffects.cs
Normal file
222
Examples/Audio/StreamEffects.cs
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [audio] example - stream effects
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 4.2, last time updated with raylib 5.0
|
||||
*
|
||||
* 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) 2022-2025 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Audio;
|
||||
|
||||
[ExcludeFromBrowser("AttachAudioStreamProcessor is unreliable on the wasm audio backend")]
|
||||
public unsafe partial class StreamEffects : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Audio / Stream Effects";
|
||||
|
||||
public string Title => "raylib [audio] example - stream effects";
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Global Variables Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
private static float[] delayBuffer = null;
|
||||
private static uint delayBufferSize = 0;
|
||||
private static uint delayReadIndex = 2;
|
||||
private static uint delayWriteIndex = 0;
|
||||
|
||||
// Low-pass filter state (was a function-local static in the C example)
|
||||
private static readonly float[] low = { 0.0f, 0.0f };
|
||||
|
||||
private Music music;
|
||||
private float timePlayed;
|
||||
private bool pause;
|
||||
private bool enableEffectLPF;
|
||||
private bool enableEffectDelay;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
InitAudioDevice(); // Initialize audio device
|
||||
|
||||
music = LoadMusicStream("resources/audio/country.mp3");
|
||||
|
||||
// Allocate buffer for the delay effect
|
||||
delayBufferSize = 48000 * 2; // 1 second delay (device sampleRate*channels)
|
||||
delayBuffer = new float[delayBufferSize];
|
||||
delayReadIndex = 2;
|
||||
delayWriteIndex = 0;
|
||||
low[0] = 0.0f;
|
||||
low[1] = 0.0f;
|
||||
|
||||
PlayMusicStream(music);
|
||||
|
||||
timePlayed = 0.0f; // Time played normalized [0.0f..1.0f]
|
||||
pause = false; // Music playing paused
|
||||
|
||||
enableEffectLPF = false; // Enable effect low-pass-filter
|
||||
enableEffectDelay = false; // Enable effect delay (1 second)
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateMusicStream(music); // Update music buffer with new stream data
|
||||
|
||||
// Restart music playing (stop and play)
|
||||
if (IsKeyPressed(KeyboardKey.Space))
|
||||
{
|
||||
StopMusicStream(music);
|
||||
PlayMusicStream(music);
|
||||
}
|
||||
|
||||
// Pause/Resume music playing
|
||||
if (IsKeyPressed(KeyboardKey.P))
|
||||
{
|
||||
pause = !pause;
|
||||
|
||||
if (pause) PauseMusicStream(music);
|
||||
else ResumeMusicStream(music);
|
||||
}
|
||||
|
||||
// Add/Remove effect: lowpass filter
|
||||
if (IsKeyPressed(KeyboardKey.F))
|
||||
{
|
||||
enableEffectLPF = !enableEffectLPF;
|
||||
if (enableEffectLPF) AttachAudioStreamProcessor(music.Stream, &AudioProcessEffectLPF);
|
||||
else DetachAudioStreamProcessor(music.Stream, &AudioProcessEffectLPF);
|
||||
}
|
||||
|
||||
// Add/Remove effect: delay
|
||||
if (IsKeyPressed(KeyboardKey.D))
|
||||
{
|
||||
enableEffectDelay = !enableEffectDelay;
|
||||
if (enableEffectDelay) AttachAudioStreamProcessor(music.Stream, &AudioProcessEffectDelay);
|
||||
else DetachAudioStreamProcessor(music.Stream, &AudioProcessEffectDelay);
|
||||
}
|
||||
|
||||
// Get normalized time played for current music stream
|
||||
timePlayed = GetMusicTimePlayed(music) / GetMusicTimeLength(music);
|
||||
|
||||
if (timePlayed > 1.0f) timePlayed = 1.0f; // Make sure time played is no longer than music
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
DrawText("MUSIC SHOULD BE PLAYING!", 245, 150, 20, Color.LightGray);
|
||||
|
||||
DrawRectangle(200, 180, 400, 12, Color.LightGray);
|
||||
DrawRectangle(200, 180, (int)(timePlayed * 400.0f), 12, Color.Maroon);
|
||||
DrawRectangleLines(200, 180, 400, 12, Color.Gray);
|
||||
|
||||
DrawText("PRESS SPACE TO RESTART MUSIC", 215, 230, 20, Color.LightGray);
|
||||
DrawText("PRESS P TO PAUSE/RESUME MUSIC", 208, 260, 20, Color.LightGray);
|
||||
|
||||
DrawText($"PRESS F TO TOGGLE LPF EFFECT: {(enableEffectLPF ? "ON" : "OFF")}", 200, 320, 20, Color.Gray);
|
||||
DrawText($"PRESS D TO TOGGLE DELAY EFFECT: {(enableEffectDelay ? "ON" : "OFF")}", 180, 350, 20, Color.Gray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadMusicStream(music); // Unload music stream buffers from RAM
|
||||
|
||||
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
|
||||
|
||||
delayBuffer = null; // Free delay buffer
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//------------------------------------------------------------------------------------
|
||||
// Audio effect: lowpass filter
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static void AudioProcessEffectLPF(void* buffer, uint frames)
|
||||
{
|
||||
const float cutoff = 70.0f / 44100.0f; // 70 Hz lowpass filter
|
||||
const float k = cutoff / (cutoff + 0.1591549431f); // RC filter formula
|
||||
|
||||
// Converts the buffer data before using it
|
||||
float* bufferData = (float*)buffer;
|
||||
for (uint i = 0; i < frames * 2; i += 2)
|
||||
{
|
||||
float l = bufferData[i];
|
||||
float r = bufferData[i + 1];
|
||||
|
||||
low[0] += k * (l - low[0]);
|
||||
low[1] += k * (r - low[1]);
|
||||
bufferData[i] = low[0];
|
||||
bufferData[i + 1] = low[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Audio effect: delay
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static void AudioProcessEffectDelay(void* buffer, uint frames)
|
||||
{
|
||||
float* bufferData = (float*)buffer;
|
||||
fixed (float* delay = delayBuffer)
|
||||
{
|
||||
for (uint i = 0; i < frames * 2; i += 2)
|
||||
{
|
||||
float leftDelay = delay[delayReadIndex++]; // ERROR: Reading buffer -> WHY??? Maybe thread related???
|
||||
float rightDelay = delay[delayReadIndex++];
|
||||
|
||||
if (delayReadIndex == delayBufferSize) delayReadIndex = 0;
|
||||
|
||||
bufferData[i] = 0.5f * bufferData[i] + 0.5f * leftDelay;
|
||||
bufferData[i + 1] = 0.5f * bufferData[i + 1] + 0.5f * rightDelay;
|
||||
|
||||
delay[delayWriteIndex++] = bufferData[i];
|
||||
delay[delayWriteIndex++] = bufferData[i + 1];
|
||||
if (delayWriteIndex == delayBufferSize) delayWriteIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [audio] example - stream effects");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new StreamEffects();
|
||||
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;
|
||||
}
|
||||
}
|
||||
379
Examples/Core/AutomationEvents.cs
Normal file
379
Examples/Core/AutomationEvents.cs
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - automation events
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 5.0, last time updated with raylib 5.0
|
||||
*
|
||||
* Example based on 2d_camera_platformer example by arvyy (@arvyy)
|
||||
*
|
||||
* 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) 2023-2025 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class AutomationEvents : IExample
|
||||
{
|
||||
private const int GRAVITY = 400;
|
||||
private const float PLAYER_JUMP_SPD = 350.0f;
|
||||
private const float PLAYER_HOR_SPD = 200.0f;
|
||||
|
||||
private const int MAX_ENVIRONMENT_ELEMENTS = 5;
|
||||
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Automation Events";
|
||||
|
||||
public string Title => "raylib [core] example - automation events";
|
||||
|
||||
private struct Player
|
||||
{
|
||||
public Vector2 Position;
|
||||
public float Speed;
|
||||
public bool CanJump;
|
||||
}
|
||||
|
||||
private struct EnvElement
|
||||
{
|
||||
public Rectangle Rect;
|
||||
public int Blocking;
|
||||
public Color Color;
|
||||
}
|
||||
|
||||
private Player player;
|
||||
private EnvElement[] envElements;
|
||||
private Camera2D camera;
|
||||
|
||||
private AutomationEventList aelist;
|
||||
private bool eventRecording;
|
||||
private bool eventPlaying;
|
||||
|
||||
private uint frameCounter;
|
||||
private uint playFrameCounter;
|
||||
private uint currentPlayFrame;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define player
|
||||
player = new Player();
|
||||
player.Position = new Vector2(400, 280);
|
||||
player.Speed = 0;
|
||||
player.CanJump = false;
|
||||
|
||||
// Define environment elements (platforms)
|
||||
envElements = new EnvElement[MAX_ENVIRONMENT_ELEMENTS]
|
||||
{
|
||||
new EnvElement { Rect = new Rectangle(0, 0, 1000, 400), Blocking = 0, Color = Color.LightGray },
|
||||
new EnvElement { Rect = new Rectangle(0, 400, 1000, 200), Blocking = 1, Color = Color.Gray },
|
||||
new EnvElement { Rect = new Rectangle(300, 200, 400, 10), Blocking = 1, Color = Color.Gray },
|
||||
new EnvElement { Rect = new Rectangle(250, 300, 100, 10), Blocking = 1, Color = Color.Gray },
|
||||
new EnvElement { Rect = new Rectangle(650, 300, 100, 10), Blocking = 1, Color = Color.Gray }
|
||||
};
|
||||
|
||||
// Define camera
|
||||
camera = new Camera2D();
|
||||
camera.Target = player.Position;
|
||||
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
|
||||
camera.Rotation = 0.0f;
|
||||
camera.Zoom = 1.0f;
|
||||
|
||||
// Automation events
|
||||
aelist = LoadAutomationEventList((sbyte*)null); // Initialize list of automation events to record new events
|
||||
SetAutomationEventList(ref aelist);
|
||||
eventRecording = false;
|
||||
eventPlaying = false;
|
||||
|
||||
frameCounter = 0;
|
||||
playFrameCounter = 0;
|
||||
currentPlayFrame = 0;
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
float deltaTime = 0.015f;//GetFrameTime();
|
||||
|
||||
// Dropped files logic
|
||||
//----------------------------------------------------------------------------------
|
||||
#if BROWSER
|
||||
// NOTE: drag-and-drop event-list loading (.txt/.rae) is not supported in the browser host.
|
||||
#else
|
||||
if (IsFileDropped())
|
||||
{
|
||||
FilePathList droppedFiles = LoadDroppedFiles();
|
||||
|
||||
// Supports loading .rgs style files (text or binary) and .png style palette images
|
||||
if (IsFileExtension(droppedFiles[0], ".txt;.rae"))
|
||||
{
|
||||
UnloadAutomationEventList(aelist);
|
||||
aelist = LoadAutomationEventList(droppedFiles[0]);
|
||||
|
||||
eventRecording = false;
|
||||
|
||||
// Reset scene state to play
|
||||
eventPlaying = true;
|
||||
playFrameCounter = 0;
|
||||
currentPlayFrame = 0;
|
||||
|
||||
player.Position = new Vector2(400, 280);
|
||||
player.Speed = 0;
|
||||
player.CanJump = false;
|
||||
|
||||
camera.Target = player.Position;
|
||||
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
|
||||
camera.Rotation = 0.0f;
|
||||
camera.Zoom = 1.0f;
|
||||
}
|
||||
|
||||
UnloadDroppedFiles(droppedFiles); // Unload filepaths from memory
|
||||
}
|
||||
#endif
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Update player
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyDown(KeyboardKey.Left)) player.Position.X -= PLAYER_HOR_SPD * deltaTime;
|
||||
if (IsKeyDown(KeyboardKey.Right)) player.Position.X += PLAYER_HOR_SPD * deltaTime;
|
||||
if (IsKeyDown(KeyboardKey.Space) && player.CanJump)
|
||||
{
|
||||
player.Speed = -PLAYER_JUMP_SPD;
|
||||
player.CanJump = false;
|
||||
}
|
||||
|
||||
int hitObstacle = 0;
|
||||
for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
|
||||
{
|
||||
EnvElement element = envElements[i];
|
||||
if (element.Blocking != 0 &&
|
||||
element.Rect.X <= player.Position.X &&
|
||||
element.Rect.X + element.Rect.Width >= player.Position.X &&
|
||||
element.Rect.Y >= player.Position.Y &&
|
||||
element.Rect.Y <= player.Position.Y + player.Speed * deltaTime)
|
||||
{
|
||||
hitObstacle = 1;
|
||||
player.Speed = 0.0f;
|
||||
player.Position.Y = element.Rect.Y;
|
||||
}
|
||||
}
|
||||
|
||||
if (hitObstacle == 0)
|
||||
{
|
||||
player.Position.Y += player.Speed * deltaTime;
|
||||
player.Speed += GRAVITY * deltaTime;
|
||||
player.CanJump = false;
|
||||
}
|
||||
else player.CanJump = true;
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.R))
|
||||
{
|
||||
// Reset game state
|
||||
player.Position = new Vector2(400, 280);
|
||||
player.Speed = 0;
|
||||
player.CanJump = false;
|
||||
|
||||
camera.Target = player.Position;
|
||||
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
|
||||
camera.Rotation = 0.0f;
|
||||
camera.Zoom = 1.0f;
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Events playing
|
||||
// NOTE: Logic must be before Camera update because it depends on mouse-wheel value,
|
||||
// that can be set by the played event... but some other inputs could be affected
|
||||
//----------------------------------------------------------------------------------
|
||||
if (eventPlaying)
|
||||
{
|
||||
// NOTE: Multiple events could be executed in a single frame
|
||||
while (playFrameCounter == aelist.Events[currentPlayFrame].Frame)
|
||||
{
|
||||
PlayAutomationEvent(aelist.Events[currentPlayFrame]);
|
||||
currentPlayFrame++;
|
||||
|
||||
if (currentPlayFrame == aelist.Count)
|
||||
{
|
||||
eventPlaying = false;
|
||||
currentPlayFrame = 0;
|
||||
playFrameCounter = 0;
|
||||
|
||||
TraceLog(TraceLogLevel.Info, "FINISH PLAYING!");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
playFrameCounter++;
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Update camera
|
||||
//----------------------------------------------------------------------------------
|
||||
camera.Target = player.Position;
|
||||
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
|
||||
float minX = 1000, minY = 1000, maxX = -1000, maxY = -1000;
|
||||
|
||||
// WARNING: On event replay, mouse-wheel internal value is set
|
||||
camera.Zoom += ((float)GetMouseWheelMove() * 0.05f);
|
||||
if (camera.Zoom > 3.0f) camera.Zoom = 3.0f;
|
||||
else if (camera.Zoom < 0.25f) camera.Zoom = 0.25f;
|
||||
|
||||
for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
|
||||
{
|
||||
EnvElement element = envElements[i];
|
||||
minX = MathF.Min(element.Rect.X, minX);
|
||||
maxX = MathF.Max(element.Rect.X + element.Rect.Width, maxX);
|
||||
minY = MathF.Min(element.Rect.Y, minY);
|
||||
maxY = MathF.Max(element.Rect.Y + element.Rect.Height, maxY);
|
||||
}
|
||||
|
||||
Vector2 max = GetWorldToScreen2D(new Vector2(maxX, maxY), camera);
|
||||
Vector2 min = GetWorldToScreen2D(new Vector2(minX, minY), camera);
|
||||
|
||||
if (max.X < screenWidth) camera.Offset.X = screenWidth - (max.X - (float)screenWidth / 2);
|
||||
if (max.Y < screenHeight) camera.Offset.Y = screenHeight - (max.Y - (float)screenHeight / 2);
|
||||
if (min.X > 0) camera.Offset.X = (float)screenWidth / 2 - min.X;
|
||||
if (min.Y > 0) camera.Offset.Y = (float)screenHeight / 2 - min.Y;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Events management
|
||||
if (IsKeyPressed(KeyboardKey.S)) // Toggle events recording
|
||||
{
|
||||
if (!eventPlaying)
|
||||
{
|
||||
if (eventRecording)
|
||||
{
|
||||
StopAutomationEventRecording();
|
||||
eventRecording = false;
|
||||
|
||||
ExportAutomationEventList(aelist, "automation.rae");
|
||||
|
||||
TraceLog(TraceLogLevel.Info, $"RECORDED FRAMES: {aelist.Count}");
|
||||
}
|
||||
else
|
||||
{
|
||||
SetAutomationEventBaseFrame(180);
|
||||
StartAutomationEventRecording();
|
||||
eventRecording = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (IsKeyPressed(KeyboardKey.A)) // Toggle events playing (WARNING: Starts next frame)
|
||||
{
|
||||
if (!eventRecording && (aelist.Count > 0))
|
||||
{
|
||||
// Reset scene state to play
|
||||
eventPlaying = true;
|
||||
playFrameCounter = 0;
|
||||
currentPlayFrame = 0;
|
||||
|
||||
player.Position = new Vector2(400, 280);
|
||||
player.Speed = 0;
|
||||
player.CanJump = false;
|
||||
|
||||
camera.Target = player.Position;
|
||||
camera.Offset = new Vector2(screenWidth / 2.0f, screenHeight / 2.0f);
|
||||
camera.Rotation = 0.0f;
|
||||
camera.Zoom = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (eventRecording || eventPlaying) frameCounter++;
|
||||
else frameCounter = 0;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.LightGray);
|
||||
|
||||
BeginMode2D(camera);
|
||||
|
||||
// Draw environment elements
|
||||
for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
|
||||
{
|
||||
DrawRectangleRec(envElements[i].Rect, envElements[i].Color);
|
||||
}
|
||||
|
||||
// Draw player rectangle
|
||||
DrawRectangleRec(new Rectangle(player.Position.X - 20, player.Position.Y - 40, 40, 40), Color.Red);
|
||||
|
||||
EndMode2D();
|
||||
|
||||
// Draw game controls
|
||||
DrawRectangle(10, 10, 290, 145, Fade(Color.SkyBlue, 0.5f));
|
||||
DrawRectangleLines(10, 10, 290, 145, Fade(Color.Blue, 0.8f));
|
||||
|
||||
DrawText("Controls:", 20, 20, 10, Color.Black);
|
||||
DrawText("- RIGHT | LEFT: Player movement", 30, 40, 10, Color.DarkGray);
|
||||
DrawText("- SPACE: Player jump", 30, 60, 10, Color.DarkGray);
|
||||
DrawText("- R: Reset game state", 30, 80, 10, Color.DarkGray);
|
||||
|
||||
DrawText("- S: START/STOP RECORDING INPUT EVENTS", 30, 110, 10, Color.Black);
|
||||
DrawText("- A: REPLAY LAST RECORDED INPUT EVENTS", 30, 130, 10, Color.Black);
|
||||
|
||||
// Draw automation events recording indicator
|
||||
if (eventRecording)
|
||||
{
|
||||
DrawRectangle(10, 160, 290, 30, Fade(Color.Red, 0.3f));
|
||||
DrawRectangleLines(10, 160, 290, 30, Fade(Color.Maroon, 0.8f));
|
||||
DrawCircle(30, 175, 10, Color.Maroon);
|
||||
|
||||
if (((frameCounter / 15) % 2) == 1) DrawText($"RECORDING EVENTS... [{aelist.Count}]", 50, 170, 10, Color.Maroon);
|
||||
}
|
||||
else if (eventPlaying)
|
||||
{
|
||||
DrawRectangle(10, 160, 290, 30, Fade(Color.Lime, 0.3f));
|
||||
DrawRectangleLines(10, 160, 290, 30, Fade(Color.DarkGreen, 0.8f));
|
||||
DrawTriangle(new Vector2(20, 155 + 10), new Vector2(20, 155 + 30), new Vector2(40, 155 + 20), Color.DarkGreen);
|
||||
|
||||
if (((frameCounter / 15) % 2) == 1) DrawText($"PLAYING RECORDED EVENTS... [{currentPlayFrame}]", 50, 170, 10, Color.DarkGreen);
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadAutomationEventList(aelist);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - automation events");
|
||||
|
||||
var game = new AutomationEvents();
|
||||
game.Init();
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
// Main game loop
|
||||
while (!WindowShouldClose())
|
||||
{
|
||||
game.Update();
|
||||
}
|
||||
|
||||
game.Unload();
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
CloseWindow(); // Close window and OpenGL context
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
184
Examples/Core/Camera2dMouseZoom.cs
Normal file
184
Examples/Core/Camera2dMouseZoom.cs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - 2d camera mouse zoom
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 4.2, last time updated with raylib 4.2
|
||||
*
|
||||
* Example contributed by Jeffery Myers (@JeffM2501) 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) 2022-2025 Jeffery Myers (@JeffM2501)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class Camera2dMouseZoom : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Camera 2D Mouse Zoom";
|
||||
|
||||
public string Title => "raylib [core] example - 2d camera mouse zoom";
|
||||
|
||||
private Camera2D camera;
|
||||
private int zoomMode; // 0-Mouse Wheel, 1-Mouse Move
|
||||
|
||||
public void Init()
|
||||
{
|
||||
camera = new Camera2D();
|
||||
camera.Zoom = 1.0f;
|
||||
|
||||
zoomMode = 0;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyPressed(KeyboardKey.One))
|
||||
{
|
||||
zoomMode = 0;
|
||||
}
|
||||
else if (IsKeyPressed(KeyboardKey.Two))
|
||||
{
|
||||
zoomMode = 1;
|
||||
}
|
||||
|
||||
// Translate based on mouse right click
|
||||
if (IsMouseButtonDown(MouseButton.Left))
|
||||
{
|
||||
Vector2 delta = GetMouseDelta();
|
||||
delta = Vector2Scale(delta, -1.0f / camera.Zoom);
|
||||
camera.Target = Vector2Add(camera.Target, delta);
|
||||
}
|
||||
|
||||
if (zoomMode == 0)
|
||||
{
|
||||
// Zoom based on mouse wheel
|
||||
float wheel = GetMouseWheelMove();
|
||||
if (wheel != 0)
|
||||
{
|
||||
// Get the world point that is under the mouse
|
||||
Vector2 mouseWorldPos = GetScreenToWorld2D(GetMousePosition(), camera);
|
||||
|
||||
// Set the offset to where the mouse is
|
||||
camera.Offset = GetMousePosition();
|
||||
|
||||
// Set the target to match, so that the camera maps the world space point
|
||||
// under the cursor to the screen space point under the cursor at any zoom
|
||||
camera.Target = mouseWorldPos;
|
||||
|
||||
// Zoom increment
|
||||
// Uses log scaling to provide consistent zoom speed
|
||||
float scale = 0.2f * wheel;
|
||||
camera.Zoom = Clamp(MathF.Exp(MathF.Log(camera.Zoom) + scale), 0.125f, 64.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Zoom based on mouse right click
|
||||
if (IsMouseButtonPressed(MouseButton.Right))
|
||||
{
|
||||
// Get the world point that is under the mouse
|
||||
Vector2 mouseWorldPos = GetScreenToWorld2D(GetMousePosition(), camera);
|
||||
|
||||
// Set the offset to where the mouse is
|
||||
camera.Offset = GetMousePosition();
|
||||
|
||||
// Set the target to match, so that the camera maps the world space point
|
||||
// under the cursor to the screen space point under the cursor at any zoom
|
||||
camera.Target = mouseWorldPos;
|
||||
}
|
||||
|
||||
if (IsMouseButtonDown(MouseButton.Right))
|
||||
{
|
||||
// Zoom increment
|
||||
// Uses log scaling to provide consistent zoom speed
|
||||
float deltaX = GetMouseDelta().X;
|
||||
float scale = 0.005f * deltaX;
|
||||
camera.Zoom = Clamp(MathF.Exp(MathF.Log(camera.Zoom) + scale), 0.125f, 64.0f);
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode2D(camera);
|
||||
// Draw the 3d grid, rotated 90 degrees and centered around 0,0
|
||||
// just so we have something in the XY plane
|
||||
Rlgl.PushMatrix();
|
||||
Rlgl.Translatef(0, 25 * 50, 0);
|
||||
Rlgl.Rotatef(90, 1, 0, 0);
|
||||
DrawGrid(100, 50);
|
||||
Rlgl.PopMatrix();
|
||||
|
||||
// Draw a reference circle
|
||||
DrawCircle(GetScreenWidth() / 2, GetScreenHeight() / 2, 50, Color.Maroon);
|
||||
EndMode2D();
|
||||
|
||||
// Draw mouse reference
|
||||
//Vector2 mousePos = GetWorldToScreen2D(GetMousePosition(), camera)
|
||||
DrawCircleV(GetMousePosition(), 4, Color.DarkGray);
|
||||
DrawTextEx(GetFontDefault(), $"[{GetMouseX()}, {GetMouseY()}]",
|
||||
Vector2Add(GetMousePosition(), new Vector2(-44, -24)), 20, 2, Color.Black);
|
||||
|
||||
DrawText("[1][2] Select mouse zoom mode (Wheel or Move)", 20, 20, 20, Color.DarkGray);
|
||||
if (zoomMode == 0)
|
||||
{
|
||||
DrawText("Mouse left button drag to move, mouse wheel to zoom", 20, 50, 20, Color.DarkGray);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawText("Mouse left button drag to move, mouse press and move to zoom", 20, 50, 20, Color.DarkGray);
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - 2d camera mouse zoom");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new Camera2dMouseZoom();
|
||||
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;
|
||||
}
|
||||
}
|
||||
348
Examples/Core/Camera3dFps.cs
Normal file
348
Examples/Core/Camera3dFps.cs
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - 3d camera fps
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.5
|
||||
*
|
||||
* Example contributed by Agnis Aldiņš (@nezvers) 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 Agnis Aldiņš (@nezvers)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class Camera3dFps : IExample
|
||||
{
|
||||
// Movement constants
|
||||
private const float GRAVITY = 32.0f;
|
||||
private const float MAX_SPEED = 20.0f;
|
||||
private const float CROUCH_SPEED = 5.0f;
|
||||
private const float JUMP_FORCE = 12.0f;
|
||||
private const float MAX_ACCEL = 150.0f;
|
||||
// Grounded drag
|
||||
private const float FRICTION = 0.86f;
|
||||
// Increasing air drag, increases strafing speed
|
||||
private const float AIR_DRAG = 0.98f;
|
||||
// Responsiveness for turning movement direction to looked direction
|
||||
private const float CONTROL = 15.0f;
|
||||
private const float CROUCH_HEIGHT = 0.0f;
|
||||
private const float STAND_HEIGHT = 1.0f;
|
||||
private const float BOTTOM_HEIGHT = 0.5f;
|
||||
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / 3D Camera FPS";
|
||||
|
||||
public string Title => "raylib [core] example - 3d camera fps";
|
||||
|
||||
public bool CursorDisabled => true;
|
||||
|
||||
// Body structure
|
||||
private struct Body
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Vector3 Velocity;
|
||||
public Vector3 Dir;
|
||||
public bool IsGrounded;
|
||||
}
|
||||
|
||||
// State that was global in the C example
|
||||
private readonly Vector2 sensitivity = new Vector2(0.001f, 0.001f);
|
||||
|
||||
private Body player;
|
||||
private Vector2 lookRotation;
|
||||
private float headTimer;
|
||||
private float walkLerp;
|
||||
private float headLerp;
|
||||
private Vector2 lean;
|
||||
|
||||
private Camera3D camera;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
player = new Body();
|
||||
lookRotation = new Vector2(0, 0);
|
||||
headTimer = 0.0f;
|
||||
walkLerp = 0.0f;
|
||||
headLerp = STAND_HEIGHT;
|
||||
lean = new Vector2(0, 0);
|
||||
|
||||
// Initialize camera variables
|
||||
// NOTE: UpdateCameraFPS() takes care of the rest
|
||||
camera = new Camera3D();
|
||||
camera.FovY = 60.0f;
|
||||
camera.Projection = CameraProjection.Perspective;
|
||||
camera.Position = new Vector3(
|
||||
player.Position.X,
|
||||
player.Position.Y + (BOTTOM_HEIGHT + headLerp),
|
||||
player.Position.Z);
|
||||
|
||||
UpdateCameraFPS(ref camera); // Update camera parameters
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
Vector2 mouseDelta = GetMouseDelta();
|
||||
lookRotation.X -= mouseDelta.X * sensitivity.X;
|
||||
lookRotation.Y += mouseDelta.Y * sensitivity.Y;
|
||||
|
||||
int sideway = (IsKeyDown(KeyboardKey.D) ? 1 : 0) - (IsKeyDown(KeyboardKey.A) ? 1 : 0);
|
||||
int forward = (IsKeyDown(KeyboardKey.W) ? 1 : 0) - (IsKeyDown(KeyboardKey.S) ? 1 : 0);
|
||||
bool crouching = IsKeyDown(KeyboardKey.LeftControl);
|
||||
UpdateBody(ref player, lookRotation.X, sideway, forward, IsKeyPressed(KeyboardKey.Space), crouching);
|
||||
|
||||
float delta = GetFrameTime();
|
||||
headLerp = Lerp(headLerp, (crouching ? CROUCH_HEIGHT : STAND_HEIGHT), 20.0f * delta);
|
||||
camera.Position = new Vector3(
|
||||
player.Position.X,
|
||||
player.Position.Y + (BOTTOM_HEIGHT + headLerp),
|
||||
player.Position.Z);
|
||||
|
||||
if (player.IsGrounded && ((forward != 0) || (sideway != 0)))
|
||||
{
|
||||
headTimer += delta * 3.0f;
|
||||
walkLerp = Lerp(walkLerp, 1.0f, 10.0f * delta);
|
||||
camera.FovY = Lerp(camera.FovY, 55.0f, 5.0f * delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
walkLerp = Lerp(walkLerp, 0.0f, 10.0f * delta);
|
||||
camera.FovY = Lerp(camera.FovY, 60.0f, 5.0f * delta);
|
||||
}
|
||||
|
||||
lean.X = Lerp(lean.X, sideway * 0.02f, 10.0f * delta);
|
||||
lean.Y = Lerp(lean.Y, forward * 0.015f, 10.0f * delta);
|
||||
|
||||
UpdateCameraFPS(ref camera);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
DrawLevel();
|
||||
EndMode3D();
|
||||
|
||||
// Draw info box
|
||||
DrawRectangle(5, 5, 330, 75, Fade(Color.SkyBlue, 0.5f));
|
||||
DrawRectangleLines(5, 5, 330, 75, Color.Blue);
|
||||
|
||||
DrawText("Camera controls:", 15, 15, 10, Color.Black);
|
||||
DrawText("- Move keys: W, A, S, D, Space, Left-Ctrl", 15, 30, 10, Color.Black);
|
||||
DrawText("- Look around: arrow keys or mouse", 15, 45, 10, Color.Black);
|
||||
float velLen = Vector2Length(new Vector2(player.Velocity.X, player.Velocity.Z));
|
||||
DrawText($"- Velocity Len: ({velLen:00.000})", 15, 60, 10, Color.Black);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
// Update body considering current world state
|
||||
private void UpdateBody(ref Body body, float rot, int side, int forward, bool jumpPressed, bool crouchHold)
|
||||
{
|
||||
Vector2 input = new Vector2((float)side, (float)-forward);
|
||||
|
||||
// Upstream guards this with `#if defined(NORMALIZE_INPUT)`, which is always true given the
|
||||
// `#define NORMALIZE_INPUT 0` above it (defined() tests definedness, not the value), so the
|
||||
// diagonal-movement normalization is active.
|
||||
// Slow down diagonal movement
|
||||
if ((side != 0) && (forward != 0)) input = Vector2Normalize(input);
|
||||
|
||||
float delta = GetFrameTime();
|
||||
|
||||
if (!body.IsGrounded) body.Velocity.Y -= GRAVITY * delta;
|
||||
|
||||
if (body.IsGrounded && jumpPressed)
|
||||
{
|
||||
body.Velocity.Y = JUMP_FORCE;
|
||||
body.IsGrounded = false;
|
||||
|
||||
// Sound can be played at this moment
|
||||
//SetSoundPitch(fxJump, 1.0f + (GetRandomValue(-100, 100)*0.001));
|
||||
//PlaySound(fxJump);
|
||||
}
|
||||
|
||||
Vector3 front = new Vector3(MathF.Sin(rot), 0.0f, MathF.Cos(rot));
|
||||
Vector3 right = new Vector3(MathF.Cos(-rot), 0.0f, MathF.Sin(-rot));
|
||||
|
||||
Vector3 desiredDir = new Vector3(
|
||||
input.X * right.X + input.Y * front.X,
|
||||
0.0f,
|
||||
input.X * right.Z + input.Y * front.Z);
|
||||
body.Dir = Vector3Lerp(body.Dir, desiredDir, CONTROL * delta);
|
||||
|
||||
float decel = (body.IsGrounded ? FRICTION : AIR_DRAG);
|
||||
Vector3 hvel = new Vector3(body.Velocity.X * decel, 0.0f, body.Velocity.Z * decel);
|
||||
|
||||
float hvelLength = Vector3Length(hvel); // Magnitude
|
||||
if (hvelLength < (MAX_SPEED * 0.01f)) hvel = new Vector3(0, 0, 0);
|
||||
|
||||
// This is what creates strafing
|
||||
float speed = Vector3DotProduct(hvel, body.Dir);
|
||||
|
||||
// Whenever the amount of acceleration to add is clamped by the maximum acceleration constant,
|
||||
// a Player can make the speed faster by bringing the direction closer to horizontal velocity angle
|
||||
// More info here: https://youtu.be/v3zT3Z5apaM?t=165
|
||||
float maxSpeed = (crouchHold ? CROUCH_SPEED : MAX_SPEED);
|
||||
float accel = Clamp(maxSpeed - speed, 0.0f, MAX_ACCEL * delta);
|
||||
hvel.X += body.Dir.X * accel;
|
||||
hvel.Z += body.Dir.Z * accel;
|
||||
|
||||
body.Velocity.X = hvel.X;
|
||||
body.Velocity.Z = hvel.Z;
|
||||
|
||||
body.Position.X += body.Velocity.X * delta;
|
||||
body.Position.Y += body.Velocity.Y * delta;
|
||||
body.Position.Z += body.Velocity.Z * delta;
|
||||
|
||||
// Fancy collision system against the floor
|
||||
if (body.Position.Y <= 0.0f)
|
||||
{
|
||||
body.Position.Y = 0.0f;
|
||||
body.Velocity.Y = 0.0f;
|
||||
body.IsGrounded = true; // Enable jumping
|
||||
}
|
||||
}
|
||||
|
||||
// Update camera for FPS behaviour
|
||||
private void UpdateCameraFPS(ref Camera3D camera)
|
||||
{
|
||||
Vector3 up = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
Vector3 targetOffset = new Vector3(0.0f, 0.0f, -1.0f);
|
||||
|
||||
// Left and right
|
||||
Vector3 yaw = Vector3RotateByAxisAngle(targetOffset, up, lookRotation.X);
|
||||
|
||||
// Clamp view up
|
||||
float maxAngleUp = Vector3Angle(up, yaw);
|
||||
maxAngleUp -= 0.001f; // Avoid numerical errors
|
||||
if (-(lookRotation.Y) > maxAngleUp) { lookRotation.Y = -maxAngleUp; }
|
||||
|
||||
// Clamp view down
|
||||
float maxAngleDown = Vector3Angle(Vector3Negate(up), yaw);
|
||||
maxAngleDown *= -1.0f; // Downwards angle is negative
|
||||
maxAngleDown += 0.001f; // Avoid numerical errors
|
||||
if (-(lookRotation.Y) < maxAngleDown) { lookRotation.Y = -maxAngleDown; }
|
||||
|
||||
// Up and down
|
||||
Vector3 right = Vector3Normalize(Vector3CrossProduct(yaw, up));
|
||||
|
||||
// Rotate view vector around right axis
|
||||
float pitchAngle = -lookRotation.Y - lean.Y;
|
||||
pitchAngle = Clamp(pitchAngle, -MathF.PI / 2 + 0.0001f, MathF.PI / 2 - 0.0001f); // Clamp angle so it doesn't go past straight up or straight down
|
||||
Vector3 pitch = Vector3RotateByAxisAngle(yaw, right, pitchAngle);
|
||||
|
||||
// Head animation
|
||||
// Rotate up direction around forward axis
|
||||
float headSin = MathF.Sin(headTimer * MathF.PI);
|
||||
float headCos = MathF.Cos(headTimer * MathF.PI);
|
||||
const float stepRotation = 0.01f;
|
||||
camera.Up = Vector3RotateByAxisAngle(up, pitch, headSin * stepRotation + lean.X);
|
||||
|
||||
// Camera BOB
|
||||
const float bobSide = 0.1f;
|
||||
const float bobUp = 0.15f;
|
||||
Vector3 bobbing = Vector3Scale(right, headSin * bobSide);
|
||||
bobbing.Y = MathF.Abs(headCos * bobUp);
|
||||
|
||||
camera.Position = Vector3Add(camera.Position, Vector3Scale(bobbing, walkLerp));
|
||||
camera.Target = Vector3Add(camera.Position, pitch);
|
||||
}
|
||||
|
||||
// Draw game level
|
||||
private void DrawLevel()
|
||||
{
|
||||
const int floorExtent = 25;
|
||||
const float tileSize = 5.0f;
|
||||
Color tileColor1 = new Color(150, 200, 200, 255);
|
||||
|
||||
// Floor tiles
|
||||
for (int y = -floorExtent; y < floorExtent; y++)
|
||||
{
|
||||
for (int x = -floorExtent; x < floorExtent; x++)
|
||||
{
|
||||
if ((y & 1) != 0 && (x & 1) != 0)
|
||||
{
|
||||
DrawPlane(new Vector3(x * tileSize, 0.0f, y * tileSize), new Vector2(tileSize, tileSize), tileColor1);
|
||||
}
|
||||
else if ((y & 1) == 0 && (x & 1) == 0)
|
||||
{
|
||||
DrawPlane(new Vector3(x * tileSize, 0.0f, y * tileSize), new Vector2(tileSize, tileSize), Color.LightGray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 towerSize = new Vector3(16.0f, 32.0f, 16.0f);
|
||||
Color towerColor = new Color(150, 200, 200, 255);
|
||||
|
||||
Vector3 towerPos = new Vector3(16.0f, 16.0f, 16.0f);
|
||||
DrawCubeV(towerPos, towerSize, towerColor);
|
||||
DrawCubeWiresV(towerPos, towerSize, Color.DarkBlue);
|
||||
|
||||
towerPos.X *= -1;
|
||||
DrawCubeV(towerPos, towerSize, towerColor);
|
||||
DrawCubeWiresV(towerPos, towerSize, Color.DarkBlue);
|
||||
|
||||
towerPos.Z *= -1;
|
||||
DrawCubeV(towerPos, towerSize, towerColor);
|
||||
DrawCubeWiresV(towerPos, towerSize, Color.DarkBlue);
|
||||
|
||||
towerPos.X *= -1;
|
||||
DrawCubeV(towerPos, towerSize, towerColor);
|
||||
DrawCubeWiresV(towerPos, towerSize, Color.DarkBlue);
|
||||
|
||||
// Red sun
|
||||
DrawSphere(new Vector3(300.0f, 300.0f, 0.0f), 100.0f, new Color(255, 0, 0, 255));
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - 3d camera fps");
|
||||
|
||||
DisableCursor(); // Limit cursor to relative movement inside the window
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new Camera3dFps();
|
||||
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;
|
||||
}
|
||||
}
|
||||
286
Examples/Core/ClipboardText.cs
Normal file
286
Examples/Core/ClipboardText.cs
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - clipboard text
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Ananth S (@Ananth1839) 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 Ananth S (@Ananth1839)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
// NOTE: The upstream C example uses raygui (GuiTextBox/GuiButton/GuiLabel) for its UI.
|
||||
// raygui is not bound in raylib-cs, so the widgets below are minimal re-implementations
|
||||
// using plain raylib drawing/input. Clipboard behaviour (cut/copy/paste and CTRL+X/C/V
|
||||
// shortcuts) matches the original.
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class ClipboardText : IExample
|
||||
{
|
||||
private const int MaxTextSamples = 5;
|
||||
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Clipboard Text";
|
||||
|
||||
public string Title => "raylib [core] example - clipboard text";
|
||||
|
||||
private string[] sampleTexts;
|
||||
private string clipboardText;
|
||||
private StringBuilder inputBuffer;
|
||||
|
||||
// UI required variables
|
||||
private bool textBoxEditMode;
|
||||
|
||||
private bool btnCutPressed;
|
||||
private bool btnCopyPressed;
|
||||
private bool btnPastePressed;
|
||||
private bool btnClearPressed;
|
||||
private bool btnRandomPressed;
|
||||
|
||||
private int framesCounter;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Define some sample texts
|
||||
sampleTexts = new string[]
|
||||
{
|
||||
"Hello from raylib!",
|
||||
"The quick brown fox jumps over the lazy dog",
|
||||
"Clipboard operations are useful!",
|
||||
"raylib is a simple and easy-to-use library",
|
||||
"Copy and paste me!"
|
||||
};
|
||||
|
||||
clipboardText = null;
|
||||
inputBuffer = new StringBuilder("Hello from raylib!"); // Random initial string
|
||||
|
||||
// UI required variables
|
||||
textBoxEditMode = false;
|
||||
|
||||
btnCutPressed = false;
|
||||
btnCopyPressed = false;
|
||||
btnPastePressed = false;
|
||||
btnClearPressed = false;
|
||||
btnRandomPressed = false;
|
||||
|
||||
framesCounter = 0;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
framesCounter++;
|
||||
|
||||
// Handle button interactions
|
||||
if (btnCutPressed)
|
||||
{
|
||||
SetClipboardText(inputBuffer.ToString());
|
||||
clipboardText = GetClipboardText_();
|
||||
inputBuffer.Clear(); // Quick solution to clear text
|
||||
}
|
||||
|
||||
if (btnCopyPressed)
|
||||
{
|
||||
SetClipboardText(inputBuffer.ToString()); // Copy text to clipboard
|
||||
clipboardText = GetClipboardText_(); // Get text from clipboard
|
||||
}
|
||||
|
||||
if (btnPastePressed)
|
||||
{
|
||||
// Paste text from clipboard
|
||||
clipboardText = GetClipboardText_();
|
||||
if (clipboardText != null) SetInputBuffer(clipboardText);
|
||||
}
|
||||
|
||||
if (btnClearPressed)
|
||||
{
|
||||
inputBuffer.Clear(); // Quick solution to clear text
|
||||
}
|
||||
|
||||
if (btnRandomPressed)
|
||||
{
|
||||
// Get random text from sample list
|
||||
SetInputBuffer(sampleTexts[GetRandomValue(0, MaxTextSamples - 1)]);
|
||||
}
|
||||
|
||||
// Quick cut/copy/paste with keyboard shortcuts
|
||||
if (IsKeyDown(KeyboardKey.LeftControl) || IsKeyDown(KeyboardKey.RightControl))
|
||||
{
|
||||
if (IsKeyPressed(KeyboardKey.X))
|
||||
{
|
||||
SetClipboardText(inputBuffer.ToString());
|
||||
inputBuffer.Clear(); // Quick solution to clear text
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.C)) SetClipboardText(inputBuffer.ToString());
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.V))
|
||||
{
|
||||
clipboardText = GetClipboardText_();
|
||||
if (clipboardText != null) SetInputBuffer(clipboardText);
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw instructions
|
||||
GuiLabel(new Rectangle(50, 20, 700, 36), "Use the BUTTONS or KEY SHORTCUTS:");
|
||||
DrawText("[CTRL+X] - CUT | [CTRL+C] COPY | [CTRL+V] | PASTE", 50, 60, 20, Color.Maroon);
|
||||
|
||||
// Draw text box
|
||||
if (GuiTextBox(new Rectangle(50, 120, 652, 40), inputBuffer, 256, textBoxEditMode)) textBoxEditMode = !textBoxEditMode;
|
||||
|
||||
// Random text button
|
||||
btnRandomPressed = GuiButton(new Rectangle(50 + 652 + 8, 120, 40, 40), "RND");
|
||||
|
||||
// Draw buttons
|
||||
btnCutPressed = GuiButton(new Rectangle(50, 180, 158, 40), "CUT");
|
||||
btnCopyPressed = GuiButton(new Rectangle(50 + 165, 180, 158, 40), "COPY");
|
||||
btnPastePressed = GuiButton(new Rectangle(50 + 165 * 2, 180, 158, 40), "PASTE");
|
||||
btnClearPressed = GuiButton(new Rectangle(50 + 165 * 3, 180, 158, 40), "CLEAR");
|
||||
|
||||
// Draw clipboard status
|
||||
GuiLabel(new Rectangle(50, 260, 700, 40), "Clipboard current text data:");
|
||||
GuiTextBoxReadOnly(new Rectangle(50, 300, 700, 40), clipboardText);
|
||||
GuiLabel(new Rectangle(50, 360, 700, 40), "Try copying text from other applications and pasting here!");
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
// Replace input buffer contents (equivalent to raylib TextCopy into a fixed buffer)
|
||||
private void SetInputBuffer(string text)
|
||||
{
|
||||
inputBuffer.Clear();
|
||||
if (text != null)
|
||||
{
|
||||
if (text.Length > 255) text = text.Substring(0, 255);
|
||||
inputBuffer.Append(text);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Minimal raygui-like widgets (plain raylib re-implementation)
|
||||
//----------------------------------------------------------------------------------
|
||||
private static void GuiLabel(Rectangle bounds, string text)
|
||||
{
|
||||
DrawText(text, (int)bounds.X, (int)(bounds.Y + (bounds.Height - 20) / 2), 20, Color.DarkGray);
|
||||
}
|
||||
|
||||
private static bool GuiButton(Rectangle bounds, string text)
|
||||
{
|
||||
bool pressed = false;
|
||||
Vector2 mouse = GetMousePosition();
|
||||
bool hover = CheckCollisionPointRec(mouse, bounds);
|
||||
|
||||
Color fill = hover ? (IsMouseButtonDown(MouseButton.Left) ? Color.SkyBlue : Color.LightGray) : Color.RayWhite;
|
||||
DrawRectangleRec(bounds, fill);
|
||||
DrawRectangleLinesEx(bounds, 1, hover ? Color.Blue : Color.Gray);
|
||||
|
||||
int textWidth = MeasureText(text, 20);
|
||||
DrawText(text, (int)(bounds.X + (bounds.Width - textWidth) / 2), (int)(bounds.Y + (bounds.Height - 20) / 2), 20, Color.DarkGray);
|
||||
|
||||
if (hover && IsMouseButtonReleased(MouseButton.Left)) pressed = true;
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
private bool GuiTextBox(Rectangle bounds, StringBuilder text, int maxChars, bool editMode)
|
||||
{
|
||||
bool pressed = false;
|
||||
Vector2 mouse = GetMousePosition();
|
||||
bool hover = CheckCollisionPointRec(mouse, bounds);
|
||||
|
||||
if (editMode)
|
||||
{
|
||||
// Get char pressed (unicode character) on the queue
|
||||
int key = GetCharPressed();
|
||||
while (key > 0)
|
||||
{
|
||||
if ((key >= 32) && (key <= 125) && (text.Length < maxChars - 1))
|
||||
{
|
||||
text.Append((char)key);
|
||||
}
|
||||
|
||||
key = GetCharPressed();
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Backspace) && (text.Length > 0)) text.Remove(text.Length - 1, 1);
|
||||
}
|
||||
|
||||
DrawRectangleRec(bounds, Color.RayWhite);
|
||||
DrawRectangleLinesEx(bounds, editMode ? 2 : 1, editMode ? Color.Red : (hover ? Color.Blue : Color.Gray));
|
||||
|
||||
string content = text.ToString();
|
||||
DrawText(content, (int)bounds.X + 4, (int)(bounds.Y + (bounds.Height - 20) / 2), 20, Color.DarkGray);
|
||||
|
||||
// Draw blinking cursor while editing
|
||||
if (editMode && ((framesCounter / 20) % 2 == 0))
|
||||
{
|
||||
DrawText("_", (int)bounds.X + 4 + MeasureText(content, 20), (int)(bounds.Y + (bounds.Height - 20) / 2), 20, Color.DarkGray);
|
||||
}
|
||||
|
||||
if (hover && IsMouseButtonPressed(MouseButton.Left)) pressed = true;
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
private static void GuiTextBoxReadOnly(Rectangle bounds, string text)
|
||||
{
|
||||
DrawRectangleRec(bounds, Color.LightGray);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (text != null) DrawText(text, (int)bounds.X + 4, (int)(bounds.Y + (bounds.Height - 20) / 2), 20, Color.Gray);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - clipboard text");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new ClipboardText();
|
||||
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;
|
||||
}
|
||||
}
|
||||
253
Examples/Core/ComputeHash.cs
Normal file
253
Examples/Core/ComputeHash.cs
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - compute hash
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* 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 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
// NOTE: The upstream C example uses raygui (GuiTextBox/GuiButton/GuiLabel) for its UI.
|
||||
// raygui is not bound in raylib-cs, so the widgets below are minimal re-implementations
|
||||
// using plain raylib drawing/input. The hashing/Base64 logic is a faithful port.
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public unsafe partial class ComputeHash : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Compute Hash";
|
||||
|
||||
public string Title => "raylib [core] example - compute hash";
|
||||
|
||||
// UI controls variables
|
||||
private StringBuilder textInput;
|
||||
private bool textBoxEditMode;
|
||||
private bool btnComputeHashes;
|
||||
|
||||
// Data hash values
|
||||
private uint hashCRC32;
|
||||
private uint[] hashMD5;
|
||||
private uint[] hashSHA1;
|
||||
private uint[] hashSHA256;
|
||||
|
||||
// Base64 encoded data
|
||||
private string base64Text;
|
||||
|
||||
private int framesCounter;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
textInput = new StringBuilder("The quick brown fox jumps over the lazy dog.");
|
||||
textBoxEditMode = false;
|
||||
btnComputeHashes = false;
|
||||
|
||||
hashCRC32 = 0;
|
||||
hashMD5 = null;
|
||||
hashSHA1 = null;
|
||||
hashSHA256 = null;
|
||||
|
||||
base64Text = null;
|
||||
|
||||
framesCounter = 0;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
framesCounter++;
|
||||
|
||||
if (btnComputeHashes)
|
||||
{
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(textInput.ToString());
|
||||
int textInputLen = bytes.Length;
|
||||
|
||||
fixed (byte* textInputPtr = bytes)
|
||||
{
|
||||
int base64TextSize;
|
||||
|
||||
// Encode data to Base64 string (includes NULL terminator), memory must be MemFree()
|
||||
sbyte* base64 = EncodeDataBase64(textInputPtr, textInputLen, &base64TextSize);
|
||||
base64Text = (base64 != null) ? new string(base64) : null;
|
||||
MemFree(base64); // Free Base64 text data (kept managed above)
|
||||
|
||||
hashCRC32 = ComputeCRC32(textInputPtr, textInputLen); // Compute CRC32 hash code (4 bytes)
|
||||
hashMD5 = CopyHash(ComputeMD5(textInputPtr, textInputLen), 4); // Compute MD5 hash code, returns static int[4] (16 bytes)
|
||||
hashSHA1 = CopyHash(ComputeSHA1(textInputPtr, textInputLen), 5); // Compute SHA1 hash code, returns static int[5] (20 bytes)
|
||||
hashSHA256 = CopyHash(ComputeSHA256(textInputPtr, textInputLen), 8); // Compute SHA256 hash code, returns static int[8] (32 bytes)
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
GuiLabel(new Rectangle(40, 26, 720, 32), "INPUT DATA (TEXT):", 20);
|
||||
|
||||
if (GuiTextBox(new Rectangle(40, 64, 720, 32), textInput, 95, textBoxEditMode, 10)) textBoxEditMode = !textBoxEditMode;
|
||||
|
||||
btnComputeHashes = GuiButton(new Rectangle(40, 64 + 40, 720, 32), "COMPUTE INPUT DATA HASHES", 10);
|
||||
|
||||
GuiLabel(new Rectangle(40, 160, 720, 32), "INPUT DATA HASH VALUES:", 20);
|
||||
|
||||
GuiLabel(new Rectangle(40, 200, 120, 32), "CRC32 [32 bit]:", 10);
|
||||
GuiTextBoxReadOnly(new Rectangle(40 + 120, 200, 720 - 120, 32), GetDataAsHexText(new uint[] { hashCRC32 }, 1), 10);
|
||||
GuiLabel(new Rectangle(40, 200 + 36, 120, 32), "MD5 [128 bit]:", 10);
|
||||
GuiTextBoxReadOnly(new Rectangle(40 + 120, 200 + 36, 720 - 120, 32), GetDataAsHexText(hashMD5, 4), 10);
|
||||
GuiLabel(new Rectangle(40, 200 + 36 * 2, 120, 32), "SHA1 [160 bit]:", 10);
|
||||
GuiTextBoxReadOnly(new Rectangle(40 + 120, 200 + 36 * 2, 720 - 120, 32), GetDataAsHexText(hashSHA1, 5), 10);
|
||||
GuiLabel(new Rectangle(40, 200 + 36 * 3, 120, 32), "SHA256 [256 bit]:", 10);
|
||||
GuiTextBoxReadOnly(new Rectangle(40 + 120, 200 + 36 * 3, 720 - 120, 32), GetDataAsHexText(hashSHA256, 8), 10);
|
||||
|
||||
GuiLabel(new Rectangle(40, 200 + 36 * 5 - 30, 320, 32), "BONUS - BAS64 ENCODED STRING:", 10);
|
||||
GuiLabel(new Rectangle(40, 200 + 36 * 5, 120, 32), "BASE64 ENCODING:", 10);
|
||||
GuiTextBoxReadOnly(new Rectangle(40 + 120, 200 + 36 * 5, 720 - 120, 32), base64Text, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
private static uint[] CopyHash(uint* data, int count)
|
||||
{
|
||||
if (data == null) return null;
|
||||
|
||||
uint[] result = new uint[count];
|
||||
for (int i = 0; i < count; i++) result[i] = data[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string GetDataAsHexText(uint[] data, int dataSize)
|
||||
{
|
||||
if ((data != null) && (dataSize > 0) && (dataSize < ((128 / 8) - 1)))
|
||||
{
|
||||
StringBuilder text = new StringBuilder();
|
||||
for (int i = 0; i < dataSize; i++) text.Append(data[i].ToString("X8"));
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
return "00000000";
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Minimal raygui-like widgets (plain raylib re-implementation)
|
||||
//----------------------------------------------------------------------------------
|
||||
private static void GuiLabel(Rectangle bounds, string text, int fontSize)
|
||||
{
|
||||
DrawText(text, (int)bounds.X, (int)(bounds.Y + (bounds.Height - fontSize) / 2), fontSize, Color.DarkGray);
|
||||
}
|
||||
|
||||
private static bool GuiButton(Rectangle bounds, string text, int fontSize)
|
||||
{
|
||||
bool pressed = false;
|
||||
Vector2 mouse = GetMousePosition();
|
||||
bool hover = CheckCollisionPointRec(mouse, bounds);
|
||||
|
||||
Color fill = hover ? (IsMouseButtonDown(MouseButton.Left) ? Color.SkyBlue : Color.LightGray) : Color.RayWhite;
|
||||
DrawRectangleRec(bounds, fill);
|
||||
DrawRectangleLinesEx(bounds, 1, hover ? Color.Blue : Color.Gray);
|
||||
|
||||
int textWidth = MeasureText(text, fontSize);
|
||||
DrawText(text, (int)(bounds.X + (bounds.Width - textWidth) / 2), (int)(bounds.Y + (bounds.Height - fontSize) / 2), fontSize, Color.DarkGray);
|
||||
|
||||
if (hover && IsMouseButtonReleased(MouseButton.Left)) pressed = true;
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
private bool GuiTextBox(Rectangle bounds, StringBuilder text, int maxChars, bool editMode, int fontSize)
|
||||
{
|
||||
bool pressed = false;
|
||||
Vector2 mouse = GetMousePosition();
|
||||
bool hover = CheckCollisionPointRec(mouse, bounds);
|
||||
|
||||
if (editMode)
|
||||
{
|
||||
int key = GetCharPressed();
|
||||
while (key > 0)
|
||||
{
|
||||
if ((key >= 32) && (key <= 125) && (text.Length < maxChars - 1))
|
||||
{
|
||||
text.Append((char)key);
|
||||
}
|
||||
|
||||
key = GetCharPressed();
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Backspace) && (text.Length > 0)) text.Remove(text.Length - 1, 1);
|
||||
}
|
||||
|
||||
DrawRectangleRec(bounds, Color.RayWhite);
|
||||
DrawRectangleLinesEx(bounds, editMode ? 2 : 1, editMode ? Color.Red : (hover ? Color.Blue : Color.Gray));
|
||||
|
||||
string content = text.ToString();
|
||||
DrawText(content, (int)bounds.X + 4, (int)(bounds.Y + (bounds.Height - fontSize) / 2), fontSize, Color.DarkGray);
|
||||
|
||||
if (editMode && ((framesCounter / 20) % 2 == 0))
|
||||
{
|
||||
DrawText("_", (int)bounds.X + 4 + MeasureText(content, fontSize), (int)(bounds.Y + (bounds.Height - fontSize) / 2), fontSize, Color.DarkGray);
|
||||
}
|
||||
|
||||
if (hover && IsMouseButtonPressed(MouseButton.Left)) pressed = true;
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
private static void GuiTextBoxReadOnly(Rectangle bounds, string text, int fontSize)
|
||||
{
|
||||
DrawRectangleRec(bounds, Color.LightGray);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (text != null) DrawText(text, (int)bounds.X + 4, (int)(bounds.Y + (bounds.Height - fontSize) / 2), fontSize, Color.Gray);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - compute hash");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new ComputeHash();
|
||||
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;
|
||||
}
|
||||
}
|
||||
188
Examples/Core/CustomFrameControl.cs
Normal file
188
Examples/Core/CustomFrameControl.cs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - custom frame control
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* NOTE: WARNING: This is an example for advanced users willing to have full control over
|
||||
* the frame processes. By default, EndDrawing() calls the following processes:
|
||||
* 1. Draw remaining batch data: rlDrawRenderBatchActive()
|
||||
* 2. SwapScreenBuffer()
|
||||
* 3. Frame time control: WaitTime()
|
||||
* 4. PollInputEvents()
|
||||
*
|
||||
* To avoid steps 2, 3 and 4, flag SUPPORT_CUSTOM_FRAME_CONTROL can be enabled in
|
||||
* config.h (it requires recompiling raylib). This way those steps are up to the user
|
||||
*
|
||||
* Note that enabling this flag invalidates some functions:
|
||||
* - GetFrameTime()
|
||||
* - SetTargetFPS()
|
||||
* - GetFPS()
|
||||
*
|
||||
* Example originally created with raylib 4.0, last time updated with raylib 4.0
|
||||
*
|
||||
* 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) 2021-2025 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
// NOTE: This example is intended to run against a raylib built with SUPPORT_CUSTOM_FRAME_CONTROL,
|
||||
// where EndDrawing() does NOT swap buffers, wait or poll input, leaving those to the user code
|
||||
// below. The stock raylib-cs native library is built WITHOUT that flag, so EndDrawing() still
|
||||
// performs those steps and the manual SwapScreenBuffer()/WaitTime()/PollInputEvents() calls here
|
||||
// run in addition to them. The port is kept faithful to upstream regardless.
|
||||
[ExcludeFromBrowser("manual PollInputEvents/SwapScreenBuffer/WaitTime clashes with the emscripten main loop")]
|
||||
public partial class CustomFrameControl : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Custom Frame Control";
|
||||
|
||||
public string Title => "raylib [core] example - custom frame control";
|
||||
|
||||
// Custom timming variables
|
||||
private double previousTime; // Previous time measure
|
||||
private double currentTime; // Current time measure
|
||||
private double updateDrawTime; // Update + Draw time
|
||||
private double waitTime; // Wait time (if target fps required)
|
||||
private float deltaTime; // Frame time (Update + Draw + Wait time)
|
||||
|
||||
private float timeCounter; // Accumulative time counter (seconds)
|
||||
private float position; // Circle position
|
||||
private bool pause; // Pause control flag
|
||||
|
||||
private int targetFPS; // Our initial target fps
|
||||
|
||||
public void Init()
|
||||
{
|
||||
previousTime = GetTime();
|
||||
currentTime = 0.0;
|
||||
updateDrawTime = 0.0;
|
||||
waitTime = 0.0;
|
||||
deltaTime = 0.0f;
|
||||
|
||||
timeCounter = 0.0f;
|
||||
position = 0.0f;
|
||||
pause = false;
|
||||
|
||||
targetFPS = 60;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
#if !BROWSER
|
||||
// NOTE: On non web platforms the PollInputEvents just works before the inputs checks
|
||||
PollInputEvents(); // Poll input events (SUPPORT_CUSTOM_FRAME_CONTROL)
|
||||
#endif
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Space)) pause = !pause;
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Up)) targetFPS += 20;
|
||||
else if (IsKeyPressed(KeyboardKey.Down)) targetFPS -= 20;
|
||||
|
||||
if (targetFPS < 0) targetFPS = 0;
|
||||
|
||||
if (!pause)
|
||||
{
|
||||
position += 200 * deltaTime; // We move at 200 pixels per second
|
||||
if (position >= GetScreenWidth()) position = 0;
|
||||
timeCounter += deltaTime; // We count time (seconds)
|
||||
}
|
||||
|
||||
#if BROWSER
|
||||
// NOTE: On web platform for some reason the PollInputEvents only works after the inputs
|
||||
// check, so just call it after check all your inputs (on web)
|
||||
PollInputEvents(); // Poll input events (SUPPORT_CUSTOM_FRAME_CONTROL)
|
||||
#endif
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
for (int i = 0; i < GetScreenWidth() / 200; i++) DrawRectangle(200 * i, 0, 1, GetScreenHeight(), Color.SkyBlue);
|
||||
|
||||
DrawCircle((int)position, GetScreenHeight() / 2 - 25, 50, Color.Red);
|
||||
|
||||
DrawText($"{timeCounter * 1000.0f:000} ms", (int)position - 40, GetScreenHeight() / 2 - 100, 20, Color.Maroon);
|
||||
DrawText($"PosX: {position:000}", (int)position - 50, GetScreenHeight() / 2 + 40, 20, Color.Black);
|
||||
|
||||
DrawText("Circle is moving at a constant 200 pixels/sec,\nindependently of the frame rate.", 10, 10, 20, Color.DarkGray);
|
||||
DrawText("PRESS SPACE to PAUSE MOVEMENT", 10, GetScreenHeight() - 60, 20, Color.Gray);
|
||||
DrawText("PRESS UP | DOWN to CHANGE TARGET FPS", 10, GetScreenHeight() - 30, 20, Color.Gray);
|
||||
DrawText($"TARGET FPS: {targetFPS}", GetScreenWidth() - 220, 10, 20, Color.Lime);
|
||||
if (deltaTime != 0)
|
||||
{
|
||||
DrawText($"CURRENT FPS: {(int)(1.0f / deltaTime)}", GetScreenWidth() - 220, 40, 20, Color.Green);
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
|
||||
// NOTE: In case raylib is configured to SUPPORT_CUSTOM_FRAME_CONTROL,
|
||||
// Events polling, screen buffer swap and frame time control must be managed by the user
|
||||
|
||||
SwapScreenBuffer(); // Flip the back buffer to screen (front buffer)
|
||||
|
||||
currentTime = GetTime();
|
||||
updateDrawTime = currentTime - previousTime;
|
||||
|
||||
if (targetFPS > 0) // We want a fixed frame rate
|
||||
{
|
||||
waitTime = (1.0f / (float)targetFPS) - updateDrawTime;
|
||||
if (waitTime > 0.0)
|
||||
{
|
||||
WaitTime((float)waitTime);
|
||||
currentTime = GetTime();
|
||||
deltaTime = (float)(currentTime - previousTime);
|
||||
}
|
||||
}
|
||||
else deltaTime = (float)updateDrawTime; // Framerate could be variable
|
||||
|
||||
previousTime = currentTime;
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - custom frame control");
|
||||
|
||||
// NOTE: Not calling SetTargetFPS(): this example manages frame timing manually with
|
||||
// WaitTime() (SetTargetFPS/GetFrameTime/GetFPS are invalidated by SUPPORT_CUSTOM_FRAME_CONTROL)
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new CustomFrameControl();
|
||||
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;
|
||||
}
|
||||
}
|
||||
205
Examples/Core/DirectoryFiles.cs
Normal file
205
Examples/Core/DirectoryFiles.cs
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - directory files
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.6
|
||||
*
|
||||
* Example contributed by Hugo ARNAL (@hugoarnal) 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 Hugo ARNAL (@hugoarnal)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
// NOTE: The original example relies on raygui (GuiButton, GuiLabel, GuiListViewEx) for its UI.
|
||||
// raygui is not part of the raylib-cs bindings, so the back button, directory label and file
|
||||
// list view are reimplemented here with plain raylib primitives. The directory navigation
|
||||
// behaviour (enter directories, go back) is preserved.
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class DirectoryFiles : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const string FileFilter = "DIRS*;.png;.c";
|
||||
|
||||
public string Name => "Core / Directory Files";
|
||||
|
||||
public string Title => "raylib [core] example - directory files";
|
||||
|
||||
private string directory;
|
||||
private FilePathList files;
|
||||
|
||||
private bool btnBackPressed;
|
||||
|
||||
private int listScrollIndex;
|
||||
private int listItemActive;
|
||||
private int listItemFocused;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
directory = GetWorkingDirectoryAsString();
|
||||
|
||||
// Load file-paths on current working directory
|
||||
// NOTE: LoadDirectoryFiles() loads files and directories by default,
|
||||
// use LoadDirectoryFilesEx() for custom filters and recursive directories loading
|
||||
//files = LoadDirectoryFiles(directory);
|
||||
files = LoadDirectoryFilesEx(directory, FileFilter, false);
|
||||
|
||||
btnBackPressed = false;
|
||||
|
||||
listScrollIndex = 0;
|
||||
listItemActive = -1;
|
||||
listItemFocused = -1;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (btnBackPressed)
|
||||
{
|
||||
directory = GetPrevDirectoryPath(directory);
|
||||
UnloadDirectoryFiles(files);
|
||||
files = LoadDirectoryFilesEx(directory, FileFilter, false);
|
||||
|
||||
listScrollIndex = 0;
|
||||
listItemActive = -1;
|
||||
listItemFocused = -1;
|
||||
}
|
||||
|
||||
if ((listItemActive >= 0) && (listItemActive < (int)files.Count))
|
||||
{
|
||||
string selected = files[(uint)listItemActive];
|
||||
bool isDirectory = DirectoryExists(selected);
|
||||
if (isDirectory)
|
||||
{
|
||||
directory = selected;
|
||||
UnloadDirectoryFiles(files);
|
||||
files = LoadDirectoryFilesEx(directory, FileFilter, false);
|
||||
|
||||
listScrollIndex = 0;
|
||||
listItemActive = -1;
|
||||
listItemFocused = -1;
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
Vector2 mouse = GetMousePosition();
|
||||
|
||||
// Back button "<"
|
||||
Rectangle backBounds = new Rectangle(40.0f, 10.0f, 48, 28);
|
||||
bool backHover = CheckCollisionPointRec(mouse, backBounds);
|
||||
DrawRectangleRec(backBounds, backHover ? Color.SkyBlue : Color.LightGray);
|
||||
DrawRectangleLinesEx(backBounds, 1, Color.Gray);
|
||||
int backTextWidth = MeasureText("<", 20);
|
||||
DrawText("<", (int)(backBounds.X + (backBounds.Width - backTextWidth) / 2), (int)(backBounds.Y + 4), 20, Color.DarkGray);
|
||||
btnBackPressed = backHover && IsMouseButtonReleased(MouseButton.Left);
|
||||
|
||||
// Current directory label
|
||||
DrawText(directory, 40 + 48 + 10, 16, 20, Color.DarkGray);
|
||||
|
||||
// File list view
|
||||
Rectangle listBounds = new Rectangle(0, 50, GetScreenWidth(), GetScreenHeight() - 50);
|
||||
DrawRectangleRec(listBounds, Color.RayWhite);
|
||||
DrawRectangleLinesEx(listBounds, 1, Color.Gray);
|
||||
|
||||
int count = (int)files.Count;
|
||||
float rowHeight = 28;
|
||||
int visibleRows = (int)(listBounds.Height / rowHeight);
|
||||
|
||||
bool mouseInList = CheckCollisionPointRec(mouse, listBounds);
|
||||
if (mouseInList)
|
||||
{
|
||||
listScrollIndex -= (int)GetMouseWheelMove();
|
||||
}
|
||||
int maxScroll = Math.Max(0, count - visibleRows);
|
||||
listScrollIndex = Math.Clamp(listScrollIndex, 0, maxScroll);
|
||||
|
||||
listItemFocused = -1;
|
||||
for (int i = 0; i < visibleRows; i++)
|
||||
{
|
||||
int itemIndex = listScrollIndex + i;
|
||||
if (itemIndex >= count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Rectangle rowRec = new Rectangle(listBounds.X + 1, listBounds.Y + 1 + i * rowHeight, listBounds.Width - 2, rowHeight);
|
||||
bool rowHover = mouseInList && CheckCollisionPointRec(mouse, rowRec);
|
||||
|
||||
if (itemIndex == listItemActive)
|
||||
{
|
||||
DrawRectangleRec(rowRec, Fade(Color.SkyBlue, 0.7f));
|
||||
}
|
||||
else if (rowHover)
|
||||
{
|
||||
DrawRectangleRec(rowRec, Fade(Color.SkyBlue, 0.3f));
|
||||
}
|
||||
|
||||
if (rowHover)
|
||||
{
|
||||
listItemFocused = itemIndex;
|
||||
if (IsMouseButtonReleased(MouseButton.Left))
|
||||
{
|
||||
listItemActive = itemIndex;
|
||||
}
|
||||
}
|
||||
|
||||
DrawText(files[(uint)itemIndex], (int)rowRec.X + 40, (int)rowRec.Y + 8, 10, Color.DarkGray);
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
UnloadDirectoryFiles(files);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - directory files");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new DirectoryFiles();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ using static Raylib_cs.Raylib;
|
|||
|
||||
namespace Examples.Core;
|
||||
|
||||
[ExcludeFromBrowser]
|
||||
public partial class DropFiles : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
|
|
|
|||
173
Examples/Core/HighDpiDemo.cs
Normal file
173
Examples/Core/HighDpiDemo.cs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - highdpi demo
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.0, last time updated with raylib 5.5
|
||||
*
|
||||
* Example contributed by Jonathan Marler (@marler8997) 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 Jonathan Marler (@marler8997)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
[ExcludeFromBrowser("monitor/DPI APIs are meaningless on the fixed wasm canvas")]
|
||||
public partial class HighDpiDemo : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / High DPI Demo";
|
||||
|
||||
public string Title => "raylib [core] example - highdpi demo";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.HighDpiWindow | ConfigFlags.ResizableWindow;
|
||||
|
||||
private int logicalGridDescY;
|
||||
private int logicalGridLabelY;
|
||||
private int logicalGridTop;
|
||||
private int logicalGridBottom;
|
||||
private int pixelGridTop;
|
||||
private int pixelGridBottom;
|
||||
private int pixelGridLabelY;
|
||||
private int pixelGridDescY;
|
||||
private int cellSize;
|
||||
private float cellSizePx;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
SetWindowMinSize(450, 450);
|
||||
|
||||
logicalGridDescY = 120;
|
||||
logicalGridLabelY = logicalGridDescY + 30;
|
||||
logicalGridTop = logicalGridLabelY + 30;
|
||||
logicalGridBottom = logicalGridTop + 80;
|
||||
pixelGridTop = logicalGridBottom - 20;
|
||||
pixelGridBottom = pixelGridTop + 80;
|
||||
pixelGridLabelY = pixelGridBottom + 30;
|
||||
pixelGridDescY = pixelGridLabelY + 30;
|
||||
cellSize = 50;
|
||||
cellSizePx = (float)cellSize;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
int monitorCount = GetMonitorCount();
|
||||
|
||||
if ((monitorCount > 1) && IsKeyPressed(KeyboardKey.N))
|
||||
{
|
||||
SetWindowMonitor((GetCurrentMonitor() + 1) % monitorCount);
|
||||
}
|
||||
|
||||
int currentMonitor = GetCurrentMonitor();
|
||||
Vector2 dpiScale = GetWindowScaleDPI();
|
||||
cellSizePx = ((float)cellSize) / dpiScale.X;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
int windowCenter = GetScreenWidth() / 2;
|
||||
DrawTextCenter($"Dpi Scale: {dpiScale.X:F6}", windowCenter, 30, 40, Color.DarkGray);
|
||||
DrawTextCenter($"Monitor: {currentMonitor + 1}/{monitorCount} ([N] next monitor)", windowCenter, 70, 20, Color.LightGray);
|
||||
DrawTextCenter($"Window is {GetScreenWidth()} \"logical points\" wide", windowCenter, logicalGridDescY, 20, Color.Orange);
|
||||
|
||||
bool odd = true;
|
||||
for (int i = cellSize; i < GetScreenWidth(); i += cellSize, odd = !odd)
|
||||
{
|
||||
if (odd)
|
||||
{
|
||||
DrawRectangle(i, logicalGridTop, cellSize, logicalGridBottom - logicalGridTop, Color.Orange);
|
||||
}
|
||||
|
||||
DrawTextCenter($"{i}", i, logicalGridLabelY, 10, Color.LightGray);
|
||||
DrawLine(i, logicalGridLabelY + 10, i, logicalGridBottom, Color.Gray);
|
||||
}
|
||||
|
||||
odd = true;
|
||||
const int minTextSpace = 30;
|
||||
int lastTextX = -minTextSpace;
|
||||
for (int i = cellSize; i < GetRenderWidth(); i += cellSize, odd = !odd)
|
||||
{
|
||||
int x = (int)(((float)i) / dpiScale.X);
|
||||
if (odd)
|
||||
{
|
||||
DrawRectangle(x, pixelGridTop, (int)cellSizePx, pixelGridBottom - pixelGridTop, new Color(0, 121, 241, 100));
|
||||
}
|
||||
|
||||
DrawLine(x, pixelGridTop, (int)(((float)i) / dpiScale.X), pixelGridLabelY - 10, Color.Gray);
|
||||
|
||||
if ((x - lastTextX) >= minTextSpace)
|
||||
{
|
||||
DrawTextCenter($"{i}", x, pixelGridLabelY, 10, Color.LightGray);
|
||||
lastTextX = x;
|
||||
}
|
||||
}
|
||||
|
||||
DrawTextCenter($"Window is {GetRenderWidth()} \"physical pixels\" wide", windowCenter, pixelGridDescY, 20, Color.Blue);
|
||||
|
||||
string text = "Can you see this?";
|
||||
Vector2 size = MeasureTextEx(GetFontDefault(), text, 20, 3);
|
||||
Vector2 pos = new Vector2(GetScreenWidth() - size.X - 5, GetScreenHeight() - size.Y - 5);
|
||||
DrawTextEx(GetFontDefault(), text, pos, 20, 3, Color.LightGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
// Draw text centered on the given position
|
||||
private static void DrawTextCenter(string text, int x, int y, int fontSize, Color color)
|
||||
{
|
||||
Vector2 size = MeasureTextEx(GetFontDefault(), text, (float)fontSize, 3);
|
||||
Vector2 pos = new Vector2(x - size.X / 2, y - size.Y / 2);
|
||||
DrawTextEx(GetFontDefault(), text, pos, (float)fontSize, 3, color);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.HighDpiWindow | ConfigFlags.ResizableWindow);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - highdpi demo");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new HighDpiDemo();
|
||||
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;
|
||||
}
|
||||
}
|
||||
145
Examples/Core/HighDpiTestbed.cs
Normal file
145
Examples/Core/HighDpiTestbed.cs
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - highdpi testbed
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Ramon Santamaria (@raysan5) 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 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
[ExcludeFromBrowser("fullscreen/borderless/monitor APIs are meaningless on the fixed wasm canvas")]
|
||||
public partial class HighDpiTestbed : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / High DPI Testbed";
|
||||
|
||||
public string Title => "raylib [core] example - highdpi testbed";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.ResizableWindow | ConfigFlags.HighDpiWindow;
|
||||
|
||||
private Vector2 scaleDpi;
|
||||
private Vector2 mousePos;
|
||||
private int currentMonitor;
|
||||
private Vector2 windowPos;
|
||||
|
||||
private int gridSpacing; // Grid spacing in pixels
|
||||
|
||||
public void Init()
|
||||
{
|
||||
scaleDpi = GetWindowScaleDPI();
|
||||
mousePos = GetMousePosition();
|
||||
currentMonitor = GetCurrentMonitor();
|
||||
windowPos = GetWindowPosition();
|
||||
|
||||
gridSpacing = 40;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
mousePos = GetMousePosition();
|
||||
currentMonitor = GetCurrentMonitor();
|
||||
scaleDpi = GetWindowScaleDPI();
|
||||
windowPos = GetWindowPosition();
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Space))
|
||||
{
|
||||
ToggleBorderlessWindowed();
|
||||
}
|
||||
if (IsKeyPressed(KeyboardKey.F))
|
||||
{
|
||||
ToggleFullscreen();
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw grid
|
||||
for (int h = 0; h < GetScreenHeight() / gridSpacing + 1; h++)
|
||||
{
|
||||
DrawText($"{h * gridSpacing:D2}", 4, h * gridSpacing - 4, 10, Color.Gray);
|
||||
DrawLine(24, h * gridSpacing, GetScreenWidth(), h * gridSpacing, Color.LightGray);
|
||||
}
|
||||
for (int v = 0; v < GetScreenWidth() / gridSpacing + 1; v++)
|
||||
{
|
||||
DrawText($"{v * gridSpacing:D2}", v * gridSpacing - 10, 4, 10, Color.Gray);
|
||||
DrawLine(v * gridSpacing, 20, v * gridSpacing, GetScreenHeight(), Color.LightGray);
|
||||
}
|
||||
|
||||
// Draw UI info
|
||||
DrawText($"CURRENT MONITOR: {currentMonitor + 1}/{GetMonitorCount()} ({GetMonitorWidth(currentMonitor)}x{GetMonitorHeight(currentMonitor)})", 50, 50, 20, Color.DarkGray);
|
||||
DrawText($"WINDOW POSITION: {(int)windowPos.X}x{(int)windowPos.Y}", 50, 90, 20, Color.DarkGray);
|
||||
DrawText($"SCREEN SIZE: {GetScreenWidth()}x{GetScreenHeight()}", 50, 130, 20, Color.DarkGray);
|
||||
DrawText($"RENDER SIZE: {GetRenderWidth()}x{GetRenderHeight()}", 50, 170, 20, Color.DarkGray);
|
||||
DrawText($"SCALE FACTOR: {scaleDpi.X:F2}x{scaleDpi.Y:F2}", 50, 210, 20, Color.Gray);
|
||||
|
||||
// Draw reference rectangles, top-left and bottom-right corners
|
||||
DrawRectangle(0, 0, 30, 60, Color.Red);
|
||||
DrawRectangle(GetScreenWidth() - 30, GetScreenHeight() - 60, 30, 60, Color.Blue);
|
||||
|
||||
// Draw mouse position
|
||||
DrawCircleV(GetMousePosition(), 20, Color.Maroon);
|
||||
DrawRectangleRec(new Rectangle(mousePos.X - 25, mousePos.Y, 50, 2), Color.Black);
|
||||
DrawRectangleRec(new Rectangle(mousePos.X, mousePos.Y - 25, 2, 50), Color.Black);
|
||||
DrawText($"[{GetMouseX()},{GetMouseY()}]", (int)mousePos.X - 44,
|
||||
(mousePos.Y > GetScreenHeight() - 60) ? (int)mousePos.Y - 46 : (int)mousePos.Y + 30, 20, Color.Black);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
// TODO: Unload all loaded resources at this point
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.ResizableWindow | ConfigFlags.HighDpiWindow);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - highdpi testbed");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new HighDpiTestbed();
|
||||
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;
|
||||
}
|
||||
}
|
||||
224
Examples/Core/InputActions.cs
Normal file
224
Examples/Core/InputActions.cs
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - input actions
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.6
|
||||
*
|
||||
* Example contributed by Jett (@JettMonstersGoBoom) 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 Jett (@JettMonstersGoBoom)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
// Simple example for decoding input as actions, allowing remapping of input to different keys or gamepad buttons
|
||||
// For example instead of using `IsKeyDown(KEY_LEFT)`, you can use `IsActionDown(ACTION_LEFT)`
|
||||
// which can be reassigned to e.g. KEY_A and also assigned to a gamepad button. the action will trigger with either gamepad or keys
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class InputActions : IExample
|
||||
{
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
private enum ActionType
|
||||
{
|
||||
NoAction = 0,
|
||||
ActionUp,
|
||||
ActionDown,
|
||||
ActionLeft,
|
||||
ActionRight,
|
||||
ActionFire,
|
||||
MaxAction
|
||||
}
|
||||
|
||||
// Key and button inputs
|
||||
private struct ActionInput
|
||||
{
|
||||
public KeyboardKey Key;
|
||||
public GamepadButton Button;
|
||||
}
|
||||
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Input Actions";
|
||||
|
||||
public string Title => "raylib [core] example - input actions";
|
||||
|
||||
private int gamepadIndex; // Gamepad default index
|
||||
private ActionInput[] actionInputs;
|
||||
|
||||
private int actionSet;
|
||||
private bool releaseAction;
|
||||
private Vector2 position;
|
||||
private Vector2 size;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
gamepadIndex = 0;
|
||||
actionInputs = new ActionInput[(int)ActionType.MaxAction];
|
||||
|
||||
// Set default actions
|
||||
actionSet = 0;
|
||||
SetActionsDefault();
|
||||
releaseAction = false;
|
||||
|
||||
position = new Vector2(400.0f, 200.0f);
|
||||
size = new Vector2(40.0f, 40.0f);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
gamepadIndex = 0; // Set gamepad being checked
|
||||
|
||||
if (IsActionDown(ActionType.ActionUp)) position.Y -= 2;
|
||||
if (IsActionDown(ActionType.ActionDown)) position.Y += 2;
|
||||
if (IsActionDown(ActionType.ActionLeft)) position.X -= 2;
|
||||
if (IsActionDown(ActionType.ActionRight)) position.X += 2;
|
||||
if (IsActionPressed(ActionType.ActionFire))
|
||||
{
|
||||
position.X = (screenWidth - size.X) / 2;
|
||||
position.Y = (screenHeight - size.Y) / 2;
|
||||
}
|
||||
|
||||
// Register release action for one frame
|
||||
releaseAction = false;
|
||||
if (IsActionReleased(ActionType.ActionFire)) releaseAction = true;
|
||||
|
||||
// Switch control scheme by pressing TAB
|
||||
if (IsKeyPressed(KeyboardKey.Tab))
|
||||
{
|
||||
actionSet = (actionSet == 0) ? 1 : 0;
|
||||
if (actionSet == 0) SetActionsDefault();
|
||||
else SetActionsCursor();
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.Gray);
|
||||
|
||||
DrawRectangleV(position, size, releaseAction ? Color.Blue : Color.Red);
|
||||
|
||||
DrawText((actionSet == 0) ? "Current input set: WASD (default)" : "Current input set: Arrow keys", 10, 10, 20, Color.White);
|
||||
DrawText("Use TAB key to toggles Actions keyset", 10, 50, 20, Color.Green);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// Check action key/button pressed
|
||||
// NOTE: Combines key pressed and gamepad button pressed in one action
|
||||
private bool IsActionPressed(ActionType action)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (action < ActionType.MaxAction) result = (IsKeyPressed(actionInputs[(int)action].Key) || IsGamepadButtonPressed(gamepadIndex, actionInputs[(int)action].Button));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check action key/button released
|
||||
// NOTE: Combines key released and gamepad button released in one action
|
||||
private bool IsActionReleased(ActionType action)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (action < ActionType.MaxAction) result = (IsKeyReleased(actionInputs[(int)action].Key) || IsGamepadButtonReleased(gamepadIndex, actionInputs[(int)action].Button));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check action key/button down
|
||||
// NOTE: Combines key down and gamepad button down in one action
|
||||
private bool IsActionDown(ActionType action)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
if (action < ActionType.MaxAction) result = (IsKeyDown(actionInputs[(int)action].Key) || IsGamepadButtonDown(gamepadIndex, actionInputs[(int)action].Button));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Set the "default" keyset
|
||||
// NOTE: Here WASD and gamepad buttons on the left side for movement
|
||||
private void SetActionsDefault()
|
||||
{
|
||||
actionInputs[(int)ActionType.ActionUp].Key = KeyboardKey.W;
|
||||
actionInputs[(int)ActionType.ActionDown].Key = KeyboardKey.S;
|
||||
actionInputs[(int)ActionType.ActionLeft].Key = KeyboardKey.A;
|
||||
actionInputs[(int)ActionType.ActionRight].Key = KeyboardKey.D;
|
||||
actionInputs[(int)ActionType.ActionFire].Key = KeyboardKey.Space;
|
||||
|
||||
actionInputs[(int)ActionType.ActionUp].Button = GamepadButton.LeftFaceUp;
|
||||
actionInputs[(int)ActionType.ActionDown].Button = GamepadButton.LeftFaceDown;
|
||||
actionInputs[(int)ActionType.ActionLeft].Button = GamepadButton.LeftFaceLeft;
|
||||
actionInputs[(int)ActionType.ActionRight].Button = GamepadButton.LeftFaceRight;
|
||||
actionInputs[(int)ActionType.ActionFire].Button = GamepadButton.RightFaceDown;
|
||||
}
|
||||
|
||||
// Set the "alternate" keyset
|
||||
// NOTE: Here cursor keys and gamepad buttons on the right side for movement
|
||||
private void SetActionsCursor()
|
||||
{
|
||||
actionInputs[(int)ActionType.ActionUp].Key = KeyboardKey.Up;
|
||||
actionInputs[(int)ActionType.ActionDown].Key = KeyboardKey.Down;
|
||||
actionInputs[(int)ActionType.ActionLeft].Key = KeyboardKey.Left;
|
||||
actionInputs[(int)ActionType.ActionRight].Key = KeyboardKey.Right;
|
||||
actionInputs[(int)ActionType.ActionFire].Key = KeyboardKey.Space;
|
||||
|
||||
actionInputs[(int)ActionType.ActionUp].Button = GamepadButton.RightFaceUp;
|
||||
actionInputs[(int)ActionType.ActionDown].Button = GamepadButton.RightFaceDown;
|
||||
actionInputs[(int)ActionType.ActionLeft].Button = GamepadButton.RightFaceLeft;
|
||||
actionInputs[(int)ActionType.ActionRight].Button = GamepadButton.RightFaceRight;
|
||||
actionInputs[(int)ActionType.ActionFire].Button = GamepadButton.LeftFaceDown;
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - input actions");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new InputActions();
|
||||
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;
|
||||
}
|
||||
}
|
||||
374
Examples/Core/KeyboardTestbed.cs
Normal file
374
Examples/Core/KeyboardTestbed.cs
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - keyboard testbed
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* NOTE: raylib defined keys refer to ENG-US Keyboard layout,
|
||||
* mapping to other layouts is up to the user
|
||||
*
|
||||
* Example originally created with raylib 5.6, last time updated with raylib 5.6
|
||||
*
|
||||
* 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) 2026 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class KeyboardTestbed : IExample
|
||||
{
|
||||
private const int KeyRecSpacing = 4; // Space in pixels between key rectangles
|
||||
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Keyboard Testbed";
|
||||
|
||||
public string Title => "raylib [core] example - keyboard testbed";
|
||||
|
||||
private int[] line01KeyWidths;
|
||||
private int[] line01Keys;
|
||||
private int[] line02KeyWidths;
|
||||
private int[] line02Keys;
|
||||
private int[] line03KeyWidths;
|
||||
private int[] line03Keys;
|
||||
private int[] line04KeyWidths;
|
||||
private int[] line04Keys;
|
||||
private int[] line05KeyWidths;
|
||||
private int[] line05Keys;
|
||||
private int[] line06KeyWidths;
|
||||
private int[] line06Keys;
|
||||
|
||||
private Vector2 keyboardOffset;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
SetExitKey(KeyboardKey.Null); // Avoid exit on KEY_ESCAPE
|
||||
|
||||
// Keyboard line 01
|
||||
line01KeyWidths = new int[15];
|
||||
for (int i = 0; i < 15; i++) line01KeyWidths[i] = 45;
|
||||
line01KeyWidths[13] = 62; // PRINTSCREEN
|
||||
line01Keys = new int[]
|
||||
{
|
||||
(int)KeyboardKey.Escape, (int)KeyboardKey.F1, (int)KeyboardKey.F2, (int)KeyboardKey.F3, (int)KeyboardKey.F4, (int)KeyboardKey.F5,
|
||||
(int)KeyboardKey.F6, (int)KeyboardKey.F7, (int)KeyboardKey.F8, (int)KeyboardKey.F9, (int)KeyboardKey.F10, (int)KeyboardKey.F11,
|
||||
(int)KeyboardKey.F12, (int)KeyboardKey.PrintScreen, (int)KeyboardKey.Pause
|
||||
};
|
||||
|
||||
// Keyboard line 02
|
||||
line02KeyWidths = new int[15];
|
||||
for (int i = 0; i < 15; i++) line02KeyWidths[i] = 45;
|
||||
line02KeyWidths[0] = 25; // GRAVE
|
||||
line02KeyWidths[13] = 82; // BACKSPACE
|
||||
line02Keys = new int[]
|
||||
{
|
||||
(int)KeyboardKey.Grave, (int)KeyboardKey.One, (int)KeyboardKey.Two, (int)KeyboardKey.Three, (int)KeyboardKey.Four,
|
||||
(int)KeyboardKey.Five, (int)KeyboardKey.Six, (int)KeyboardKey.Seven, (int)KeyboardKey.Eight, (int)KeyboardKey.Nine,
|
||||
(int)KeyboardKey.Zero, (int)KeyboardKey.Minus, (int)KeyboardKey.Equal, (int)KeyboardKey.Backspace, (int)KeyboardKey.Delete
|
||||
};
|
||||
|
||||
// Keyboard line 03
|
||||
line03KeyWidths = new int[15];
|
||||
for (int i = 0; i < 15; i++) line03KeyWidths[i] = 45;
|
||||
line03KeyWidths[0] = 50; // TAB
|
||||
line03KeyWidths[13] = 57; // BACKSLASH
|
||||
line03Keys = new int[]
|
||||
{
|
||||
(int)KeyboardKey.Tab, (int)KeyboardKey.Q, (int)KeyboardKey.W, (int)KeyboardKey.E, (int)KeyboardKey.R, (int)KeyboardKey.T, (int)KeyboardKey.Y,
|
||||
(int)KeyboardKey.U, (int)KeyboardKey.I, (int)KeyboardKey.O, (int)KeyboardKey.P, (int)KeyboardKey.LeftBracket,
|
||||
(int)KeyboardKey.RightBracket, (int)KeyboardKey.Backslash, (int)KeyboardKey.Insert
|
||||
};
|
||||
|
||||
// Keyboard line 04
|
||||
line04KeyWidths = new int[14];
|
||||
for (int i = 0; i < 14; i++) line04KeyWidths[i] = 45;
|
||||
line04KeyWidths[0] = 68; // CAPS
|
||||
line04KeyWidths[12] = 88; // ENTER
|
||||
line04Keys = new int[]
|
||||
{
|
||||
(int)KeyboardKey.CapsLock, (int)KeyboardKey.A, (int)KeyboardKey.S, (int)KeyboardKey.D, (int)KeyboardKey.F, (int)KeyboardKey.G,
|
||||
(int)KeyboardKey.H, (int)KeyboardKey.J, (int)KeyboardKey.K, (int)KeyboardKey.L, (int)KeyboardKey.Semicolon,
|
||||
(int)KeyboardKey.Apostrophe, (int)KeyboardKey.Enter, (int)KeyboardKey.PageUp
|
||||
};
|
||||
|
||||
// Keyboard line 05
|
||||
line05KeyWidths = new int[14];
|
||||
for (int i = 0; i < 14; i++) line05KeyWidths[i] = 45;
|
||||
line05KeyWidths[0] = 80; // LSHIFT
|
||||
line05KeyWidths[11] = 76; // RSHIFT
|
||||
line05Keys = new int[]
|
||||
{
|
||||
(int)KeyboardKey.LeftShift, (int)KeyboardKey.Z, (int)KeyboardKey.X, (int)KeyboardKey.C, (int)KeyboardKey.V, (int)KeyboardKey.B,
|
||||
(int)KeyboardKey.N, (int)KeyboardKey.M, (int)KeyboardKey.Comma, (int)KeyboardKey.Period, /*KEY_MINUS*/
|
||||
(int)KeyboardKey.Slash, (int)KeyboardKey.RightShift, (int)KeyboardKey.Up, (int)KeyboardKey.PageDown
|
||||
};
|
||||
|
||||
// Keyboard line 06
|
||||
line06KeyWidths = new int[11];
|
||||
for (int i = 0; i < 11; i++) line06KeyWidths[i] = 45;
|
||||
line06KeyWidths[0] = 80; // LCTRL
|
||||
line06KeyWidths[3] = 208; // SPACE
|
||||
line06KeyWidths[7] = 60; // RCTRL
|
||||
line06Keys = new int[]
|
||||
{
|
||||
(int)KeyboardKey.LeftControl, (int)KeyboardKey.LeftSuper, (int)KeyboardKey.LeftAlt,
|
||||
(int)KeyboardKey.Space, (int)KeyboardKey.RightAlt, 162, (int)KeyboardKey.Null,
|
||||
(int)KeyboardKey.RightControl, (int)KeyboardKey.Left, (int)KeyboardKey.Down, (int)KeyboardKey.Right
|
||||
};
|
||||
|
||||
keyboardOffset = new Vector2(26, 80);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
int key = GetKeyPressed(); // Get pressed keycode
|
||||
if (key > 0) TraceLog(TraceLogLevel.Info, $"KEYBOARD TESTBED: KEY PRESSED: {key}");
|
||||
|
||||
int ch = GetCharPressed(); // Get pressed char for text input, using OS mapping
|
||||
if (ch > 0) TraceLog(TraceLogLevel.Info, $"KEYBOARD TESTBED: CHAR PRESSED: {(char)ch} ({ch})");
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
DrawText("KEYBOARD LAYOUT: ENG-US", 26, 38, 20, Color.LightGray);
|
||||
|
||||
// Keyboard line 01 - 15 keys
|
||||
// ESC, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, IMP, CLOSE
|
||||
for (int i = 0, recOffsetX = 0; i < 15; i++)
|
||||
{
|
||||
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y, (float)line01KeyWidths[i], 30.0f), line01Keys[i]);
|
||||
recOffsetX += line01KeyWidths[i] + KeyRecSpacing;
|
||||
}
|
||||
|
||||
// Keyboard line 02 - 15 keys
|
||||
// `, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, -, =, BACKSPACE, DEL
|
||||
for (int i = 0, recOffsetX = 0; i < 15; i++)
|
||||
{
|
||||
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y + 30 + KeyRecSpacing, (float)line02KeyWidths[i], 38.0f), line02Keys[i]);
|
||||
recOffsetX += line02KeyWidths[i] + KeyRecSpacing;
|
||||
}
|
||||
|
||||
// Keyboard line 03 - 15 keys
|
||||
// TAB, Q, W, E, R, T, Y, U, I, O, P, [, ], \, INS
|
||||
for (int i = 0, recOffsetX = 0; i < 15; i++)
|
||||
{
|
||||
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y + 30 + 38 + KeyRecSpacing * 2, (float)line03KeyWidths[i], 38.0f), line03Keys[i]);
|
||||
recOffsetX += line03KeyWidths[i] + KeyRecSpacing;
|
||||
}
|
||||
|
||||
// Keyboard line 04 - 14 keys
|
||||
// MAYUS, A, S, D, F, G, H, J, K, L, ;, ', ENTER, REPAG
|
||||
for (int i = 0, recOffsetX = 0; i < 14; i++)
|
||||
{
|
||||
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y + 30 + 38 * 2 + KeyRecSpacing * 3, (float)line04KeyWidths[i], 38.0f), line04Keys[i]);
|
||||
recOffsetX += line04KeyWidths[i] + KeyRecSpacing;
|
||||
}
|
||||
|
||||
// Keyboard line 05 - 14 keys
|
||||
// LSHIFT, Z, X, C, V, B, N, M, ,, ., /, RSHIFT, UP, AVPAG
|
||||
for (int i = 0, recOffsetX = 0; i < 14; i++)
|
||||
{
|
||||
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y + 30 + 38 * 3 + KeyRecSpacing * 4, (float)line05KeyWidths[i], 38.0f), line05Keys[i]);
|
||||
recOffsetX += line05KeyWidths[i] + KeyRecSpacing;
|
||||
}
|
||||
|
||||
// Keyboard line 06 - 11 keys
|
||||
// LCTRL, WIN, LALT, SPACE, ALTGR, \, FN, RCTRL, LEFT, DOWN, RIGHT
|
||||
for (int i = 0, recOffsetX = 0; i < 11; i++)
|
||||
{
|
||||
GuiKeyboardKey(new Rectangle(keyboardOffset.X + recOffsetX, keyboardOffset.Y + 30 + 38 * 4 + KeyRecSpacing * 5, (float)line06KeyWidths[i], 38.0f), line06Keys[i]);
|
||||
recOffsetX += line06KeyWidths[i] + KeyRecSpacing;
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//------------------------------------------------------------------------------------
|
||||
// Get keyboard keycode as text (US keyboard)
|
||||
// NOTE: Mapping for other keyboard layouts can be done here
|
||||
private static string GetKeyText(int key)
|
||||
{
|
||||
switch ((KeyboardKey)key)
|
||||
{
|
||||
case KeyboardKey.Apostrophe: return "'"; // Key: '
|
||||
case KeyboardKey.Comma: return ","; // Key: ,
|
||||
case KeyboardKey.Minus: return "-"; // Key: -
|
||||
case KeyboardKey.Period: return "."; // Key: .
|
||||
case KeyboardKey.Slash: return "/"; // Key: /
|
||||
case KeyboardKey.Zero: return "0"; // Key: 0
|
||||
case KeyboardKey.One: return "1"; // Key: 1
|
||||
case KeyboardKey.Two: return "2"; // Key: 2
|
||||
case KeyboardKey.Three: return "3"; // Key: 3
|
||||
case KeyboardKey.Four: return "4"; // Key: 4
|
||||
case KeyboardKey.Five: return "5"; // Key: 5
|
||||
case KeyboardKey.Six: return "6"; // Key: 6
|
||||
case KeyboardKey.Seven: return "7"; // Key: 7
|
||||
case KeyboardKey.Eight: return "8"; // Key: 8
|
||||
case KeyboardKey.Nine: return "9"; // Key: 9
|
||||
case KeyboardKey.Semicolon: return ";"; // Key: ;
|
||||
case KeyboardKey.Equal: return "="; // Key: =
|
||||
case KeyboardKey.A: return "A"; // Key: A | a
|
||||
case KeyboardKey.B: return "B"; // Key: B | b
|
||||
case KeyboardKey.C: return "C"; // Key: C | c
|
||||
case KeyboardKey.D: return "D"; // Key: D | d
|
||||
case KeyboardKey.E: return "E"; // Key: E | e
|
||||
case KeyboardKey.F: return "F"; // Key: F | f
|
||||
case KeyboardKey.G: return "G"; // Key: G | g
|
||||
case KeyboardKey.H: return "H"; // Key: H | h
|
||||
case KeyboardKey.I: return "I"; // Key: I | i
|
||||
case KeyboardKey.J: return "J"; // Key: J | j
|
||||
case KeyboardKey.K: return "K"; // Key: K | k
|
||||
case KeyboardKey.L: return "L"; // Key: L | l
|
||||
case KeyboardKey.M: return "M"; // Key: M | m
|
||||
case KeyboardKey.N: return "N"; // Key: N | n
|
||||
case KeyboardKey.O: return "O"; // Key: O | o
|
||||
case KeyboardKey.P: return "P"; // Key: P | p
|
||||
case KeyboardKey.Q: return "Q"; // Key: Q | q
|
||||
case KeyboardKey.R: return "R"; // Key: R | r
|
||||
case KeyboardKey.S: return "S"; // Key: S | s
|
||||
case KeyboardKey.T: return "T"; // Key: T | t
|
||||
case KeyboardKey.U: return "U"; // Key: U | u
|
||||
case KeyboardKey.V: return "V"; // Key: V | v
|
||||
case KeyboardKey.W: return "W"; // Key: W | w
|
||||
case KeyboardKey.X: return "X"; // Key: X | x
|
||||
case KeyboardKey.Y: return "Y"; // Key: Y | y
|
||||
case KeyboardKey.Z: return "Z"; // Key: Z | z
|
||||
case KeyboardKey.LeftBracket: return "["; // Key: [
|
||||
case KeyboardKey.Backslash: return "\\"; // Key: '\'
|
||||
case KeyboardKey.RightBracket: return "]"; // Key: ]
|
||||
case KeyboardKey.Grave: return "`"; // Key: `
|
||||
case KeyboardKey.Space: return "SPACE"; // Key: Space
|
||||
case KeyboardKey.Escape: return "ESC"; // Key: Esc
|
||||
case KeyboardKey.Enter: return "ENTER"; // Key: Enter
|
||||
case KeyboardKey.Tab: return "TAB"; // Key: Tab
|
||||
case KeyboardKey.Backspace: return "BACK"; // Key: Backspace
|
||||
case KeyboardKey.Insert: return "INS"; // Key: Ins
|
||||
case KeyboardKey.Delete: return "DEL"; // Key: Del
|
||||
case KeyboardKey.Right: return "RIGHT"; // Key: Cursor right
|
||||
case KeyboardKey.Left: return "LEFT"; // Key: Cursor left
|
||||
case KeyboardKey.Down: return "DOWN"; // Key: Cursor down
|
||||
case KeyboardKey.Up: return "UP"; // Key: Cursor up
|
||||
case KeyboardKey.PageUp: return "PGUP"; // Key: Page up
|
||||
case KeyboardKey.PageDown: return "PGDOWN"; // Key: Page down
|
||||
case KeyboardKey.Home: return "HOME"; // Key: Home
|
||||
case KeyboardKey.End: return "END"; // Key: End
|
||||
case KeyboardKey.CapsLock: return "CAPS"; // Key: Caps lock
|
||||
case KeyboardKey.ScrollLock: return "LOCK"; // Key: Scroll down
|
||||
case KeyboardKey.NumLock: return "NUMLOCK"; // Key: Num lock
|
||||
case KeyboardKey.PrintScreen: return "PRINTSCR"; // Key: Print screen
|
||||
case KeyboardKey.Pause: return "PAUSE"; // Key: Pause
|
||||
case KeyboardKey.F1: return "F1"; // Key: F1
|
||||
case KeyboardKey.F2: return "F2"; // Key: F2
|
||||
case KeyboardKey.F3: return "F3"; // Key: F3
|
||||
case KeyboardKey.F4: return "F4"; // Key: F4
|
||||
case KeyboardKey.F5: return "F5"; // Key: F5
|
||||
case KeyboardKey.F6: return "F6"; // Key: F6
|
||||
case KeyboardKey.F7: return "F7"; // Key: F7
|
||||
case KeyboardKey.F8: return "F8"; // Key: F8
|
||||
case KeyboardKey.F9: return "F9"; // Key: F9
|
||||
case KeyboardKey.F10: return "F10"; // Key: F10
|
||||
case KeyboardKey.F11: return "F11"; // Key: F11
|
||||
case KeyboardKey.F12: return "F12"; // Key: F12
|
||||
case KeyboardKey.LeftShift: return "LSHIFT"; // Key: Shift left
|
||||
case KeyboardKey.LeftControl: return "LCTRL"; // Key: Control left
|
||||
case KeyboardKey.LeftAlt: return "LALT"; // Key: Alt left
|
||||
case KeyboardKey.LeftSuper: return "WIN"; // Key: Super left
|
||||
case KeyboardKey.RightShift: return "RSHIFT"; // Key: Shift right
|
||||
case KeyboardKey.RightControl: return "RCTRL"; // Key: Control right
|
||||
case KeyboardKey.RightAlt: return "ALTGR"; // Key: Alt right
|
||||
case KeyboardKey.RightSuper: return "RSUPER"; // Key: Super right
|
||||
case KeyboardKey.KeyboardMenu: return "KBMENU"; // Key: KB menu
|
||||
case KeyboardKey.Kp0: return "KP0"; // Key: Keypad 0
|
||||
case KeyboardKey.Kp1: return "KP1"; // Key: Keypad 1
|
||||
case KeyboardKey.Kp2: return "KP2"; // Key: Keypad 2
|
||||
case KeyboardKey.Kp3: return "KP3"; // Key: Keypad 3
|
||||
case KeyboardKey.Kp4: return "KP4"; // Key: Keypad 4
|
||||
case KeyboardKey.Kp5: return "KP5"; // Key: Keypad 5
|
||||
case KeyboardKey.Kp6: return "KP6"; // Key: Keypad 6
|
||||
case KeyboardKey.Kp7: return "KP7"; // Key: Keypad 7
|
||||
case KeyboardKey.Kp8: return "KP8"; // Key: Keypad 8
|
||||
case KeyboardKey.Kp9: return "KP9"; // Key: Keypad 9
|
||||
case KeyboardKey.KpDecimal: return "KPDEC"; // Key: Keypad .
|
||||
case KeyboardKey.KpDivide: return "KPDIV"; // Key: Keypad /
|
||||
case KeyboardKey.KpMultiply: return "KPMUL"; // Key: Keypad *
|
||||
case KeyboardKey.KpSubtract: return "KPSUB"; // Key: Keypad -
|
||||
case KeyboardKey.KpAdd: return "KPADD"; // Key: Keypad +
|
||||
case KeyboardKey.KpEnter: return "KPENTER"; // Key: Keypad Enter
|
||||
case KeyboardKey.KpEqual: return "KPEQU"; // Key: Keypad =
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Draw keyboard key
|
||||
private static void GuiKeyboardKey(Rectangle bounds, int key)
|
||||
{
|
||||
if (key == (int)KeyboardKey.Null) DrawRectangleLinesEx(bounds, 2.0f, Color.LightGray);
|
||||
else
|
||||
{
|
||||
if (IsKeyDown((KeyboardKey)key))
|
||||
{
|
||||
DrawRectangleLinesEx(bounds, 2.0f, Color.Maroon);
|
||||
DrawText(GetKeyText(key), (int)(bounds.X + 4), (int)(bounds.Y + 4), 10, Color.Maroon);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawRectangleLinesEx(bounds, 2.0f, Color.DarkGray);
|
||||
DrawText(GetKeyText(key), (int)(bounds.X + 4), (int)(bounds.Y + 4), 10, Color.DarkGray);
|
||||
}
|
||||
}
|
||||
|
||||
if (CheckCollisionPointRec(GetMousePosition(), bounds))
|
||||
{
|
||||
DrawRectangleRec(bounds, Fade(Color.Red, 0.2f));
|
||||
DrawRectangleLinesEx(bounds, 3.0f, Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - keyboard testbed");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new KeyboardTestbed();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ using static Raylib_cs.Raylib;
|
|||
|
||||
namespace Examples.Core;
|
||||
|
||||
[ExcludeFromBrowser("System.Threading.Thread is unsupported on single-threaded wasm")]
|
||||
public partial class LoadingThread : IExample
|
||||
{
|
||||
const int screenWidth = 800;
|
||||
|
|
|
|||
209
Examples/Core/MonitorDetector.cs
Normal file
209
Examples/Core/MonitorDetector.cs
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - monitor detector
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.6
|
||||
*
|
||||
* Example contributed by Maicon Santana (@maiconpintoabreu) 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 Maicon Santana (@maiconpintoabreu)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
[ExcludeFromBrowser("GetMonitorCount() is not implemented on the wasm target")]
|
||||
public partial class MonitorDetector : IExample
|
||||
{
|
||||
private const int MaxMonitors = 10;
|
||||
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Monitor Detector";
|
||||
|
||||
public string Title => "raylib [core] example - monitor detector";
|
||||
|
||||
// Monitor info
|
||||
private struct MonitorInfo
|
||||
{
|
||||
public Vector2 Position;
|
||||
public string Name;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public int PhysicalWidth;
|
||||
public int PhysicalHeight;
|
||||
public int RefreshRate;
|
||||
}
|
||||
|
||||
private MonitorInfo[] monitors;
|
||||
private int currentMonitorIndex;
|
||||
private int monitorCount;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
monitors = new MonitorInfo[MaxMonitors];
|
||||
currentMonitorIndex = GetCurrentMonitor();
|
||||
monitorCount = 0;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Variables to find the max x and Y to calculate the scale
|
||||
int maxWidth = 1;
|
||||
int maxHeight = 1;
|
||||
|
||||
// Monitor offset is to fix when monitor position x is negative
|
||||
int monitorOffsetX = 0;
|
||||
|
||||
// Rebuild monitors array every frame
|
||||
monitorCount = GetMonitorCount();
|
||||
for (int i = 0; i < monitorCount; i++)
|
||||
{
|
||||
monitors[i] = new MonitorInfo
|
||||
{
|
||||
Position = GetMonitorPosition(i),
|
||||
Name = GetMonitorName_(i),
|
||||
Width = GetMonitorWidth(i),
|
||||
Height = GetMonitorHeight(i),
|
||||
PhysicalWidth = GetMonitorPhysicalWidth(i),
|
||||
PhysicalHeight = GetMonitorPhysicalHeight(i),
|
||||
RefreshRate = GetMonitorRefreshRate(i)
|
||||
};
|
||||
|
||||
if (monitors[i].Position.X < monitorOffsetX)
|
||||
{
|
||||
monitorOffsetX = -(int)monitors[i].Position.X;
|
||||
}
|
||||
|
||||
int width = (int)monitors[i].Position.X + monitors[i].Width;
|
||||
int height = (int)monitors[i].Position.Y + monitors[i].Height;
|
||||
|
||||
if (maxWidth < width)
|
||||
{
|
||||
maxWidth = width;
|
||||
}
|
||||
if (maxHeight < height)
|
||||
{
|
||||
maxHeight = height;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Enter) && (monitorCount > 1))
|
||||
{
|
||||
currentMonitorIndex += 1;
|
||||
|
||||
// Set index to 0 if the last one
|
||||
if (currentMonitorIndex == monitorCount)
|
||||
{
|
||||
currentMonitorIndex = 0;
|
||||
}
|
||||
|
||||
SetWindowMonitor(currentMonitorIndex); // Move window to currentMonitorIndex
|
||||
}
|
||||
else
|
||||
{
|
||||
currentMonitorIndex = GetCurrentMonitor(); // Get currentMonitorIndex if manually moved
|
||||
}
|
||||
|
||||
float monitorScale = 0.6f;
|
||||
|
||||
if (maxHeight > (maxWidth + monitorOffsetX))
|
||||
{
|
||||
monitorScale *= ((float)screenHeight / (float)maxHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
monitorScale *= ((float)screenWidth / (float)(maxWidth + monitorOffsetX));
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
DrawText("Press [Enter] to move window to next monitor available", 20, 20, 20, Color.DarkGray);
|
||||
|
||||
DrawRectangleLines(20, 60, screenWidth - 40, screenHeight - 100, Color.DarkGray);
|
||||
|
||||
// Draw Monitor Rectangles with information inside
|
||||
for (int i = 0; i < monitorCount; i++)
|
||||
{
|
||||
// Calculate retangle position and size using monitorScale
|
||||
Rectangle rec = new Rectangle(
|
||||
(monitors[i].Position.X + monitorOffsetX) * monitorScale + 140,
|
||||
monitors[i].Position.Y * monitorScale + 80,
|
||||
monitors[i].Width * monitorScale,
|
||||
monitors[i].Height * monitorScale
|
||||
);
|
||||
|
||||
// Draw monitor name and information inside the rectangle
|
||||
DrawText($"[{i}] {monitors[i].Name}", (int)rec.X + 10, (int)rec.Y + (int)(100 * monitorScale), (int)(120 * monitorScale), Color.Blue);
|
||||
DrawText(
|
||||
$"Resolution: [{monitors[i].Width}px x {monitors[i].Height}px]\nRefreshRate: [{monitors[i].RefreshRate}hz]\nPhysical Size: [{monitors[i].PhysicalWidth}mm x {monitors[i].PhysicalHeight}mm]\nPosition: {monitors[i].Position.X,3:F0} x {monitors[i].Position.Y,3:F0}",
|
||||
(int)rec.X + 10, (int)rec.Y + (int)(200 * monitorScale), (int)(120 * monitorScale), Color.DarkGray);
|
||||
|
||||
// Highlight current monitor
|
||||
if (i == currentMonitorIndex)
|
||||
{
|
||||
DrawRectangleLinesEx(rec, 5, Color.Red);
|
||||
Vector2 windowPosition = new Vector2((GetWindowPosition().X + monitorOffsetX) * monitorScale + 140, GetWindowPosition().Y * monitorScale + 80);
|
||||
|
||||
// Draw window position based on monitors
|
||||
DrawRectangleV(windowPosition, new Vector2(screenWidth * monitorScale, screenHeight * monitorScale), Fade(Color.Green, 0.5f));
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawRectangleLinesEx(rec, 5, Color.Gray);
|
||||
}
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - monitor detector");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new MonitorDetector();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -27,8 +27,6 @@ public partial class Picking3d : IExample
|
|||
|
||||
public string Title => "raylib [core] example - 3d picking";
|
||||
|
||||
public bool CursorDisabled => true;
|
||||
|
||||
private Camera3D camera;
|
||||
private Vector3 cubePosition;
|
||||
private Vector3 cubeSize;
|
||||
|
|
|
|||
183
Examples/Core/RandomSequence.cs
Normal file
183
Examples/Core/RandomSequence.cs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - random sequence
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 5.0, last time updated with raylib 5.0
|
||||
*
|
||||
* Example contributed by Dalton Overmyer (@REDl3east) 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) 2023-2025 Dalton Overmyer (@REDl3east)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class RandomSequence : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Random Sequence";
|
||||
|
||||
public string Title => "raylib [core] example - random sequence";
|
||||
|
||||
private struct ColorRect
|
||||
{
|
||||
public Color Color;
|
||||
public Rectangle Rect;
|
||||
}
|
||||
|
||||
private int rectCount;
|
||||
private float rectSize;
|
||||
private ColorRect[] rectangles;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
rectCount = 20;
|
||||
rectSize = (float)screenWidth / rectCount;
|
||||
rectangles = GenerateRandomColorRectSequence(rectCount, rectSize, screenWidth, 0.75f * screenHeight);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyPressed(KeyboardKey.Space))
|
||||
{
|
||||
ShuffleColorRectSequence(rectangles, rectCount);
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Up))
|
||||
{
|
||||
rectCount++;
|
||||
rectSize = (float)screenWidth / rectCount;
|
||||
|
||||
// Re-generate random sequence with new count
|
||||
rectangles = GenerateRandomColorRectSequence(rectCount, rectSize, screenWidth, 0.75f * screenHeight);
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Down))
|
||||
{
|
||||
if (rectCount >= 4)
|
||||
{
|
||||
rectCount--;
|
||||
rectSize = (float)screenWidth / rectCount;
|
||||
|
||||
// Re-generate random sequence with new count
|
||||
rectangles = GenerateRandomColorRectSequence(rectCount, rectSize, screenWidth, 0.75f * screenHeight);
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
for (int i = 0; i < rectCount; i++)
|
||||
{
|
||||
DrawRectangleRec(rectangles[i].Rect, rectangles[i].Color);
|
||||
|
||||
DrawText("Press SPACE to shuffle the current sequence", 10, screenHeight - 96, 20, Color.Black);
|
||||
DrawText("Press UP to add a rectangle and generate a new sequence", 10, screenHeight - 64, 20, Color.Black);
|
||||
DrawText("Press DOWN to remove a rectangle and generate a new sequence", 10, screenHeight - 32, 20, Color.Black);
|
||||
}
|
||||
|
||||
DrawText($"Count: {rectCount} rectangles", 10, 10, 20, Color.Maroon);
|
||||
|
||||
DrawFPS(screenWidth - 80, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
private static Color GenerateRandomColor()
|
||||
{
|
||||
return new Color(
|
||||
GetRandomValue(0, 255),
|
||||
GetRandomValue(0, 255),
|
||||
GetRandomValue(0, 255),
|
||||
255
|
||||
);
|
||||
}
|
||||
|
||||
private static ColorRect[] GenerateRandomColorRectSequence(float rectCount, float rectWidth, float screenWidth, float screenHeight)
|
||||
{
|
||||
ColorRect[] rectangles = new ColorRect[(int)rectCount];
|
||||
|
||||
int[] seq = GetRandomSequence((uint)rectCount, 0, (int)rectCount - 1);
|
||||
float rectSeqWidth = rectCount * rectWidth;
|
||||
float startX = (screenWidth - rectSeqWidth) * 0.5f;
|
||||
|
||||
for (int i = 0; i < rectCount; i++)
|
||||
{
|
||||
int rectHeight = (int)Remap(seq[i], 0, rectCount - 1, 0, screenHeight);
|
||||
|
||||
rectangles[i].Color = GenerateRandomColor();
|
||||
rectangles[i].Rect = new Rectangle(startX + i * rectWidth, screenHeight - rectHeight, rectWidth, rectHeight);
|
||||
}
|
||||
|
||||
return rectangles;
|
||||
}
|
||||
|
||||
private static void ShuffleColorRectSequence(ColorRect[] rectangles, int rectCount)
|
||||
{
|
||||
int[] seq = GetRandomSequence((uint)rectCount, 0, rectCount - 1);
|
||||
|
||||
for (int i1 = 0; i1 < rectCount; i1++)
|
||||
{
|
||||
int i2 = seq[i1];
|
||||
|
||||
// Swap only the color and height
|
||||
ColorRect tmp = rectangles[i1];
|
||||
rectangles[i1].Color = rectangles[i2].Color;
|
||||
rectangles[i1].Rect.Height = rectangles[i2].Rect.Height;
|
||||
rectangles[i1].Rect.Y = rectangles[i2].Rect.Y;
|
||||
rectangles[i2].Color = tmp.Color;
|
||||
rectangles[i2].Rect.Height = tmp.Rect.Height;
|
||||
rectangles[i2].Rect.Y = tmp.Rect.Y;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - random sequence");
|
||||
|
||||
SetTargetFPS(60);
|
||||
|
||||
var game = new RandomSequence();
|
||||
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;
|
||||
}
|
||||
}
|
||||
139
Examples/Core/RenderTexture.cs
Normal file
139
Examples/Core/RenderTexture.cs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - render texture
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* 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 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class RenderTextureDemo : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
// Define a render texture to render
|
||||
private const int renderTextureWidth = 300;
|
||||
private const int renderTextureHeight = 300;
|
||||
|
||||
public string Name => "Core / Render Texture";
|
||||
|
||||
public string Title => "raylib [core] example - render texture";
|
||||
|
||||
private RenderTexture2D target;
|
||||
private Vector2 ballPosition;
|
||||
private Vector2 ballSpeed;
|
||||
private int ballRadius;
|
||||
private float rotation;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
target = LoadRenderTexture(renderTextureWidth, renderTextureHeight);
|
||||
|
||||
ballPosition = new Vector2(renderTextureWidth / 2.0f, renderTextureHeight / 2.0f);
|
||||
ballSpeed = new Vector2(5.0f, 4.0f);
|
||||
ballRadius = 20;
|
||||
|
||||
rotation = 0.0f;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//-----------------------------------------------------
|
||||
// Ball movement logic
|
||||
ballPosition.X += ballSpeed.X;
|
||||
ballPosition.Y += ballSpeed.Y;
|
||||
|
||||
// Check walls collision for bouncing
|
||||
if ((ballPosition.X >= (renderTextureWidth - ballRadius)) || (ballPosition.X <= ballRadius))
|
||||
{
|
||||
ballSpeed.X *= -1.0f;
|
||||
}
|
||||
if ((ballPosition.Y >= (renderTextureHeight - ballRadius)) || (ballPosition.Y <= ballRadius))
|
||||
{
|
||||
ballSpeed.Y *= -1.0f;
|
||||
}
|
||||
|
||||
// Render texture rotation
|
||||
rotation += 0.5f;
|
||||
//-----------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//-----------------------------------------------------
|
||||
// Draw our scene to the render texture
|
||||
BeginTextureMode(target);
|
||||
|
||||
ClearBackground(Color.SkyBlue);
|
||||
|
||||
DrawRectangle(0, 0, 20, 20, Color.Red);
|
||||
DrawCircleV(ballPosition, ballRadius, Color.Maroon);
|
||||
|
||||
EndTextureMode();
|
||||
|
||||
// Draw render texture to main framebuffer
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw our render texture with rotation applied
|
||||
// NOTE 1: We set the origin of the texture to the center of the render texture
|
||||
// NOTE 2: We flip vertically the texture setting negative source rectangle height
|
||||
DrawTexturePro(target.Texture,
|
||||
new Rectangle(0, 0, target.Texture.Width, -target.Texture.Height),
|
||||
new Rectangle(screenWidth / 2.0f, screenHeight / 2.0f, target.Texture.Width, target.Texture.Height),
|
||||
new Vector2(target.Texture.Width / 2.0f, target.Texture.Height / 2.0f), rotation, Color.White);
|
||||
|
||||
DrawText("DRAWING BOUNCING BALL INSIDE RENDER TEXTURE!", 10, screenHeight - 40, 20, Color.Black);
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//-----------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadRenderTexture(target);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//---------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - render texture");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//----------------------------------------------------------
|
||||
|
||||
var game = new RenderTextureDemo();
|
||||
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;
|
||||
}
|
||||
}
|
||||
382
Examples/Core/ScreenRecording.cs
Normal file
382
Examples/Core/ScreenRecording.cs
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - screen recording
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* 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 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
// NOTE: The upstream C example records frames into an animated GIF using the bundled msf_gif.h
|
||||
// single-header library. raylib-cs does not bind msf_gif, so this port replaces it with a small,
|
||||
// self-contained GIF89a encoder (GifRecorder, below) that uses a fixed 3-3-2 RGB palette. The
|
||||
// rest of the example (rendering, CTRL+R toggle, saving to <appdir>/screenrecording.gif) mirrors
|
||||
// upstream. Frame capture via LoadImageFromScreen() is slow and can cause stuttering, as noted
|
||||
// upstream.
|
||||
[ExcludeFromBrowser("desktop screen capture + gif file export, no web equivalent")]
|
||||
public partial class ScreenRecording : IExample
|
||||
{
|
||||
private const int GIF_RECORD_FRAMERATE = 5; // Record framerate, we get a frame every N frames
|
||||
|
||||
private const int MAX_SINEWAVE_POINTS = 256;
|
||||
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Screen Recording";
|
||||
|
||||
public string Title => "raylib [core] example - screen recording";
|
||||
|
||||
private bool gifRecording; // GIF recording state
|
||||
private uint gifFrameCounter; // GIF frames counter
|
||||
private GifRecorder gifState; // GIF context state
|
||||
|
||||
private Vector2 circlePosition;
|
||||
private float timeCounter;
|
||||
|
||||
private Vector2[] sinePoints;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
gifRecording = false;
|
||||
gifFrameCounter = 0;
|
||||
gifState = new GifRecorder();
|
||||
|
||||
circlePosition = new Vector2(0.0f, screenHeight / 2.0f);
|
||||
timeCounter = 0.0f;
|
||||
|
||||
// Get sine wave points for line drawing
|
||||
sinePoints = new Vector2[MAX_SINEWAVE_POINTS];
|
||||
for (int i = 0; i < MAX_SINEWAVE_POINTS; i++)
|
||||
{
|
||||
sinePoints[i].X = i * GetScreenWidth() / 180.0f;
|
||||
sinePoints[i].Y = screenHeight / 2.0f + 150 * MathF.Sin((2 * MathF.PI / 1.5f) * (1.0f / 60.0f) * (float)i); // Calculate for 60 fps
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Update circle sinusoidal movement
|
||||
timeCounter += GetFrameTime();
|
||||
circlePosition.X += GetScreenWidth() / 180.0f;
|
||||
circlePosition.Y = screenHeight / 2.0f + 150 * MathF.Sin((2 * MathF.PI / 1.5f) * timeCounter);
|
||||
if (circlePosition.X > screenWidth)
|
||||
{
|
||||
circlePosition.X = 0.0f;
|
||||
circlePosition.Y = screenHeight / 2.0f;
|
||||
timeCounter = 0.0f;
|
||||
}
|
||||
|
||||
// Start-Stop GIF recording on CTRL+R
|
||||
if (IsKeyDown(KeyboardKey.LeftControl) && IsKeyPressed(KeyboardKey.R))
|
||||
{
|
||||
if (gifRecording)
|
||||
{
|
||||
// Stop current recording and save file
|
||||
gifRecording = false;
|
||||
byte[] result = gifState.End();
|
||||
SaveFileData(result, $"{GetApplicationDirectoryString()}/screenrecording.gif");
|
||||
|
||||
TraceLog(TraceLogLevel.Info, "Finish animated GIF recording");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start a new recording
|
||||
gifRecording = true;
|
||||
gifFrameCounter = 0;
|
||||
gifState.Begin(GetRenderWidth(), GetRenderHeight());
|
||||
|
||||
TraceLog(TraceLogLevel.Info, "Start animated GIF recording");
|
||||
}
|
||||
}
|
||||
|
||||
if (gifRecording)
|
||||
{
|
||||
gifFrameCounter++;
|
||||
|
||||
// NOTE: We record one gif frame depending on the desired gif framerate
|
||||
if (gifFrameCounter > GIF_RECORD_FRAMERATE)
|
||||
{
|
||||
// Get image data for the current frame (from backbuffer)
|
||||
// WARNING: This process is quite slow, it can generate stuttering
|
||||
Image imScreen = LoadImageFromScreen();
|
||||
|
||||
// Add the frame to the gif recording, providing and "estimated" time for display in centiseconds
|
||||
int delayCs = (int)((1.0f / 60.0f) * GIF_RECORD_FRAMERATE) / 10;
|
||||
gifState.AddFrame((byte*)imScreen.Data, imScreen.Width, imScreen.Height, imScreen.Width * 4, delayCs);
|
||||
gifFrameCounter = 0;
|
||||
|
||||
UnloadImage(imScreen); // Free image data
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
for (int i = 0; i < (MAX_SINEWAVE_POINTS - 1); i++)
|
||||
{
|
||||
DrawLineV(sinePoints[i], sinePoints[i + 1], Color.Maroon);
|
||||
DrawCircleV(sinePoints[i], 3, Color.Maroon);
|
||||
}
|
||||
|
||||
DrawCircleV(circlePosition, 30, Color.Red);
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
/*
|
||||
// Draw record indicator
|
||||
// WARNING: If drawn here, it will appear in the recorded image,
|
||||
// use a render texture instead for the recording and LoadImageFromTexture(rt.texture)
|
||||
if (gifRecording)
|
||||
{
|
||||
// Display the recording indicator every half-second
|
||||
if ((int)(GetTime()/0.5)%2 == 1)
|
||||
{
|
||||
DrawCircle(30, GetScreenHeight() - 20, 10, Color.Maroon);
|
||||
DrawText("GIF RECORDING", 50, GetScreenHeight() - 25, 10, Color.Red);
|
||||
}
|
||||
}
|
||||
*/
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
// If still recording a GIF on close window, just finish
|
||||
if (gifRecording)
|
||||
{
|
||||
gifState.End();
|
||||
gifRecording = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - screen recording");
|
||||
|
||||
var game = new ScreenRecording();
|
||||
game.Init();
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Minimal self-contained animated GIF89a encoder (replacement for msf_gif.h)
|
||||
// Uses a fixed 3-3-2 RGB global palette and standard GIF-variant LZW compression.
|
||||
private class GifRecorder
|
||||
{
|
||||
private List<byte> output;
|
||||
private int width;
|
||||
private int height;
|
||||
|
||||
// LZW bit-packing state (per-frame)
|
||||
private int bitBuffer;
|
||||
private int bitCount;
|
||||
private List<byte> subBlock;
|
||||
|
||||
public void Begin(int w, int h)
|
||||
{
|
||||
width = w;
|
||||
height = h;
|
||||
output = new List<byte>();
|
||||
|
||||
// Header
|
||||
output.AddRange(new byte[] { (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a' });
|
||||
|
||||
// Logical Screen Descriptor
|
||||
WriteU16(width);
|
||||
WriteU16(height);
|
||||
output.Add(0xF7); // Global color table present, 8-bit color res, 256-entry table
|
||||
output.Add(0x00); // Background color index
|
||||
output.Add(0x00); // Pixel aspect ratio
|
||||
|
||||
// Global Color Table: 256 entries, 3-3-2 RGB
|
||||
for (int k = 0; k < 256; k++)
|
||||
{
|
||||
int r3 = (k >> 5) & 0x7;
|
||||
int g3 = (k >> 2) & 0x7;
|
||||
int b2 = k & 0x3;
|
||||
output.Add((byte)((r3 << 5) | (r3 << 2) | (r3 >> 1)));
|
||||
output.Add((byte)((g3 << 5) | (g3 << 2) | (g3 >> 1)));
|
||||
output.Add((byte)((b2 << 6) | (b2 << 4) | (b2 << 2) | b2));
|
||||
}
|
||||
|
||||
// NETSCAPE2.0 application extension (loop forever)
|
||||
output.Add(0x21);
|
||||
output.Add(0xFF);
|
||||
output.Add(0x0B);
|
||||
output.AddRange(new byte[] { (byte)'N', (byte)'E', (byte)'T', (byte)'S', (byte)'C', (byte)'A', (byte)'P', (byte)'E', (byte)'2', (byte)'.', (byte)'0' });
|
||||
output.Add(0x03);
|
||||
output.Add(0x01);
|
||||
WriteU16(0); // Loop count (0 = forever)
|
||||
output.Add(0x00);
|
||||
}
|
||||
|
||||
public unsafe void AddFrame(byte* data, int w, int h, int stride, int delayCs)
|
||||
{
|
||||
if (output == null) return;
|
||||
|
||||
// Graphic Control Extension
|
||||
output.Add(0x21);
|
||||
output.Add(0xF9);
|
||||
output.Add(0x04);
|
||||
output.Add(0x00); // No transparency, disposal method 0
|
||||
WriteU16(delayCs);
|
||||
output.Add(0x00); // Transparent color index
|
||||
output.Add(0x00); // Block terminator
|
||||
|
||||
// Image Descriptor
|
||||
output.Add(0x2C);
|
||||
WriteU16(0); // Left
|
||||
WriteU16(0); // Top
|
||||
WriteU16(w);
|
||||
WriteU16(h);
|
||||
output.Add(0x00); // No local color table, not interlaced
|
||||
|
||||
// Map pixels to palette indices (3-3-2)
|
||||
byte[] indices = new byte[w * h];
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int row = y * stride;
|
||||
int dst = y * w;
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
byte r = data[row + x * 4 + 0];
|
||||
byte g = data[row + x * 4 + 1];
|
||||
byte b = data[row + x * 4 + 2];
|
||||
indices[dst + x] = (byte)((r & 0xE0) | ((g & 0xE0) >> 3) | (b >> 6));
|
||||
}
|
||||
}
|
||||
|
||||
// LZW image data
|
||||
const int minCodeSize = 8;
|
||||
output.Add((byte)minCodeSize);
|
||||
|
||||
bitBuffer = 0;
|
||||
bitCount = 0;
|
||||
subBlock = new List<byte>();
|
||||
|
||||
int clearCode = 1 << minCodeSize; // 256
|
||||
int stopCode = clearCode + 1; // 257
|
||||
int keySize = minCodeSize + 1; // 9
|
||||
int nkeys = clearCode + 2; // 258
|
||||
var dict = new Dictionary<int, int>();
|
||||
|
||||
WriteBits(clearCode, keySize);
|
||||
|
||||
int key = indices[0];
|
||||
for (int i = 1; i < indices.Length; i++)
|
||||
{
|
||||
int p = indices[i];
|
||||
int combined = (key << 8) | p;
|
||||
if (dict.TryGetValue(combined, out int existing))
|
||||
{
|
||||
key = existing;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteBits(key, keySize);
|
||||
dict[combined] = nkeys;
|
||||
nkeys++;
|
||||
if (nkeys == (1 << keySize))
|
||||
{
|
||||
if (keySize < 12) keySize++;
|
||||
}
|
||||
if (nkeys == 0x1000)
|
||||
{
|
||||
WriteBits(clearCode, keySize);
|
||||
dict.Clear();
|
||||
keySize = minCodeSize + 1;
|
||||
nkeys = clearCode + 2;
|
||||
}
|
||||
key = p;
|
||||
}
|
||||
}
|
||||
|
||||
WriteBits(key, keySize);
|
||||
WriteBits(stopCode, keySize);
|
||||
|
||||
// Flush remaining bits
|
||||
if (bitCount > 0)
|
||||
{
|
||||
subBlock.Add((byte)(bitBuffer & 0xFF));
|
||||
bitBuffer = 0;
|
||||
bitCount = 0;
|
||||
}
|
||||
if (subBlock.Count > 0) FlushSubBlock();
|
||||
output.Add(0x00); // Image data block terminator
|
||||
}
|
||||
|
||||
public byte[] End()
|
||||
{
|
||||
if (output == null) return Array.Empty<byte>();
|
||||
output.Add(0x3B); // Trailer
|
||||
byte[] result = output.ToArray();
|
||||
output = null;
|
||||
return result;
|
||||
}
|
||||
|
||||
private void WriteBits(int code, int len)
|
||||
{
|
||||
bitBuffer |= code << bitCount;
|
||||
bitCount += len;
|
||||
while (bitCount >= 8)
|
||||
{
|
||||
subBlock.Add((byte)(bitBuffer & 0xFF));
|
||||
bitBuffer >>= 8;
|
||||
bitCount -= 8;
|
||||
if (subBlock.Count == 255) FlushSubBlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void FlushSubBlock()
|
||||
{
|
||||
output.Add((byte)subBlock.Count);
|
||||
output.AddRange(subBlock);
|
||||
subBlock.Clear();
|
||||
}
|
||||
|
||||
private void WriteU16(int value)
|
||||
{
|
||||
output.Add((byte)(value & 0xFF));
|
||||
output.Add((byte)((value >> 8) & 0xFF));
|
||||
}
|
||||
}
|
||||
}
|
||||
204
Examples/Core/SplitScreen2D.cs
Normal file
204
Examples/Core/SplitScreen2D.cs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - 2d camera split screen
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Addapted from the core_3d_camera_split_screen example:
|
||||
* https://github.com/raysan5/raylib/blob/master/examples/core/core_3d_camera_split_screen.c
|
||||
*
|
||||
* Example originally created with raylib 4.5, last time updated with raylib 4.5
|
||||
*
|
||||
* Example contributed by Gabriel dos Santos Sanches (@gabrielssanches) 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) 2023-2025 Gabriel dos Santos Sanches (@gabrielssanches)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class SplitScreen2D : IExample
|
||||
{
|
||||
private const int PLAYER_SIZE = 40;
|
||||
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 440;
|
||||
|
||||
public string Name => "Core / 2D Camera Split Screen";
|
||||
|
||||
public string Title => "raylib [core] example - 2d camera split screen";
|
||||
|
||||
public int Width => screenWidth;
|
||||
|
||||
public int Height => screenHeight;
|
||||
|
||||
private Rectangle player1;
|
||||
private Rectangle player2;
|
||||
private Camera2D camera1;
|
||||
private Camera2D camera2;
|
||||
private RenderTexture2D screenCamera1;
|
||||
private RenderTexture2D screenCamera2;
|
||||
private Rectangle splitScreenRect;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
player1 = new Rectangle(200, 200, PLAYER_SIZE, PLAYER_SIZE);
|
||||
player2 = new Rectangle(250, 200, PLAYER_SIZE, PLAYER_SIZE);
|
||||
|
||||
camera1 = new Camera2D();
|
||||
camera1.Target = new Vector2(player1.X, player1.Y);
|
||||
camera1.Offset = new Vector2(200.0f, 200.0f);
|
||||
camera1.Rotation = 0.0f;
|
||||
camera1.Zoom = 1.0f;
|
||||
|
||||
camera2 = new Camera2D();
|
||||
camera2.Target = new Vector2(player2.X, player2.Y);
|
||||
camera2.Offset = new Vector2(200.0f, 200.0f);
|
||||
camera2.Rotation = 0.0f;
|
||||
camera2.Zoom = 1.0f;
|
||||
|
||||
screenCamera1 = LoadRenderTexture(screenWidth / 2, screenHeight);
|
||||
screenCamera2 = LoadRenderTexture(screenWidth / 2, screenHeight);
|
||||
|
||||
// Build a flipped rectangle the size of the split view to use for drawing later
|
||||
splitScreenRect = new Rectangle(0.0f, 0.0f, (float)screenCamera1.Texture.Width, (float)-screenCamera1.Texture.Height);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyDown(KeyboardKey.S)) player1.Y += 3.0f;
|
||||
else if (IsKeyDown(KeyboardKey.W)) player1.Y -= 3.0f;
|
||||
if (IsKeyDown(KeyboardKey.D)) player1.X += 3.0f;
|
||||
else if (IsKeyDown(KeyboardKey.A)) player1.X -= 3.0f;
|
||||
|
||||
if (IsKeyDown(KeyboardKey.Up)) player2.Y -= 3.0f;
|
||||
else if (IsKeyDown(KeyboardKey.Down)) player2.Y += 3.0f;
|
||||
if (IsKeyDown(KeyboardKey.Right)) player2.X += 3.0f;
|
||||
else if (IsKeyDown(KeyboardKey.Left)) player2.X -= 3.0f;
|
||||
|
||||
camera1.Target = new Vector2(player1.X, player1.Y);
|
||||
camera2.Target = new Vector2(player2.X, player2.Y);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginTextureMode(screenCamera1);
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode2D(camera1);
|
||||
|
||||
// Draw full scene with first camera
|
||||
for (int i = 0; i < screenWidth / PLAYER_SIZE + 1; i++)
|
||||
{
|
||||
DrawLineV(new Vector2((float)PLAYER_SIZE * i, 0), new Vector2((float)PLAYER_SIZE * i, (float)screenHeight), Color.LightGray);
|
||||
}
|
||||
|
||||
for (int i = 0; i < screenHeight / PLAYER_SIZE + 1; i++)
|
||||
{
|
||||
DrawLineV(new Vector2(0, (float)PLAYER_SIZE * i), new Vector2((float)screenWidth, (float)PLAYER_SIZE * i), Color.LightGray);
|
||||
}
|
||||
|
||||
for (int i = 0; i < screenWidth / PLAYER_SIZE; i++)
|
||||
{
|
||||
for (int j = 0; j < screenHeight / PLAYER_SIZE; j++)
|
||||
{
|
||||
DrawText($"[{i},{j}]", 10 + PLAYER_SIZE * i, 15 + PLAYER_SIZE * j, 10, Color.LightGray);
|
||||
}
|
||||
}
|
||||
|
||||
DrawRectangleRec(player1, Color.Red);
|
||||
DrawRectangleRec(player2, Color.Blue);
|
||||
EndMode2D();
|
||||
|
||||
DrawRectangle(0, 0, GetScreenWidth() / 2, 30, Fade(Color.RayWhite, 0.6f));
|
||||
DrawText("PLAYER1: W/S/A/D to move", 10, 10, 10, Color.Maroon);
|
||||
|
||||
EndTextureMode();
|
||||
|
||||
BeginTextureMode(screenCamera2);
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode2D(camera2);
|
||||
|
||||
// Draw full scene with second camera
|
||||
for (int i = 0; i < screenWidth / PLAYER_SIZE + 1; i++)
|
||||
{
|
||||
DrawLineV(new Vector2((float)PLAYER_SIZE * i, 0), new Vector2((float)PLAYER_SIZE * i, (float)screenHeight), Color.LightGray);
|
||||
}
|
||||
|
||||
for (int i = 0; i < screenHeight / PLAYER_SIZE + 1; i++)
|
||||
{
|
||||
DrawLineV(new Vector2(0, (float)PLAYER_SIZE * i), new Vector2((float)screenWidth, (float)PLAYER_SIZE * i), Color.LightGray);
|
||||
}
|
||||
|
||||
for (int i = 0; i < screenWidth / PLAYER_SIZE; i++)
|
||||
{
|
||||
for (int j = 0; j < screenHeight / PLAYER_SIZE; j++)
|
||||
{
|
||||
DrawText($"[{i},{j}]", 10 + PLAYER_SIZE * i, 15 + PLAYER_SIZE * j, 10, Color.LightGray);
|
||||
}
|
||||
}
|
||||
|
||||
DrawRectangleRec(player1, Color.Red);
|
||||
DrawRectangleRec(player2, Color.Blue);
|
||||
|
||||
EndMode2D();
|
||||
|
||||
DrawRectangle(0, 0, GetScreenWidth() / 2, 30, Fade(Color.RayWhite, 0.6f));
|
||||
DrawText("PLAYER2: UP/DOWN/LEFT/RIGHT to move", 10, 10, 10, Color.DarkBlue);
|
||||
|
||||
EndTextureMode();
|
||||
|
||||
// Draw both views render textures to the screen side by side
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.Black);
|
||||
|
||||
DrawTextureRec(screenCamera1.Texture, splitScreenRect, new Vector2(0, 0), Color.White);
|
||||
DrawTextureRec(screenCamera2.Texture, splitScreenRect, new Vector2(screenWidth / 2.0f, 0), Color.White);
|
||||
|
||||
DrawRectangle(GetScreenWidth() / 2 - 2, 0, 4, GetScreenHeight(), Color.LightGray);
|
||||
EndDrawing();
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadRenderTexture(screenCamera1); // Unload render texture
|
||||
UnloadRenderTexture(screenCamera2); // Unload render texture
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - 2d camera split screen");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new SplitScreen2D();
|
||||
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;
|
||||
}
|
||||
}
|
||||
209
Examples/Core/TextFileLoading.cs
Normal file
209
Examples/Core/TextFileLoading.cs
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - text file loading
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.6
|
||||
*
|
||||
* Example contributed by Aanjishnu Bhattacharyya (@NimComPoo-04) 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) 0 Aanjishnu Bhattacharyya (@NimComPoo-04)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
// NOTE: The upstream C code mutates the raw char buffers returned by LoadTextLines() in place,
|
||||
// temporarily null-terminating to reuse MeasureText for word wrapping. This C# port keeps the
|
||||
// same algorithm but works on managed char arrays: LoadFileText + split on '\n' reproduces
|
||||
// raylib's LoadTextLines('\n') behaviour, and '\n' characters are inserted where lines wrap.
|
||||
public partial class TextFileLoading : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Text File Loading";
|
||||
|
||||
public string Title => "raylib [core] example - text file loading";
|
||||
|
||||
private Camera2D cam;
|
||||
private string fileName;
|
||||
private string[] lines;
|
||||
private int lineCount;
|
||||
private int fontSize;
|
||||
private int textTop;
|
||||
private int wrapWidth;
|
||||
private int textHeight;
|
||||
private Rectangle scrollBar;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Setting up the camera
|
||||
cam = new Camera2D();
|
||||
cam.Offset = new Vector2(0, 0);
|
||||
cam.Target = new Vector2(0, 0);
|
||||
cam.Rotation = 0;
|
||||
cam.Zoom = 1;
|
||||
|
||||
// Loading text file from resources/text_file.txt
|
||||
fileName = "resources/text_file.txt";
|
||||
string text = LoadFileText(fileName);
|
||||
|
||||
// Loading all the text lines (raylib's LoadTextLines splits on '\n')
|
||||
lines = text.Split('\n');
|
||||
lineCount = lines.Length;
|
||||
|
||||
// Stylistic choises
|
||||
fontSize = 20;
|
||||
textTop = 25 + fontSize; // Top of the screen from where the text is rendered
|
||||
wrapWidth = screenWidth - 20;
|
||||
|
||||
// Wrap the lines as needed
|
||||
for (int i = 0; i < lineCount; i++)
|
||||
{
|
||||
char[] chars = lines[i].ToCharArray();
|
||||
int len = chars.Length;
|
||||
int j = 0;
|
||||
int lastSpace = 0; // Keeping track of last valid space to insert '\n'
|
||||
int lastWrapStart = 0; // Keeping track of the start of this wrapped line.
|
||||
|
||||
while (j <= len)
|
||||
{
|
||||
char cur = (j < len) ? chars[j] : '\0';
|
||||
if (cur == ' ' || cur == '\0')
|
||||
{
|
||||
// Making a C style string by "cutting" at the required location so that we can use MeasureText
|
||||
string sub = new string(chars, lastWrapStart, j - lastWrapStart);
|
||||
|
||||
// Checking if the text has crossed the wrapWidth, then going back and inserting a newline
|
||||
if (MeasureText(sub, fontSize) > wrapWidth)
|
||||
{
|
||||
chars[lastSpace] = '\n';
|
||||
|
||||
// Since we added a newline the place of wrap changed so we update our lastWrapStart
|
||||
lastWrapStart = lastSpace + 1;
|
||||
}
|
||||
|
||||
lastSpace = j; // Since we encountered a new space we update our last encountered space location
|
||||
}
|
||||
|
||||
j++;
|
||||
}
|
||||
|
||||
lines[i] = new string(chars);
|
||||
}
|
||||
|
||||
// Calculating the total height so that we can show a scrollbar
|
||||
textHeight = 0;
|
||||
|
||||
for (int i = 0; i < lineCount; i++)
|
||||
{
|
||||
Vector2 size = MeasureTextEx(GetFontDefault(), lines[i], (float)fontSize, 2);
|
||||
textHeight += (int)size.Y + 10;
|
||||
}
|
||||
|
||||
// A simple scrollbar on the side to show how far we have read into the file
|
||||
scrollBar = new Rectangle(
|
||||
(float)screenWidth - 5,
|
||||
0,
|
||||
5,
|
||||
screenHeight * 100.0f / (textHeight - screenHeight)); // Scrollbar height is just a percentage
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
float scroll = GetMouseWheelMove();
|
||||
cam.Target.Y -= scroll * fontSize * 1.5f; // Choosing an arbitrary speed for scroll
|
||||
|
||||
if (cam.Target.Y < 0) cam.Target.Y = 0; // Snapping to 0 if we go too far back
|
||||
|
||||
// Ensuring that the camera does not scroll past all text
|
||||
if (cam.Target.Y > textHeight - screenHeight + textTop)
|
||||
cam.Target.Y = (float)textHeight - screenHeight + textTop;
|
||||
|
||||
// Computing the position of the scrollBar depending on the percentage of text covered
|
||||
scrollBar.Y = Lerp((float)textTop, (float)screenHeight - scrollBar.Height, (float)(cam.Target.Y - textTop) / (textHeight - screenHeight));
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode2D(cam);
|
||||
// Going through all the read lines
|
||||
for (int i = 0, t = textTop; i < lineCount; i++)
|
||||
{
|
||||
// Each time we go through and calculate the height of the text to move the cursor appropriately
|
||||
Vector2 size;
|
||||
if (lines[i] != "")
|
||||
{
|
||||
size = MeasureTextEx(GetFontDefault(), lines[i], (float)fontSize, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fix for empty line in the text file
|
||||
size = MeasureTextEx(GetFontDefault(), " ", (float)fontSize, 2);
|
||||
}
|
||||
|
||||
DrawText(lines[i], 10, t, fontSize, Color.Red);
|
||||
|
||||
// Inserting extra space for real newlines,
|
||||
// wrapped lines are rendered closer together
|
||||
t += (int)size.Y + 10;
|
||||
}
|
||||
EndMode2D();
|
||||
|
||||
// Header displaying which file is being read currently
|
||||
DrawRectangle(0, 0, screenWidth, textTop - 10, Color.Beige);
|
||||
DrawText($"File: {fileName}", 10, 10, fontSize, Color.Maroon);
|
||||
|
||||
DrawRectangleRec(scrollBar, Color.Maroon);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - text file loading");
|
||||
|
||||
var game = new TextFileLoading();
|
||||
game.Init();
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
333
Examples/Core/UndoRedo.cs
Normal file
333
Examples/Core/UndoRedo.cs
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - undo redo
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.6
|
||||
*
|
||||
* Example contributed 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 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class UndoRedo : IExample
|
||||
{
|
||||
private const int MAX_UNDO_STATES = 26; // Maximum undo states supported for the ring buffer
|
||||
|
||||
private const int GRID_CELL_SIZE = 24;
|
||||
private const int MAX_GRID_CELLS_X = 30;
|
||||
private const int MAX_GRID_CELLS_Y = 13;
|
||||
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Undo Redo";
|
||||
|
||||
public string Title => "raylib [core] example - undo redo";
|
||||
|
||||
// Point struct, like Vector2 but using int
|
||||
private struct Point
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
// Player state struct
|
||||
// NOTE: Contains all player data that needs to be affected by undo/redo
|
||||
private struct PlayerState
|
||||
{
|
||||
public Point Cell;
|
||||
public Color Color;
|
||||
}
|
||||
|
||||
// Undo/redo system variables
|
||||
private int currentUndoIndex;
|
||||
private int firstUndoIndex;
|
||||
private int lastUndoIndex;
|
||||
private int undoFrameCounter;
|
||||
private Vector2 undoInfoPos;
|
||||
|
||||
private PlayerState player;
|
||||
private PlayerState[] states;
|
||||
|
||||
// Grid variables
|
||||
private Vector2 gridPosition;
|
||||
|
||||
// Compare two player states (replaces memcmp)
|
||||
private static bool SameState(in PlayerState a, in PlayerState b)
|
||||
{
|
||||
return (a.Cell.X == b.Cell.X) && (a.Cell.Y == b.Cell.Y) &&
|
||||
(a.Color.R == b.Color.R) && (a.Color.G == b.Color.G) &&
|
||||
(a.Color.B == b.Color.B) && (a.Color.A == b.Color.A);
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
currentUndoIndex = 0;
|
||||
firstUndoIndex = 0;
|
||||
lastUndoIndex = 0;
|
||||
undoFrameCounter = 0;
|
||||
undoInfoPos = new Vector2(110, 400);
|
||||
|
||||
// Init current player state and undo/redo recorded states array
|
||||
player = new PlayerState();
|
||||
player.Cell = new Point { X = 10, Y = 10 };
|
||||
player.Color = Color.Red;
|
||||
|
||||
// Init undo buffer to store MAX_UNDO_STATES states
|
||||
states = new PlayerState[MAX_UNDO_STATES];
|
||||
// Init all undo states to current state
|
||||
for (int i = 0; i < MAX_UNDO_STATES; i++) states[i] = player;
|
||||
|
||||
// Grid variables
|
||||
gridPosition = new Vector2(40, 60);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Player movement logic
|
||||
if (IsKeyPressed(KeyboardKey.Right)) player.Cell.X++;
|
||||
else if (IsKeyPressed(KeyboardKey.Left)) player.Cell.X--;
|
||||
else if (IsKeyPressed(KeyboardKey.Up)) player.Cell.Y--;
|
||||
else if (IsKeyPressed(KeyboardKey.Down)) player.Cell.Y++;
|
||||
|
||||
// Make sure player does not go out of bounds
|
||||
if (player.Cell.X < 0) player.Cell.X = 0;
|
||||
else if (player.Cell.X >= MAX_GRID_CELLS_X) player.Cell.X = MAX_GRID_CELLS_X - 1;
|
||||
if (player.Cell.Y < 0) player.Cell.Y = 0;
|
||||
else if (player.Cell.Y >= MAX_GRID_CELLS_Y) player.Cell.Y = MAX_GRID_CELLS_Y - 1;
|
||||
|
||||
// Player color change logic
|
||||
if (IsKeyPressed(KeyboardKey.Space))
|
||||
{
|
||||
player.Color.R = (byte)GetRandomValue(20, 255);
|
||||
player.Color.G = (byte)GetRandomValue(20, 220);
|
||||
player.Color.B = (byte)GetRandomValue(20, 240);
|
||||
}
|
||||
|
||||
// Undo state change logic
|
||||
undoFrameCounter++;
|
||||
|
||||
// Waiting a number of frames before checking if we should store a new state snapshot
|
||||
if (undoFrameCounter >= 2) // Checking every 2 frames
|
||||
{
|
||||
if (!SameState(states[currentUndoIndex], player))
|
||||
{
|
||||
// Move cursor to next available position of the undo ring buffer to record state
|
||||
currentUndoIndex++;
|
||||
if (currentUndoIndex >= MAX_UNDO_STATES) currentUndoIndex = 0;
|
||||
if (currentUndoIndex == firstUndoIndex) firstUndoIndex++;
|
||||
if (firstUndoIndex >= MAX_UNDO_STATES) firstUndoIndex = 0;
|
||||
|
||||
states[currentUndoIndex] = player;
|
||||
lastUndoIndex = currentUndoIndex;
|
||||
}
|
||||
|
||||
undoFrameCounter = 0;
|
||||
}
|
||||
|
||||
// Recover previous state from buffer: CTRL+Z
|
||||
if (IsKeyDown(KeyboardKey.LeftControl) && IsKeyPressed(KeyboardKey.Z))
|
||||
{
|
||||
if (currentUndoIndex != firstUndoIndex)
|
||||
{
|
||||
currentUndoIndex--;
|
||||
if (currentUndoIndex < 0) currentUndoIndex = MAX_UNDO_STATES - 1;
|
||||
|
||||
if (!SameState(states[currentUndoIndex], player))
|
||||
{
|
||||
player = states[currentUndoIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recover next state from buffer: CTRL+Y
|
||||
if (IsKeyDown(KeyboardKey.LeftControl) && IsKeyPressed(KeyboardKey.Y))
|
||||
{
|
||||
if (currentUndoIndex != lastUndoIndex)
|
||||
{
|
||||
int nextUndoIndex = currentUndoIndex + 1;
|
||||
if (nextUndoIndex >= MAX_UNDO_STATES) nextUndoIndex = 0;
|
||||
|
||||
if (nextUndoIndex != firstUndoIndex)
|
||||
{
|
||||
currentUndoIndex = nextUndoIndex;
|
||||
|
||||
if (!SameState(states[currentUndoIndex], player))
|
||||
{
|
||||
player = states[currentUndoIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw controls info
|
||||
DrawText("[ARROWS] MOVE PLAYER - [SPACE] CHANGE PLAYER COLOR", 40, 20, 20, Color.DarkGray);
|
||||
|
||||
// Draw player visited cells recorded by undo
|
||||
// NOTE: Remember we are using a ring buffer approach so,
|
||||
// some cells info could start at the end of the array and end at the beginning
|
||||
if (lastUndoIndex > firstUndoIndex)
|
||||
{
|
||||
for (int i = firstUndoIndex; i < currentUndoIndex; i++)
|
||||
DrawRectangleRec(new Rectangle(gridPosition.X + states[i].Cell.X * GRID_CELL_SIZE, gridPosition.Y + states[i].Cell.Y * GRID_CELL_SIZE,
|
||||
GRID_CELL_SIZE, GRID_CELL_SIZE), Color.LightGray);
|
||||
}
|
||||
else if (firstUndoIndex > lastUndoIndex)
|
||||
{
|
||||
if ((currentUndoIndex < MAX_UNDO_STATES) && (currentUndoIndex > lastUndoIndex))
|
||||
{
|
||||
for (int i = firstUndoIndex; i < currentUndoIndex; i++)
|
||||
DrawRectangleRec(new Rectangle(gridPosition.X + states[i].Cell.X * GRID_CELL_SIZE, gridPosition.Y + states[i].Cell.Y * GRID_CELL_SIZE,
|
||||
GRID_CELL_SIZE, GRID_CELL_SIZE), Color.LightGray);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = firstUndoIndex; i < MAX_UNDO_STATES; i++)
|
||||
DrawRectangle((int)gridPosition.X + states[i].Cell.X * GRID_CELL_SIZE, (int)gridPosition.Y + states[i].Cell.Y * GRID_CELL_SIZE,
|
||||
GRID_CELL_SIZE, GRID_CELL_SIZE, Color.LightGray);
|
||||
for (int i = 0; i < currentUndoIndex; i++)
|
||||
DrawRectangle((int)gridPosition.X + states[i].Cell.X * GRID_CELL_SIZE, (int)gridPosition.Y + states[i].Cell.Y * GRID_CELL_SIZE,
|
||||
GRID_CELL_SIZE, GRID_CELL_SIZE, Color.LightGray);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw game grid
|
||||
for (int y = 0; y <= MAX_GRID_CELLS_Y; y++)
|
||||
DrawLine((int)gridPosition.X, (int)gridPosition.Y + y * GRID_CELL_SIZE,
|
||||
(int)gridPosition.X + MAX_GRID_CELLS_X * GRID_CELL_SIZE, (int)gridPosition.Y + y * GRID_CELL_SIZE, Color.Gray);
|
||||
for (int x = 0; x <= MAX_GRID_CELLS_X; x++)
|
||||
DrawLine((int)gridPosition.X + x * GRID_CELL_SIZE, (int)gridPosition.Y,
|
||||
(int)gridPosition.X + x * GRID_CELL_SIZE, (int)gridPosition.Y + MAX_GRID_CELLS_Y * GRID_CELL_SIZE, Color.Gray);
|
||||
|
||||
// Draw player
|
||||
DrawRectangle((int)gridPosition.X + player.Cell.X * GRID_CELL_SIZE, (int)gridPosition.Y + player.Cell.Y * GRID_CELL_SIZE,
|
||||
GRID_CELL_SIZE + 1, GRID_CELL_SIZE + 1, player.Color);
|
||||
|
||||
// Draw undo system buffer info
|
||||
DrawText("UNDO STATES:", (int)undoInfoPos.X - 85, (int)undoInfoPos.Y + 9, 10, Color.DarkGray);
|
||||
DrawUndoBuffer(undoInfoPos, firstUndoIndex, lastUndoIndex, currentUndoIndex, 24);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
// Draw undo system visualization logic
|
||||
// NOTE: Visualizing the ring buffer array, every square can store a player state
|
||||
private static void DrawUndoBuffer(Vector2 position, int firstUndoIndex, int lastUndoIndex, int currentUndoIndex, int slotSize)
|
||||
{
|
||||
// Draw index marks
|
||||
DrawRectangle((int)position.X + 8 + slotSize * currentUndoIndex, (int)position.Y - 10, 8, 8, Color.Red);
|
||||
DrawRectangleLines((int)position.X + 2 + slotSize * firstUndoIndex, (int)position.Y + 27, 8, 8, Color.Black);
|
||||
DrawRectangle((int)position.X + 14 + slotSize * lastUndoIndex, (int)position.Y + 27, 8, 8, Color.Black);
|
||||
|
||||
// Draw background gray slots
|
||||
for (int i = 0; i < MAX_UNDO_STATES; i++)
|
||||
{
|
||||
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.LightGray);
|
||||
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Gray);
|
||||
}
|
||||
|
||||
// Draw occupied slots: firstUndoIndex --> lastUndoIndex
|
||||
if (firstUndoIndex <= lastUndoIndex)
|
||||
{
|
||||
for (int i = firstUndoIndex; i < lastUndoIndex + 1; i++)
|
||||
{
|
||||
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.SkyBlue);
|
||||
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Blue);
|
||||
}
|
||||
}
|
||||
else if (lastUndoIndex < firstUndoIndex)
|
||||
{
|
||||
for (int i = firstUndoIndex; i < MAX_UNDO_STATES; i++)
|
||||
{
|
||||
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.SkyBlue);
|
||||
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Blue);
|
||||
}
|
||||
|
||||
for (int i = 0; i < lastUndoIndex + 1; i++)
|
||||
{
|
||||
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.SkyBlue);
|
||||
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Blue);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw occupied slots: firstUndoIndex --> currentUndoIndex
|
||||
if (firstUndoIndex < currentUndoIndex)
|
||||
{
|
||||
for (int i = firstUndoIndex; i < currentUndoIndex; i++)
|
||||
{
|
||||
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Green);
|
||||
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Lime);
|
||||
}
|
||||
}
|
||||
else if (currentUndoIndex < firstUndoIndex)
|
||||
{
|
||||
for (int i = firstUndoIndex; i < MAX_UNDO_STATES; i++)
|
||||
{
|
||||
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Green);
|
||||
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Lime);
|
||||
}
|
||||
|
||||
for (int i = 0; i < currentUndoIndex; i++)
|
||||
{
|
||||
DrawRectangle((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Green);
|
||||
DrawRectangleLines((int)position.X + slotSize * i, (int)position.Y, slotSize, slotSize, Color.Lime);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw current selected UNDO slot
|
||||
DrawRectangle((int)position.X + slotSize * currentUndoIndex, (int)position.Y, slotSize, slotSize, Color.Gold);
|
||||
DrawRectangleLines((int)position.X + slotSize * currentUndoIndex, (int)position.Y, slotSize, slotSize, Color.Orange);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - undo redo");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new UndoRedo();
|
||||
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;
|
||||
}
|
||||
}
|
||||
361
Examples/Core/ViewportScaling.cs
Normal file
361
Examples/Core/ViewportScaling.cs
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - viewport scaling
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.5
|
||||
*
|
||||
* Example contributed by Agnis Aldiņš (@nezvers) 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 Agnis Aldiņš (@nezvers)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class ViewportScaling : IExample
|
||||
{
|
||||
private const int ResolutionCount = 4; // For iteration purposes and teaching example
|
||||
|
||||
private enum ViewportType
|
||||
{
|
||||
// Only upscale, useful for pixel art
|
||||
KeepAspectInteger,
|
||||
KeepHeightInteger,
|
||||
KeepWidthInteger,
|
||||
// Can also downscale
|
||||
KeepAspect,
|
||||
KeepHeight,
|
||||
KeepWidth,
|
||||
// For itteration purposes and as a teaching example
|
||||
ViewportTypeCount,
|
||||
}
|
||||
|
||||
// For displaying on GUI
|
||||
private static readonly string[] ViewportTypeNames = new string[]
|
||||
{
|
||||
"KEEP_ASPECT_INTEGER",
|
||||
"KEEP_HEIGHT_INTEGER",
|
||||
"KEEP_WIDTH_INTEGER",
|
||||
"KEEP_ASPECT",
|
||||
"KEEP_HEIGHT",
|
||||
"KEEP_WIDTH",
|
||||
};
|
||||
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Viewport Scaling";
|
||||
|
||||
public string Title => "raylib [core] example - viewport scaling";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.ResizableWindow;
|
||||
|
||||
// Mutable window size (tracked from GetScreenWidth/GetScreenHeight)
|
||||
private int curScreenWidth;
|
||||
private int curScreenHeight;
|
||||
|
||||
private Vector2[] resolutionList;
|
||||
private int resolutionIndex;
|
||||
private int gameWidth;
|
||||
private int gameHeight;
|
||||
|
||||
private RenderTexture2D target;
|
||||
private Rectangle sourceRect;
|
||||
private Rectangle destRect;
|
||||
|
||||
private ViewportType viewportType;
|
||||
|
||||
// Button rectangles
|
||||
private Rectangle decreaseResolutionButton;
|
||||
private Rectangle increaseResolutionButton;
|
||||
private Rectangle decreaseTypeButton;
|
||||
private Rectangle increaseTypeButton;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
curScreenWidth = screenWidth;
|
||||
curScreenHeight = screenHeight;
|
||||
|
||||
// Preset resolutions that could be created by subdividing screen resolution
|
||||
resolutionList = new Vector2[]
|
||||
{
|
||||
new Vector2(64, 64),
|
||||
new Vector2(256, 240),
|
||||
new Vector2(320, 180),
|
||||
// 4K doesn't work with integer scaling but included for example purposes with non-integer scaling
|
||||
new Vector2(3840, 2160),
|
||||
};
|
||||
|
||||
resolutionIndex = 0;
|
||||
gameWidth = 64;
|
||||
gameHeight = 64;
|
||||
|
||||
target = default;
|
||||
sourceRect = default;
|
||||
destRect = default;
|
||||
|
||||
viewportType = ViewportType.KeepAspectInteger;
|
||||
ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
|
||||
|
||||
// Button rectangles
|
||||
decreaseResolutionButton = new Rectangle(200, 30, 10, 10);
|
||||
increaseResolutionButton = new Rectangle(215, 30, 10, 10);
|
||||
decreaseTypeButton = new Rectangle(200, 45, 10, 10);
|
||||
increaseTypeButton = new Rectangle(215, 45, 10, 10);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsWindowResized()) ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
|
||||
|
||||
Vector2 mousePosition = GetMousePosition();
|
||||
bool mousePressed = IsMouseButtonPressed(MouseButton.Left);
|
||||
|
||||
// Check buttons and rescale
|
||||
if (CheckCollisionPointRec(mousePosition, decreaseResolutionButton) && mousePressed)
|
||||
{
|
||||
resolutionIndex = (resolutionIndex + ResolutionCount - 1) % ResolutionCount;
|
||||
gameWidth = (int)resolutionList[resolutionIndex].X;
|
||||
gameHeight = (int)resolutionList[resolutionIndex].Y;
|
||||
ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
|
||||
}
|
||||
|
||||
if (CheckCollisionPointRec(mousePosition, increaseResolutionButton) && mousePressed)
|
||||
{
|
||||
resolutionIndex = (resolutionIndex + 1) % ResolutionCount;
|
||||
gameWidth = (int)resolutionList[resolutionIndex].X;
|
||||
gameHeight = (int)resolutionList[resolutionIndex].Y;
|
||||
ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
|
||||
}
|
||||
|
||||
if (CheckCollisionPointRec(mousePosition, decreaseTypeButton) && mousePressed)
|
||||
{
|
||||
viewportType = (ViewportType)(((int)viewportType + (int)ViewportType.ViewportTypeCount - 1) % (int)ViewportType.ViewportTypeCount);
|
||||
ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
|
||||
}
|
||||
|
||||
if (CheckCollisionPointRec(mousePosition, increaseTypeButton) && mousePressed)
|
||||
{
|
||||
viewportType = (ViewportType)(((int)viewportType + 1) % (int)ViewportType.ViewportTypeCount);
|
||||
ResizeRenderSize(viewportType, ref curScreenWidth, ref curScreenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect, ref target);
|
||||
}
|
||||
|
||||
Vector2 textureMousePosition = Screen2RenderTexturePosition(mousePosition, sourceRect, destRect);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
// Draw our scene to the render texture
|
||||
BeginTextureMode(target);
|
||||
ClearBackground(Color.White);
|
||||
DrawCircleV(textureMousePosition, 20.0f, Color.Lime);
|
||||
EndTextureMode();
|
||||
|
||||
// Draw render texture to main framebuffer
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.Black);
|
||||
|
||||
// Draw our render texture with rotation applied
|
||||
DrawTexturePro(target.Texture, sourceRect, destRect, new Vector2(0.0f, 0.0f), 0.0f, Color.White);
|
||||
|
||||
// Draw Native resolution (GUI or anything)
|
||||
// Draw info box
|
||||
Rectangle infoRect = new Rectangle(5, 5, 330, 105);
|
||||
DrawRectangleRec(infoRect, Fade(Color.LightGray, 0.7f));
|
||||
DrawRectangleLinesEx(infoRect, 1, Color.Blue);
|
||||
|
||||
DrawText($"Window Resolution: {curScreenWidth} x {curScreenHeight}", 15, 15, 10, Color.Black);
|
||||
DrawText($"Game Resolution: {gameWidth} x {gameHeight}", 15, 30, 10, Color.Black);
|
||||
|
||||
DrawText($"Type: {ViewportTypeNames[(int)viewportType]}", 15, 45, 10, Color.Black);
|
||||
Vector2 scaleRatio = new Vector2(destRect.Width / sourceRect.Width, -destRect.Height / sourceRect.Height);
|
||||
if (scaleRatio.X < 0.001f || scaleRatio.Y < 0.001f) DrawText("Scale ratio: INVALID", 15, 60, 10, Color.Black);
|
||||
else DrawText($"Scale ratio: {scaleRatio.X:F2} x {scaleRatio.Y:F2}", 15, 60, 10, Color.Black);
|
||||
|
||||
DrawText($"Source size: {sourceRect.Width:F2} x {-sourceRect.Height:F2}", 15, 75, 10, Color.Black);
|
||||
DrawText($"Destination size: {destRect.Width:F2} x {destRect.Height:F2}", 15, 90, 10, Color.Black);
|
||||
|
||||
// Draw buttons
|
||||
DrawRectangleRec(decreaseTypeButton, Color.SkyBlue);
|
||||
DrawRectangleRec(increaseTypeButton, Color.SkyBlue);
|
||||
DrawRectangleRec(decreaseResolutionButton, Color.SkyBlue);
|
||||
DrawRectangleRec(increaseResolutionButton, Color.SkyBlue);
|
||||
DrawText("<", (int)decreaseTypeButton.X + 3, (int)decreaseTypeButton.Y + 1, 10, Color.Black);
|
||||
DrawText(">", (int)increaseTypeButton.X + 3, (int)increaseTypeButton.Y + 1, 10, Color.Black);
|
||||
DrawText("<", (int)decreaseResolutionButton.X + 3, (int)decreaseResolutionButton.Y + 1, 10, Color.Black);
|
||||
DrawText(">", (int)increaseResolutionButton.X + 3, (int)increaseResolutionButton.Y + 1, 10, Color.Black);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadRenderTexture(target);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//--------------------------------------------------------------------------------------
|
||||
private static void KeepAspectCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
|
||||
{
|
||||
sourceRect.X = 0.0f;
|
||||
sourceRect.Y = (float)gameHeight;
|
||||
sourceRect.Width = (float)gameWidth;
|
||||
sourceRect.Height = (float)-gameHeight;
|
||||
|
||||
int ratioX = (screenWidth / gameWidth);
|
||||
int ratioY = (screenHeight / gameHeight);
|
||||
float resizeRatio = (float)((ratioX < ratioY) ? ratioX : ratioY);
|
||||
|
||||
destRect.X = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5f);
|
||||
destRect.Y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5f);
|
||||
destRect.Width = (float)(int)(gameWidth * resizeRatio);
|
||||
destRect.Height = (float)(int)(gameHeight * resizeRatio);
|
||||
}
|
||||
|
||||
private static void KeepHeightCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
|
||||
{
|
||||
float resizeRatio = (float)screenHeight / gameHeight;
|
||||
sourceRect.X = 0.0f;
|
||||
sourceRect.Y = 0.0f;
|
||||
sourceRect.Width = (float)(int)(screenWidth / resizeRatio);
|
||||
sourceRect.Height = (float)-gameHeight;
|
||||
|
||||
destRect.X = (float)(int)((screenWidth - (sourceRect.Width * resizeRatio)) * 0.5f);
|
||||
destRect.Y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5f);
|
||||
destRect.Width = (float)(int)(sourceRect.Width * resizeRatio);
|
||||
destRect.Height = (float)(int)(gameHeight * resizeRatio);
|
||||
}
|
||||
|
||||
private static void KeepWidthCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
|
||||
{
|
||||
float resizeRatio = (float)screenWidth / gameWidth;
|
||||
sourceRect.X = 0.0f;
|
||||
sourceRect.Y = 0.0f;
|
||||
sourceRect.Width = (float)gameWidth;
|
||||
sourceRect.Height = (float)(int)(screenHeight / resizeRatio);
|
||||
|
||||
destRect.X = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5f);
|
||||
destRect.Y = (float)(int)((screenHeight - (sourceRect.Height * resizeRatio)) * 0.5f);
|
||||
destRect.Width = (float)(int)(gameWidth * resizeRatio);
|
||||
destRect.Height = (float)(int)(sourceRect.Height * resizeRatio);
|
||||
|
||||
sourceRect.Height *= -1.0f;
|
||||
}
|
||||
|
||||
private static void KeepAspectCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
|
||||
{
|
||||
sourceRect.X = 0.0f;
|
||||
sourceRect.Y = (float)gameHeight;
|
||||
sourceRect.Width = (float)gameWidth;
|
||||
sourceRect.Height = (float)-gameHeight;
|
||||
|
||||
float ratioX = ((float)screenWidth / (float)gameWidth);
|
||||
float ratioY = ((float)screenHeight / (float)gameHeight);
|
||||
float resizeRatio = (ratioX < ratioY ? ratioX : ratioY);
|
||||
|
||||
destRect.X = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5f);
|
||||
destRect.Y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5f);
|
||||
destRect.Width = (float)(int)(gameWidth * resizeRatio);
|
||||
destRect.Height = (float)(int)(gameHeight * resizeRatio);
|
||||
}
|
||||
|
||||
private static void KeepHeightCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
|
||||
{
|
||||
float resizeRatio = ((float)screenHeight / (float)gameHeight);
|
||||
sourceRect.X = 0.0f;
|
||||
sourceRect.Y = 0.0f;
|
||||
sourceRect.Width = (float)(int)((float)screenWidth / resizeRatio);
|
||||
sourceRect.Height = (float)-gameHeight;
|
||||
|
||||
destRect.X = (float)(int)((screenWidth - (sourceRect.Width * resizeRatio)) * 0.5f);
|
||||
destRect.Y = (float)(int)((screenHeight - (gameHeight * resizeRatio)) * 0.5f);
|
||||
destRect.Width = (float)(int)(sourceRect.Width * resizeRatio);
|
||||
destRect.Height = (float)(int)(gameHeight * resizeRatio);
|
||||
}
|
||||
|
||||
private static void KeepWidthCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect)
|
||||
{
|
||||
float resizeRatio = ((float)screenWidth / (float)gameWidth);
|
||||
sourceRect.X = 0.0f;
|
||||
sourceRect.Y = 0.0f;
|
||||
sourceRect.Width = (float)gameWidth;
|
||||
sourceRect.Height = (float)(int)((float)screenHeight / resizeRatio);
|
||||
|
||||
destRect.X = (float)(int)((screenWidth - (gameWidth * resizeRatio)) * 0.5f);
|
||||
destRect.Y = (float)(int)((screenHeight - (sourceRect.Height * resizeRatio)) * 0.5f);
|
||||
destRect.Width = (float)(int)(gameWidth * resizeRatio);
|
||||
destRect.Height = (float)(int)(sourceRect.Height * resizeRatio);
|
||||
|
||||
sourceRect.Height *= -1.0f;
|
||||
}
|
||||
|
||||
private static void ResizeRenderSize(ViewportType viewportType, ref int screenWidth, ref int screenHeight, int gameWidth, int gameHeight, ref Rectangle sourceRect, ref Rectangle destRect, ref RenderTexture2D target)
|
||||
{
|
||||
screenWidth = GetScreenWidth();
|
||||
screenHeight = GetScreenHeight();
|
||||
|
||||
switch (viewportType)
|
||||
{
|
||||
case ViewportType.KeepAspectInteger: KeepAspectCenteredInteger(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
|
||||
case ViewportType.KeepHeightInteger: KeepHeightCenteredInteger(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
|
||||
case ViewportType.KeepWidthInteger: KeepWidthCenteredInteger(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
|
||||
case ViewportType.KeepAspect: KeepAspectCentered(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
|
||||
case ViewportType.KeepHeight: KeepHeightCentered(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
|
||||
case ViewportType.KeepWidth: KeepWidthCentered(screenWidth, screenHeight, gameWidth, gameHeight, ref sourceRect, ref destRect); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
UnloadRenderTexture(target);
|
||||
target = LoadRenderTexture((int)sourceRect.Width, -(int)sourceRect.Height);
|
||||
}
|
||||
|
||||
// Example how to calculate position on RenderTexture
|
||||
private static Vector2 Screen2RenderTexturePosition(Vector2 point, Rectangle textureRect, Rectangle scaledRect)
|
||||
{
|
||||
Vector2 relativePosition = new Vector2(point.X - scaledRect.X, point.Y - scaledRect.Y);
|
||||
Vector2 ratio = new Vector2(textureRect.Width / scaledRect.Width, -textureRect.Height / scaledRect.Height);
|
||||
|
||||
return new Vector2(relativePosition.X * ratio.X, relativePosition.Y * ratio.X);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//---------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.ResizableWindow);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - viewport scaling");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//----------------------------------------------------------
|
||||
|
||||
var game = new ViewportScaling();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ using static Raylib_cs.ConfigFlags;
|
|||
|
||||
namespace Examples.Core;
|
||||
|
||||
[ExcludeFromBrowser("runtime window-state flags don't apply to the emscripten canvas")]
|
||||
public partial class WindowFlags : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
|
|
|
|||
130
Examples/Core/WindowShouldClose.cs
Normal file
130
Examples/Core/WindowShouldClose.cs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [core] example - window should close
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 4.2, last time updated with raylib 4.2
|
||||
*
|
||||
* 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) 2013-2025 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Core;
|
||||
|
||||
public partial class WindowShouldClose : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Core / Window Should Close";
|
||||
|
||||
public string Title => "raylib [core] example - window should close";
|
||||
|
||||
// The runner drives its loop off this instead of WindowShouldClose(), so the exit
|
||||
// confirmation (polled in Update) can intercept both the X-button and KEY_ESCAPE.
|
||||
public bool ShouldClose => exitWindow;
|
||||
|
||||
private bool exitWindowRequested; // Flag to request window to exit
|
||||
private bool exitWindow; // Flag to set window to exit
|
||||
|
||||
public void Init()
|
||||
{
|
||||
SetExitKey(KeyboardKey.Null); // Disable KEY_ESCAPE to close window, X-button still works
|
||||
|
||||
exitWindowRequested = false;
|
||||
exitWindow = false;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Detect if X-button or KEY_ESCAPE have been pressed to close window
|
||||
bool closeRequested = IsKeyPressed(KeyboardKey.Escape);
|
||||
#if !BROWSER
|
||||
// WindowShouldClose() polls the OS window close button, but on the wasm target it calls
|
||||
// emscripten_sleep (ASYNCIFY is not enabled) and the canvas has no window chrome anyway,
|
||||
// so on web the confirmation is triggered by KEY_ESCAPE only.
|
||||
closeRequested |= Raylib.WindowShouldClose();
|
||||
#endif
|
||||
if (closeRequested)
|
||||
{
|
||||
exitWindowRequested = true;
|
||||
}
|
||||
|
||||
if (exitWindowRequested)
|
||||
{
|
||||
// A request for close window has been issued, we can save data before closing
|
||||
// or just show a message asking for confirmation
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Y))
|
||||
{
|
||||
exitWindow = true;
|
||||
}
|
||||
else if (IsKeyPressed(KeyboardKey.N))
|
||||
{
|
||||
exitWindowRequested = false;
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
if (exitWindowRequested)
|
||||
{
|
||||
DrawRectangle(0, 100, screenWidth, 200, Color.Black);
|
||||
DrawText("Are you sure you want to exit program? [Y/N]", 40, 180, 30, Color.White);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawText("Try to close the window to get confirmation message!", 120, 200, 20, Color.LightGray);
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [core] example - window should close");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new WindowShouldClose();
|
||||
game.Init();
|
||||
|
||||
// Main game loop
|
||||
while (!game.exitWindow)
|
||||
{
|
||||
game.Update();
|
||||
}
|
||||
|
||||
game.Unload();
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
CloseWindow(); // Close window and OpenGL context
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using Examples.Core;
|
||||
using Examples.Models;
|
||||
using Examples.Shapes;
|
||||
|
||||
namespace Examples;
|
||||
|
||||
/// <summary>
|
||||
/// Discovers every <see cref="IExample"/> implementation in this assembly via reflection and
|
||||
/// derives the per-platform lists: <see cref="DesktopExamples"/> (Program.cs) and
|
||||
/// <see cref="BrowserExamples"/> (Web/Host.cs). Ordering is by category (browser dropdown
|
||||
/// grouping), then display name.
|
||||
/// Discovers every <see cref="IExample"/> implementation in this assembly via reflection:
|
||||
/// <see cref="DesktopExamples"/> (Program.cs) is all of them, <see cref="BrowserExamples"/>
|
||||
/// (Web/Host.cs) is everything not marked <see cref="ExcludeFromBrowserAttribute"/>.
|
||||
/// Ordering is by category (browser dropdown grouping), then display name.
|
||||
/// </summary>
|
||||
public static class ExampleRegistry
|
||||
{
|
||||
|
|
@ -25,29 +22,12 @@ public static class ExampleRegistry
|
|||
"Shaders",
|
||||
];
|
||||
|
||||
/// <summary>Desktop examples omitted from the browser host (platform limitations).</summary>
|
||||
private static readonly Type[] DesktopExcludedFromBrowser =
|
||||
[
|
||||
typeof(DropFiles),
|
||||
typeof(LoadingThread), // System.Threading.Thread is unsupported on single-threaded wasm
|
||||
typeof(SkyboxDemo),
|
||||
];
|
||||
|
||||
/// <summary>Browser-only shape examples not registered for desktop CLI runs.</summary>
|
||||
private static readonly Type[] BrowserOnly =
|
||||
[
|
||||
typeof(DrawCircleSector),
|
||||
typeof(DrawRectangleRounded),
|
||||
typeof(DrawRing),
|
||||
];
|
||||
|
||||
private static readonly IExample[] AllExamples = DiscoverAll();
|
||||
|
||||
public static readonly IExample[] DesktopExamples =
|
||||
Array.FindAll(AllExamples, e => Array.IndexOf(BrowserOnly, e.GetType()) < 0);
|
||||
public static readonly IExample[] DesktopExamples = AllExamples;
|
||||
|
||||
public static readonly IExample[] BrowserExamples =
|
||||
Array.FindAll(AllExamples, e => Array.IndexOf(DesktopExcludedFromBrowser, e.GetType()) < 0);
|
||||
Array.FindAll(AllExamples, e => !e.GetType().IsDefined(typeof(ExcludeFromBrowserAttribute), inherit: false));
|
||||
|
||||
[UnconditionalSuppressMessage("Trimming", "IL2026",
|
||||
Justification = "The Examples assembly is rooted via TrimmerRootAssembly in Examples.csproj.")]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
|
||||
<!-- The wasm build targets net10 only: net8 + browser-wasm needs the retired
|
||||
wasm-tools-net8 workload, whereas net10 uses the current wasm-tools workload. -->
|
||||
<TargetFrameworks Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">net10.0</TargetFrameworks>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<StartupObject Condition="'$(RuntimeIdentifier)' != 'browser-wasm'">Examples.Program</StartupObject>
|
||||
<StartupObject Condition="'$(RuntimeIdentifier)' == 'browser-wasm'">Examples.Web.Host</StartupObject>
|
||||
|
|
@ -31,10 +34,6 @@
|
|||
<WasmEmitSymbolMap Condition="'$(WasmEmitSymbolMap)' == ''">false</WasmEmitSymbolMap>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Text/Unicode.cs"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(RuntimeIdentifier)' != 'browser-wasm'">
|
||||
<Compile Remove="Web/**/*.cs"/>
|
||||
</ItemGroup>
|
||||
|
|
|
|||
17
Examples/ExcludeFromBrowserAttribute.cs
Normal file
17
Examples/ExcludeFromBrowserAttribute.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace Examples;
|
||||
|
||||
/// <summary>
|
||||
/// Marks an <see cref="IExample"/> that is omitted from the browser host (Web/Host.cs)
|
||||
/// because of a wasm/WebGL platform limitation. Desktop runs are unaffected.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
public sealed class ExcludeFromBrowserAttribute : Attribute
|
||||
{
|
||||
/// <summary>Why the example cannot run in the browser.</summary>
|
||||
public string Reason { get; }
|
||||
|
||||
public ExcludeFromBrowserAttribute(string reason = null)
|
||||
{
|
||||
Reason = reason;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,14 @@
|
|||
namespace Examples;
|
||||
|
||||
/// <summary>
|
||||
/// A runnable raylib example. Each example class implements this interface directly: loop-spanning
|
||||
/// state from the original example's Main lives in instance fields, (re)initialized in
|
||||
/// A runnable raylib example. Loop-spanning state lives in instance fields, (re)initialized in
|
||||
/// <see cref="Init"/> so re-selecting an example resets it.
|
||||
///
|
||||
/// <para>
|
||||
/// Desktop runs the example via its <c>static Main()</c>, a thin driver that owns the window
|
||||
/// (InitWindow/CloseWindow, SetTargetFPS and friends) and drives <see cref="Init"/>,
|
||||
/// <see cref="Update"/>, and <see cref="Unload"/> around a blocking
|
||||
/// <c>while (!WindowShouldClose())</c> loop. In the browser, <c>Web/Host.cs</c> owns the single
|
||||
/// window and calls <see cref="Update"/> one frame at a time from JavaScript, so examples never
|
||||
/// block. Platform divergences (e.g. GLSL 100 vs 330 shaders) are guarded with <c>#if BROWSER</c>,
|
||||
/// preferably around a single constant so both platforms share one code path.
|
||||
/// Desktop drives examples via <c>Program.cs</c> (each also keeps a thin standalone
|
||||
/// <c>static Main()</c>); in the browser, <c>Web/Host.cs</c> owns the single window and calls
|
||||
/// <see cref="Update"/> one frame at a time from JavaScript. Platform divergences are guarded
|
||||
/// with <c>#if BROWSER</c>, preferably around a single constant.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IExample
|
||||
|
|
@ -23,19 +19,32 @@ public interface IExample
|
|||
/// <summary>Window title, matching the example's standalone <c>Main()</c>.</summary>
|
||||
string Title { get; }
|
||||
|
||||
/// <summary>Config flags the desktop runner applies before window creation.</summary>
|
||||
/// <summary>Desktop window size, matching the standalone <c>Main()</c>. The browser canvas is fixed at 800x450.</summary>
|
||||
int Width => 800;
|
||||
|
||||
/// <inheritdoc cref="Width"/>
|
||||
int Height => 450;
|
||||
|
||||
/// <summary>Config flags the desktop runner applies before window creation. Ignored in the browser.</summary>
|
||||
ConfigFlags ConfigFlags => 0;
|
||||
|
||||
/// <summary>Target FPS the desktop runner sets after window creation.</summary>
|
||||
/// <summary>Target FPS. The desktop runner always applies it; the browser host paces its frame loop with it.</summary>
|
||||
int TargetFps => 60;
|
||||
|
||||
/// <summary>Whether the desktop runner disables the cursor (relative mouse movement).</summary>
|
||||
/// <summary>Whether the desktop runner disables the cursor. Ignored in the browser (the pointer stays visible).</summary>
|
||||
bool CursorDisabled => false;
|
||||
|
||||
/// <summary>Whether the desktop runner hides the cursor.</summary>
|
||||
/// <summary>Whether the desktop runner hides the cursor. Ignored in the browser.</summary>
|
||||
bool CursorHidden => false;
|
||||
|
||||
/// <summary>One-time setup.</summary>
|
||||
/// <summary>
|
||||
/// Whether the desktop runner should exit its frame loop; defaults to
|
||||
/// <see cref="Raylib.WindowShouldClose"/>. Examples that intercept the close request
|
||||
/// override this and poll WindowShouldClose in <see cref="Update"/> themselves.
|
||||
/// </summary>
|
||||
bool ShouldClose => WindowShouldClose();
|
||||
|
||||
/// <summary>Set up the Example and preload necessary resources.</summary>
|
||||
void Init();
|
||||
|
||||
/// <summary>Render one frame, including BeginDrawing/EndDrawing.</summary>
|
||||
|
|
|
|||
390
Examples/Models/AnimationBlendCustom.cs
Normal file
390
Examples/Models/AnimationBlendCustom.cs
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - animation blend custom
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by dmitrii-brand (@dmitrii-brand) and reviewed by Ramon Santamaria (@raysan5)
|
||||
*
|
||||
* DETAILS: Example demonstrates per-bone animation blending, allowing smooth transitions
|
||||
* between two animations by interpolating bone transforms. This is useful for:
|
||||
* - Blending movement animations (walk/run) with action animations (jump/attack)
|
||||
* - Creating smooth animation transitions
|
||||
* - Layering animations (e.g., upper body attack while lower body walks)
|
||||
*
|
||||
* WARNING: GPU skinning must be enabled in raylib with a compilation flag,
|
||||
* if not enabled, CPU skinning will be used instead
|
||||
*
|
||||
* 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) 2026 dmitrii-brand (@dmitrii-brand)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class AnimationBlendCustom : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
private const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Models / Animation Blend Custom";
|
||||
|
||||
public string Title => "raylib [models] example - animation blend custom";
|
||||
|
||||
private Camera3D camera;
|
||||
private Model model;
|
||||
private Vector3 position;
|
||||
private Shader skinningShader;
|
||||
private unsafe ModelAnimation* anims;
|
||||
private int animCount;
|
||||
private int animIndex0;
|
||||
private int animIndex1;
|
||||
private int animCurrentFrame0;
|
||||
private int animCurrentFrame1;
|
||||
private bool upperBodyBlend;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(4.0f, 4.0f, 4.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 1.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
// Load gltf model
|
||||
model = LoadModel("resources/models/gltf/greenman.glb");
|
||||
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model position
|
||||
|
||||
// Load skinning shader
|
||||
// WARNING: GPU skinning must be enabled in raylib with a compilation flag,
|
||||
// if not enabled, CPU skinning will be used instead
|
||||
skinningShader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/skinning.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/skinning.fs"
|
||||
);
|
||||
model.Materials[1].Shader = skinningShader;
|
||||
|
||||
// Load gltf model animations
|
||||
animCount = 0;
|
||||
anims = LoadModelAnimations("resources/models/gltf/greenman.glb", ref animCount);
|
||||
|
||||
// Use specific animation indices: 2-walk/move, 3-attack
|
||||
animIndex0 = 2; // Walk/Move animation (index 2)
|
||||
animIndex1 = 3; // Attack animation (index 3)
|
||||
animCurrentFrame0 = 0;
|
||||
animCurrentFrame1 = 0;
|
||||
|
||||
// Validate indices
|
||||
if (animIndex0 >= animCount)
|
||||
{
|
||||
animIndex0 = 0;
|
||||
}
|
||||
if (animIndex1 >= animCount)
|
||||
{
|
||||
animIndex1 = (animCount > 1) ? 1 : 0;
|
||||
}
|
||||
|
||||
upperBodyBlend = true; // Toggle: true = upper/lower body blending, false = uniform blending (50/50)
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
// Toggle upper/lower body blending mode (SPACE key)
|
||||
if (IsKeyPressed(KeyboardKey.Space))
|
||||
{
|
||||
upperBodyBlend = !upperBodyBlend;
|
||||
}
|
||||
|
||||
// Update animation frames
|
||||
var anim0 = anims[animIndex0];
|
||||
var anim1 = anims[animIndex1];
|
||||
|
||||
animCurrentFrame0 = (animCurrentFrame0 + 1) % anim0.KeyFrameCount;
|
||||
animCurrentFrame1 = (animCurrentFrame1 + 1) % anim1.KeyFrameCount;
|
||||
|
||||
// Blend the two animations
|
||||
// When upperBodyBlend is ON: upper body = attack (1.0), lower body = walk (0.0)
|
||||
// When upperBodyBlend is OFF: uniform blend at 0.5 (50% walk, 50% attack)
|
||||
var blendFactor = upperBodyBlend ? 1.0f : 0.5f;
|
||||
UpdateModelAnimationBones(anim0, animCurrentFrame0, anim1, animCurrentFrame1, blendFactor, upperBodyBlend);
|
||||
|
||||
// raylib provided animation blending function
|
||||
//UpdateModelAnimationEx(model, anim0, (float)animCurrentFrame0,
|
||||
// anim1, (float)animCurrentFrame1, blendFactor);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
DrawModel(model, position, 1.0f, Color.White);
|
||||
|
||||
DrawGrid(10, 1.0f);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
// Draw UI
|
||||
DrawText($"ANIM 0: {anim0.NameToString()}", 10, 10, 20, Color.Gray);
|
||||
DrawText($"ANIM 1: {anim1.NameToString()}", 10, 40, 20, Color.Gray);
|
||||
DrawText($"[SPACE] Toggle blending mode: {(upperBodyBlend ? "Upper/Lower Body Blending" : "Uniform Blending")}",
|
||||
10, GetScreenHeight() - 30, 20, Color.DarkGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public unsafe void Unload()
|
||||
{
|
||||
UnloadModelAnimations(anims, animCount); // Unload model animation
|
||||
UnloadModel(model); // Unload model and meshes/material
|
||||
UnloadShader(skinningShader); // Unload GPU skinning shader
|
||||
}
|
||||
|
||||
// Check if a bone is part of upper body (for selective blending)
|
||||
private static bool IsUpperBodyBone(string boneName)
|
||||
{
|
||||
// Common upper body bone names (adjust based on your model)
|
||||
if (boneName is "spine" or "spine1" or "spine2" or
|
||||
"chest" or "upperChest" or
|
||||
"neck" or "head" or
|
||||
"shoulder" or "shoulder_L" or "shoulder_R" or
|
||||
"upperArm" or "upperArm_L" or "upperArm_R" or
|
||||
"lowerArm" or "lowerArm_L" or "lowerArm_R" or
|
||||
"hand" or "hand_L" or "hand_R" or
|
||||
"clavicle" or "clavicle_L" or "clavicle_R")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if bone name contains upper body keywords
|
||||
if (boneName.Contains("spine") || boneName.Contains("chest") ||
|
||||
boneName.Contains("neck") || boneName.Contains("head") ||
|
||||
boneName.Contains("shoulder") || boneName.Contains("arm") ||
|
||||
boneName.Contains("hand") || boneName.Contains("clavicle"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Blend two animations per-bone with selective upper/lower body blending
|
||||
private unsafe void UpdateModelAnimationBones(ModelAnimation anim0, int frame0,
|
||||
ModelAnimation anim1, int frame1, float blend, bool upperBodyBlend)
|
||||
{
|
||||
// Validate inputs
|
||||
if ((anim0.BoneCount != 0) && (anim0.KeyframePoses != null) &&
|
||||
(anim1.BoneCount != 0) && (anim1.KeyframePoses != null) &&
|
||||
(model.Skeleton.BoneCount != 0) && (model.Skeleton.BindPose != null))
|
||||
{
|
||||
// Clamp blend factor to [0, 1]
|
||||
blend = MathF.Min(1.0f, MathF.Max(0.0f, blend));
|
||||
|
||||
// Ensure frame indices are valid
|
||||
if (frame0 >= anim0.KeyFrameCount)
|
||||
{
|
||||
frame0 = anim0.KeyFrameCount - 1;
|
||||
}
|
||||
if (frame1 >= anim1.KeyFrameCount)
|
||||
{
|
||||
frame1 = anim1.KeyFrameCount - 1;
|
||||
}
|
||||
if (frame0 < 0)
|
||||
{
|
||||
frame0 = 0;
|
||||
}
|
||||
if (frame1 < 0)
|
||||
{
|
||||
frame1 = 0;
|
||||
}
|
||||
|
||||
// Get bone count (use minimum of all to be safe)
|
||||
var boneCount = model.Skeleton.BoneCount;
|
||||
if (anim0.BoneCount < boneCount)
|
||||
{
|
||||
boneCount = anim0.BoneCount;
|
||||
}
|
||||
if (anim1.BoneCount < boneCount)
|
||||
{
|
||||
boneCount = anim1.BoneCount;
|
||||
}
|
||||
|
||||
// Blend each bone
|
||||
for (var boneIndex = 0; boneIndex < boneCount; boneIndex++)
|
||||
{
|
||||
// Determine blend factor for this bone
|
||||
var boneBlendFactor = blend;
|
||||
|
||||
// If upper body blending is enabled, use different blend factors for upper vs lower body
|
||||
if (upperBodyBlend)
|
||||
{
|
||||
var boneName = model.Skeleton.Bones[boneIndex].NameToString();
|
||||
var isUpperBody = IsUpperBodyBone(boneName);
|
||||
|
||||
// Upper body: use anim1 (attack), Lower body: use anim0 (walk)
|
||||
// blend = 0.0 means full anim0 (walk), 1.0 means full anim1 (attack)
|
||||
if (isUpperBody)
|
||||
{
|
||||
boneBlendFactor = blend; // Upper body: blend towards anim1 (attack)
|
||||
}
|
||||
else
|
||||
{
|
||||
boneBlendFactor = 1.0f - blend; // Lower body: blend towards anim0 (walk) - invert the blend
|
||||
}
|
||||
}
|
||||
|
||||
// Get transforms from both animations
|
||||
var bindTransform = model.Skeleton.BindPose[boneIndex];
|
||||
var animTransform0 = anim0.KeyframePoses[frame0][boneIndex];
|
||||
var animTransform1 = anim1.KeyframePoses[frame1][boneIndex];
|
||||
|
||||
// Blend the transforms
|
||||
Transform blended = new();
|
||||
blended.Translation = Vector3Lerp(animTransform0.Translation, animTransform1.Translation, boneBlendFactor);
|
||||
blended.Rotation = QuaternionSlerp(animTransform0.Rotation, animTransform1.Rotation, boneBlendFactor);
|
||||
blended.Scale = Vector3Lerp(animTransform0.Scale, animTransform1.Scale, boneBlendFactor);
|
||||
|
||||
// Convert bind pose to matrix
|
||||
var bindMatrix = MatrixMultiply(MatrixMultiply(
|
||||
MatrixScale(bindTransform.Scale.X, bindTransform.Scale.Y, bindTransform.Scale.Z),
|
||||
QuaternionToMatrix(bindTransform.Rotation)),
|
||||
MatrixTranslate(bindTransform.Translation.X, bindTransform.Translation.Y, bindTransform.Translation.Z));
|
||||
|
||||
// Convert blended transform to matrix
|
||||
var blendedMatrix = MatrixMultiply(MatrixMultiply(
|
||||
MatrixScale(blended.Scale.X, blended.Scale.Y, blended.Scale.Z),
|
||||
QuaternionToMatrix(blended.Rotation)),
|
||||
MatrixTranslate(blended.Translation.X, blended.Translation.Y, blended.Translation.Z));
|
||||
|
||||
// Calculate final bone matrix (similar to UpdateModelAnimationBones)
|
||||
model.BoneMatrices[boneIndex] = MatrixMultiply(MatrixInvert(bindMatrix), blendedMatrix);
|
||||
}
|
||||
|
||||
// CPU skinning, updates CPU buffers and uploads them to GPU (if available)
|
||||
// NOTE: Fallback in case GPU skinning is not supported or enabled
|
||||
for (var m = 0; m < model.MeshCount; m++)
|
||||
{
|
||||
var mesh = model.Meshes[m];
|
||||
Vector3 animVertex;
|
||||
Vector3 animNormal;
|
||||
var vertexValuesCount = mesh.VertexCount * 3;
|
||||
|
||||
var boneCounter = 0;
|
||||
var bufferUpdateRequired = false; // Flag to check when anim vertex information is updated
|
||||
|
||||
// Skip if missing bone data or missing anim buffers initialization
|
||||
if ((mesh.BoneWeights == null) || (mesh.BoneIndices == null) ||
|
||||
(mesh.AnimVertices == null) || (mesh.AnimNormals == null))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var vCounter = 0; vCounter < vertexValuesCount; vCounter += 3)
|
||||
{
|
||||
mesh.AnimVertices[vCounter] = 0;
|
||||
mesh.AnimVertices[vCounter + 1] = 0;
|
||||
mesh.AnimVertices[vCounter + 2] = 0;
|
||||
if (mesh.AnimNormals != null)
|
||||
{
|
||||
mesh.AnimNormals[vCounter] = 0;
|
||||
mesh.AnimNormals[vCounter + 1] = 0;
|
||||
mesh.AnimNormals[vCounter + 2] = 0;
|
||||
}
|
||||
|
||||
// Iterates over 4 bones per vertex
|
||||
for (var j = 0; j < 4; j++, boneCounter++)
|
||||
{
|
||||
var boneWeight = mesh.BoneWeights[boneCounter];
|
||||
var boneIndex = mesh.BoneIndices[boneCounter];
|
||||
|
||||
// Early stop when no transformation will be applied
|
||||
if (boneWeight == 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
animVertex = new Vector3(mesh.Vertices[vCounter], mesh.Vertices[vCounter + 1], mesh.Vertices[vCounter + 2]);
|
||||
animVertex = Vector3Transform(animVertex, model.BoneMatrices[boneIndex]);
|
||||
mesh.AnimVertices[vCounter] += animVertex.X * boneWeight;
|
||||
mesh.AnimVertices[vCounter + 1] += animVertex.Y * boneWeight;
|
||||
mesh.AnimVertices[vCounter + 2] += animVertex.Z * boneWeight;
|
||||
bufferUpdateRequired = true;
|
||||
|
||||
// Normals processing
|
||||
// NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals)
|
||||
if ((mesh.Normals != null) && (mesh.AnimNormals != null))
|
||||
{
|
||||
animNormal = new Vector3(mesh.Normals[vCounter], mesh.Normals[vCounter + 1], mesh.Normals[vCounter + 2]);
|
||||
animNormal = Vector3Transform(animNormal, MatrixTranspose(MatrixInvert(model.BoneMatrices[boneIndex])));
|
||||
mesh.AnimNormals[vCounter] += animNormal.X * boneWeight;
|
||||
mesh.AnimNormals[vCounter + 1] += animNormal.Y * boneWeight;
|
||||
mesh.AnimNormals[vCounter + 2] += animNormal.Z * boneWeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bufferUpdateRequired)
|
||||
{
|
||||
// Update GPU vertex buffers with updated data (position + normals)
|
||||
Rlgl.UpdateVertexBuffer(mesh.VboId[(int)ShaderLocationIndex.VertexPosition], mesh.AnimVertices, mesh.VertexCount * 3 * sizeof(float), 0);
|
||||
if (mesh.Normals != null)
|
||||
{
|
||||
Rlgl.UpdateVertexBuffer(mesh.VboId[(int)ShaderLocationIndex.VertexNormal], mesh.AnimNormals, mesh.VertexCount * 3 * sizeof(float), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - animation blend custom");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new AnimationBlendCustom();
|
||||
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;
|
||||
}
|
||||
}
|
||||
474
Examples/Models/AnimationBlending.cs
Normal file
474
Examples/Models/AnimationBlending.cs
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - animation blending
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Kirandeep (@Kirandeep-Singh-Khehra) and reviewed by Ramon Santamaria (@raysan5)
|
||||
*
|
||||
* WARNING: GPU skinning must be enabled in raylib with a compilation flag,
|
||||
* if not enabled, CPU skinning will be used instead
|
||||
*
|
||||
* 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) 2024-2026 Kirandeep (@Kirandeep-Singh-Khehra) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
// NOTE: The upstream example uses raygui for its UI controls (dropdowns, sliders, progress bars).
|
||||
// raygui is not part of raylib-cs, so the required controls are reimplemented here with
|
||||
// basic raylib drawing primitives, preserving the original behaviour.
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class AnimationBlending : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
private const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Models / Animation Blending";
|
||||
|
||||
public string Title => "raylib [models] example - animation blending";
|
||||
|
||||
private Camera3D camera;
|
||||
private Model model;
|
||||
private Vector3 position;
|
||||
private Shader skinningShader;
|
||||
private unsafe ModelAnimation* anims;
|
||||
private int animCount;
|
||||
|
||||
private int currentAnimPlaying;
|
||||
private int nextAnimToPlay;
|
||||
private bool animTransition;
|
||||
|
||||
private int animIndex0;
|
||||
private float animCurrentFrame0;
|
||||
private float animFrameSpeed0;
|
||||
private int animIndex1;
|
||||
private float animCurrentFrame1;
|
||||
private float animFrameSpeed1;
|
||||
|
||||
private float animBlendFactor;
|
||||
private float animBlendTime;
|
||||
private float animBlendTimeCounter;
|
||||
|
||||
private bool animPause;
|
||||
|
||||
private string[] animNames;
|
||||
private bool dropdownEditMode0;
|
||||
private bool dropdownEditMode1;
|
||||
private float animFrameProgress0;
|
||||
private float animFrameProgress1;
|
||||
private float animBlendProgress;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(6.0f, 6.0f, 6.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 2.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
// Load model
|
||||
model = LoadModel("resources/models/gltf/robot.glb"); // Load character model
|
||||
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model world position
|
||||
|
||||
// Load skinning shader
|
||||
// NOTE: It must be a valid shader, following raylib attribs/uniform conventions for GPU skinning
|
||||
skinningShader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/skinning.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/skinning.fs"
|
||||
);
|
||||
|
||||
// Skinning shader could be required to be assigned to all materials shaders, just to make
|
||||
// sure required uniforms are being updated for the mesh using that material (and shader)
|
||||
for (var i = 0; i < model.MaterialCount; i++)
|
||||
{
|
||||
model.Materials[i].Shader = skinningShader;
|
||||
}
|
||||
|
||||
// Load model animations
|
||||
animCount = 0;
|
||||
anims = LoadModelAnimations("resources/models/gltf/robot.glb", ref animCount);
|
||||
|
||||
// Animation playing variables
|
||||
// NOTE: Two animations are played with a smooth transition between them
|
||||
currentAnimPlaying = 0; // Current animation playing (0 o 1)
|
||||
nextAnimToPlay = 1; // Next animation to play (to transition)
|
||||
animTransition = false; // Flag to register anim transition state
|
||||
|
||||
animIndex0 = 10; // Current animation playing (walking)
|
||||
animCurrentFrame0 = 0.0f; // Current animation frame (supporting interpolated frames)
|
||||
animFrameSpeed0 = 0.5f; // Current animation play speed
|
||||
animIndex1 = 6; // Next animation to play (running)
|
||||
animCurrentFrame1 = 0.0f; // Next animation frame (supporting interpolated frames)
|
||||
animFrameSpeed1 = 0.5f; // Next animation play speed
|
||||
|
||||
animBlendFactor = 0.0f; // Blend factor from anim0[frame0] --> anim1[frame1], [0.0f..1.0f]
|
||||
|
||||
animBlendTime = 2.0f; // Time to blend from one playing animation to another (in seconds)
|
||||
animBlendTimeCounter = 0.0f; // Time counter (delta time)
|
||||
|
||||
animPause = false; // Pause animation
|
||||
|
||||
// UI required variables
|
||||
animNames = new string[animCount];
|
||||
for (var i = 0; i < animCount; i++)
|
||||
{
|
||||
animNames[i] = anims[i].NameToString();
|
||||
}
|
||||
|
||||
dropdownEditMode0 = false;
|
||||
dropdownEditMode1 = false;
|
||||
animFrameProgress0 = 0.0f;
|
||||
animFrameProgress1 = 0.0f;
|
||||
animBlendProgress = 0.0f;
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.P))
|
||||
{
|
||||
animPause = !animPause;
|
||||
}
|
||||
|
||||
if (!animPause)
|
||||
{
|
||||
// Start transition from anim0[] to anim1[]
|
||||
if (IsKeyPressed(KeyboardKey.Space) && !animTransition)
|
||||
{
|
||||
if (currentAnimPlaying == 0)
|
||||
{
|
||||
// Transition anim0 --> anim1
|
||||
nextAnimToPlay = 1;
|
||||
animCurrentFrame1 = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Transition anim1 --> anim0
|
||||
nextAnimToPlay = 0;
|
||||
animCurrentFrame0 = 0.0f;
|
||||
}
|
||||
|
||||
// Set animation transition
|
||||
animTransition = true;
|
||||
animBlendTimeCounter = 0.0f;
|
||||
animBlendFactor = 0.0f;
|
||||
}
|
||||
|
||||
if (animTransition)
|
||||
{
|
||||
// Playing anim0 and anim1 at the same time
|
||||
animCurrentFrame0 += animFrameSpeed0;
|
||||
if (animCurrentFrame0 >= anims[animIndex0].KeyFrameCount)
|
||||
{
|
||||
animCurrentFrame0 = 0.0f;
|
||||
}
|
||||
animCurrentFrame1 += animFrameSpeed1;
|
||||
if (animCurrentFrame1 >= anims[animIndex1].KeyFrameCount)
|
||||
{
|
||||
animCurrentFrame1 = 0.0f;
|
||||
}
|
||||
|
||||
// Increment blend factor over time to transition from anim0 --> anim1 over time
|
||||
// NOTE: Time blending could be other than linear, using some easing
|
||||
animBlendFactor = animBlendTimeCounter / animBlendTime;
|
||||
animBlendTimeCounter += GetFrameTime();
|
||||
animBlendProgress = animBlendFactor;
|
||||
|
||||
// Update model with animations blending
|
||||
if (nextAnimToPlay == 1)
|
||||
{
|
||||
// Blend anim0 --> anim1
|
||||
UpdateModelAnimationEx(model, anims[animIndex0], animCurrentFrame0,
|
||||
anims[animIndex1], animCurrentFrame1, animBlendFactor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Blend anim1 --> anim0
|
||||
UpdateModelAnimationEx(model, anims[animIndex1], animCurrentFrame1,
|
||||
anims[animIndex0], animCurrentFrame0, animBlendFactor);
|
||||
}
|
||||
|
||||
// Check if transition completed
|
||||
if (animBlendFactor > 1.0f)
|
||||
{
|
||||
// Reset frame states
|
||||
if (currentAnimPlaying == 0)
|
||||
{
|
||||
animCurrentFrame0 = 0.0f;
|
||||
}
|
||||
else if (currentAnimPlaying == 1)
|
||||
{
|
||||
animCurrentFrame1 = 0.0f;
|
||||
}
|
||||
currentAnimPlaying = nextAnimToPlay; // Update current animation playing
|
||||
|
||||
animBlendFactor = 0.0f; // Reset blend factor
|
||||
animTransition = false; // Exit transition mode
|
||||
animBlendTimeCounter = 0.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Play only one anim, the current one
|
||||
if (currentAnimPlaying == 0)
|
||||
{
|
||||
// Playing anim0 at defined speed
|
||||
animCurrentFrame0 += animFrameSpeed0;
|
||||
if (animCurrentFrame0 >= anims[animIndex0].KeyFrameCount)
|
||||
{
|
||||
animCurrentFrame0 = 0.0f;
|
||||
}
|
||||
UpdateModelAnimation(model, anims[animIndex0], animCurrentFrame0);
|
||||
}
|
||||
else if (currentAnimPlaying == 1)
|
||||
{
|
||||
// Playing anim1 at defined speed
|
||||
animCurrentFrame1 += animFrameSpeed1;
|
||||
if (animCurrentFrame1 >= anims[animIndex1].KeyFrameCount)
|
||||
{
|
||||
animCurrentFrame1 = 0.0f;
|
||||
}
|
||||
UpdateModelAnimation(model, anims[animIndex1], animCurrentFrame1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update progress bars values with current frame for each animation
|
||||
animFrameProgress0 = animCurrentFrame0;
|
||||
animFrameProgress1 = animCurrentFrame1;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
DrawModel(model, position, 1.0f, Color.White); // Draw animated model
|
||||
|
||||
DrawGrid(10, 1.0f);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
if (animTransition)
|
||||
{
|
||||
DrawText("ANIM TRANSITION BLENDING!", 170, 50, 30, Color.Blue);
|
||||
}
|
||||
|
||||
// Draw UI elements
|
||||
//---------------------------------------------------------------------------------------------
|
||||
GuiSlider(new Rectangle(10, 38, 160, 12), null, $"x{animFrameSpeed0:0.0}", ref animFrameSpeed0, 0.1f, 2.0f);
|
||||
GuiSlider(new Rectangle(GetScreenWidth() - 170.0f, 38, 160, 12), $"{animFrameSpeed1:0.0}x", null, ref animFrameSpeed1, 0.1f, 2.0f);
|
||||
|
||||
// Blending process progress bar
|
||||
GuiProgressBar(new Rectangle(180, 14, 440, 16), null, null, animBlendProgress, 0.0f, 1.0f);
|
||||
|
||||
// Centered "PRESS SPACE" label
|
||||
const string spaceLabel = "PRESS SPACE to START BLENDING";
|
||||
var spaceLabelWidth = MeasureText(spaceLabel, 20);
|
||||
DrawText(spaceLabel, (GetScreenWidth() - spaceLabelWidth) / 2, (int)(GetScreenHeight() - 100.0f + 10), 20, Color.DarkGray);
|
||||
|
||||
// Draw playing timeline with keyframes for anim0[]
|
||||
GuiProgressBar(new Rectangle(60, GetScreenHeight() - 60.0f, GetScreenWidth() - 180.0f, 20), "ANIM 0",
|
||||
$"FRAME: {animFrameProgress0:0.00} / {anims[animIndex0].KeyFrameCount}",
|
||||
animFrameProgress0, 0.0f, anims[animIndex0].KeyFrameCount);
|
||||
for (var i = 0; i < anims[animIndex0].KeyFrameCount; i++)
|
||||
{
|
||||
DrawRectangle(60 + (int)(((float)(GetScreenWidth() - 180) / anims[animIndex0].KeyFrameCount) * i),
|
||||
GetScreenHeight() - 60, 1, 20, Color.Blue);
|
||||
}
|
||||
|
||||
// Draw playing timeline with keyframes for anim1[]
|
||||
GuiProgressBar(new Rectangle(60, GetScreenHeight() - 30.0f, GetScreenWidth() - 180.0f, 20), "ANIM 1",
|
||||
$"FRAME: {animFrameProgress1:0.00} / {anims[animIndex1].KeyFrameCount}",
|
||||
animFrameProgress1, 0.0f, anims[animIndex1].KeyFrameCount);
|
||||
for (var i = 0; i < anims[animIndex1].KeyFrameCount; i++)
|
||||
{
|
||||
DrawRectangle(60 + (int)(((float)(GetScreenWidth() - 180) / anims[animIndex1].KeyFrameCount) * i),
|
||||
GetScreenHeight() - 30, 1, 20, Color.Blue);
|
||||
}
|
||||
|
||||
// Draw animation selectors for blending transition (drawn last so open lists render on top)
|
||||
// NOTE: Transition does not start until requested
|
||||
if (GuiDropdownBox(new Rectangle(10, 10, 160, 24), animNames, ref animIndex0, dropdownEditMode0))
|
||||
{
|
||||
dropdownEditMode0 = !dropdownEditMode0;
|
||||
}
|
||||
if (GuiDropdownBox(new Rectangle(GetScreenWidth() - 170.0f, 10, 160, 24), animNames, ref animIndex1, dropdownEditMode1))
|
||||
{
|
||||
dropdownEditMode1 = !dropdownEditMode1;
|
||||
}
|
||||
//---------------------------------------------------------------------------------------------
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public unsafe void Unload()
|
||||
{
|
||||
UnloadModelAnimations(anims, animCount); // Unload model animation
|
||||
UnloadModel(model); // Unload model and meshes/material
|
||||
UnloadShader(skinningShader); // Unload GPU skinning shader
|
||||
}
|
||||
|
||||
// Minimal immediate-mode slider (raygui replacement)
|
||||
private static bool GuiSlider(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
|
||||
{
|
||||
var mouse = GetMousePosition();
|
||||
var dragging = false;
|
||||
if (CheckCollisionPointRec(mouse, bounds) && IsMouseButtonDown(MouseButton.Left))
|
||||
{
|
||||
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
|
||||
if (value < minValue)
|
||||
{
|
||||
value = minValue;
|
||||
}
|
||||
if (value > maxValue)
|
||||
{
|
||||
value = maxValue;
|
||||
}
|
||||
dragging = true;
|
||||
}
|
||||
|
||||
DrawRectangleRec(bounds, Color.LightGray);
|
||||
var pct = (value - minValue) / (maxValue - minValue);
|
||||
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (!string.IsNullOrEmpty(textLeft))
|
||||
{
|
||||
DrawText(textLeft, (int)(bounds.X - MeasureText(textLeft, 10) - 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(textRight))
|
||||
{
|
||||
DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
return dragging;
|
||||
}
|
||||
|
||||
// Minimal immediate-mode progress bar (raygui replacement)
|
||||
private static void GuiProgressBar(Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue)
|
||||
{
|
||||
DrawRectangleRec(bounds, Color.LightGray);
|
||||
var pct = maxValue > minValue ? (value - minValue) / (maxValue - minValue) : 0.0f;
|
||||
if (pct < 0)
|
||||
{
|
||||
pct = 0;
|
||||
}
|
||||
if (pct > 1)
|
||||
{
|
||||
pct = 1;
|
||||
}
|
||||
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (!string.IsNullOrEmpty(textLeft))
|
||||
{
|
||||
DrawText(textLeft, (int)(bounds.X - MeasureText(textLeft, 10) - 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(textRight))
|
||||
{
|
||||
DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
}
|
||||
|
||||
// Minimal immediate-mode dropdown box (raygui replacement)
|
||||
private static bool GuiDropdownBox(Rectangle bounds, string[] items, ref int active, bool editMode)
|
||||
{
|
||||
var result = false;
|
||||
var mouse = GetMousePosition();
|
||||
|
||||
// Draw main box
|
||||
DrawRectangleRec(bounds, Color.LightGray);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (active >= 0 && active < items.Length)
|
||||
{
|
||||
DrawText(items[active], (int)bounds.X + 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
DrawText(editMode ? "^" : "v", (int)(bounds.X + bounds.Width - 12), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
|
||||
// Draw items when open
|
||||
if (editMode)
|
||||
{
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
Rectangle item = new(bounds.X, bounds.Y + bounds.Height * (i + 1), bounds.Width, bounds.Height);
|
||||
var hover = CheckCollisionPointRec(mouse, item);
|
||||
DrawRectangleRec(item, hover ? Color.SkyBlue : Color.LightGray);
|
||||
DrawRectangleLinesEx(item, 1, Color.Gray);
|
||||
DrawText(items[i], (int)item.X + 5, (int)(item.Y + item.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsMouseButtonPressed(MouseButton.Left))
|
||||
{
|
||||
if (editMode)
|
||||
{
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
Rectangle item = new(bounds.X, bounds.Y + bounds.Height * (i + 1), bounds.Width, bounds.Height);
|
||||
if (CheckCollisionPointRec(mouse, item))
|
||||
{
|
||||
active = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = true; // any click closes the dropdown
|
||||
}
|
||||
else if (CheckCollisionPointRec(mouse, bounds))
|
||||
{
|
||||
result = true; // open the dropdown
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - animation blending");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new AnimationBlending();
|
||||
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;
|
||||
}
|
||||
}
|
||||
160
Examples/Models/AnimationGpuSkinning.cs
Normal file
160
Examples/Models/AnimationGpuSkinning.cs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - animation gpu skinning
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 4.5, last time updated with raylib 4.5
|
||||
*
|
||||
* Example contributed by Daniel Holden (@orangeduck) and reviewed by Ramon Santamaria (@raysan5)
|
||||
*
|
||||
* WARNING: GPU skinning must be enabled in raylib with a compilation flag,
|
||||
* if not enabled, CPU skinning will be used instead
|
||||
*
|
||||
* 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) 2024-2025 Daniel Holden (@orangeduck)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class AnimationGpuSkinning : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
private const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Models / Animation GPU Skinning";
|
||||
|
||||
public string Title => "raylib [models] example - animation gpu skinning";
|
||||
|
||||
private Camera3D camera;
|
||||
private Model model;
|
||||
private Vector3 position;
|
||||
private Shader skinningShader;
|
||||
private unsafe ModelAnimation* anims;
|
||||
private int animCount;
|
||||
private int animIndex;
|
||||
private int animCurrentFrame;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(5.0f, 5.0f, 5.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 1.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
// Load gltf model
|
||||
model = LoadModel("resources/models/gltf/greenman.glb"); // Load character model
|
||||
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model position
|
||||
|
||||
// Load skinning shader
|
||||
// NOTE: It must be a valid shader, following raylib attribs/uniform conventions for GPU skinning
|
||||
skinningShader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/skinning.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/skinning.fs"
|
||||
);
|
||||
|
||||
// Skinning shader could be required to be assigned to all materials shaders, just to make
|
||||
// sure required uniforms are being updated for the mesh using that material (and shader)
|
||||
model.Materials[1].Shader = skinningShader; // Just assigning to materials[1] for this model
|
||||
|
||||
// Load gltf model animations
|
||||
animCount = 0;
|
||||
anims = LoadModelAnimations("resources/models/gltf/greenman.glb", ref animCount);
|
||||
|
||||
// Animation playing variables
|
||||
animIndex = 0; // Current animation playing
|
||||
animCurrentFrame = 0; // Current animation frame
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
// Select current animation
|
||||
if (IsKeyPressed(KeyboardKey.Right))
|
||||
{
|
||||
animIndex = (animIndex + 1) % animCount;
|
||||
}
|
||||
else if (IsKeyPressed(KeyboardKey.Left))
|
||||
{
|
||||
animIndex = (animIndex + animCount - 1) % animCount;
|
||||
}
|
||||
|
||||
// Update model animation
|
||||
animCurrentFrame = (animCurrentFrame + 1) % anims[animIndex].KeyFrameCount;
|
||||
UpdateModelAnimation(model, anims[animIndex], animCurrentFrame);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
DrawModel(model, position, 1.0f, Color.White);
|
||||
|
||||
DrawGrid(10, 1.0f);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
DrawText($"Current animation: {anims[animIndex].NameToString()}", 10, 40, 20, Color.Maroon);
|
||||
DrawText("Use the LEFT/RIGHT keys to switch animation", 10, 10, 20, Color.Gray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public unsafe void Unload()
|
||||
{
|
||||
UnloadModelAnimations(anims, animCount); // Unload model animation
|
||||
UnloadModel(model); // Unload model and meshes/material
|
||||
UnloadShader(skinningShader); // Unload GPU skinning shader
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - animation gpu skinning");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new AnimationGpuSkinning();
|
||||
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;
|
||||
}
|
||||
}
|
||||
292
Examples/Models/AnimationTiming.cs
Normal file
292
Examples/Models/AnimationTiming.cs
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - animation timing
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* 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) 2026 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
// NOTE: The upstream example uses raygui for its UI controls (dropdown, slider, progress bar).
|
||||
// raygui is not part of raylib-cs, so the required controls are reimplemented here with
|
||||
// basic raylib drawing primitives, preserving the original behaviour.
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class AnimationTiming : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Models / Animation Timing";
|
||||
|
||||
public string Title => "raylib [models] example - animation timing";
|
||||
|
||||
private Camera3D camera;
|
||||
private Model model;
|
||||
private Vector3 position;
|
||||
private unsafe ModelAnimation* anims;
|
||||
private int animCount;
|
||||
private int animIndex;
|
||||
private float animCurrentFrame;
|
||||
private float animFrameSpeed;
|
||||
private bool animPause;
|
||||
private string[] animNames;
|
||||
private bool dropdownEditMode;
|
||||
private float animFrameProgress;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(6.0f, 6.0f, 6.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 2.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
// Load model
|
||||
model = LoadModel("resources/models/gltf/robot.glb");
|
||||
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model world position
|
||||
|
||||
// Load model animations
|
||||
animCount = 0;
|
||||
anims = LoadModelAnimations("resources/models/gltf/robot.glb", ref animCount);
|
||||
|
||||
// Animation playing variables
|
||||
animIndex = 10; // Current animation playing
|
||||
animCurrentFrame = 0.0f; // Current animation frame (supporting interpolated frames)
|
||||
animFrameSpeed = 0.5f; // Animation play speed
|
||||
animPause = false; // Pause animation
|
||||
|
||||
// UI required variables
|
||||
animNames = new string[animCount];
|
||||
for (var i = 0; i < animCount; i++)
|
||||
{
|
||||
animNames[i] = anims[i].NameToString();
|
||||
}
|
||||
|
||||
dropdownEditMode = false;
|
||||
animFrameProgress = 0.0f;
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.P))
|
||||
{
|
||||
animPause = !animPause;
|
||||
}
|
||||
|
||||
if (!animPause && (animIndex < animCount))
|
||||
{
|
||||
// Update model animation
|
||||
animCurrentFrame += animFrameSpeed;
|
||||
if (animCurrentFrame >= anims[animIndex].KeyFrameCount)
|
||||
{
|
||||
animCurrentFrame = 0.0f;
|
||||
}
|
||||
UpdateModelAnimation(model, anims[animIndex], animCurrentFrame);
|
||||
}
|
||||
|
||||
// NOTE: Animation and playing speed selected through UI
|
||||
|
||||
// Update progressbar value with current frame
|
||||
animFrameProgress = animCurrentFrame;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
DrawModel(model, position, 1.0f, Color.White);
|
||||
|
||||
DrawGrid(10, 1.0f);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
// Draw UI, select anim and playing speed
|
||||
GuiSlider(new Rectangle(260, 10, 500, 24), "FRAME SPEED: ", $"x{animFrameSpeed:0.0}", ref animFrameSpeed, 0.1f, 2.0f);
|
||||
|
||||
// Draw playing timeline with keyframes
|
||||
DrawText($"CURRENT FRAME: {animFrameProgress:0.00} / {anims[animIndex].KeyFrameCount}",
|
||||
10, (int)(GetScreenHeight() - 64.0f), 10, Color.DarkGray);
|
||||
GuiProgressBar(new Rectangle(10, GetScreenHeight() - 40.0f, GetScreenWidth() - 20.0f, 24), null, null,
|
||||
animFrameProgress, 0.0f, anims[animIndex].KeyFrameCount);
|
||||
for (var i = 0; i < anims[animIndex].KeyFrameCount; i++)
|
||||
{
|
||||
DrawRectangle(10 + (int)(((float)(GetScreenWidth() - 20) / anims[animIndex].KeyFrameCount) * i),
|
||||
GetScreenHeight() - 40, 1, 24, Color.Blue);
|
||||
}
|
||||
|
||||
// NOTE: Dropdown drawn last so its open item list renders on top
|
||||
if (GuiDropdownBox(new Rectangle(10, 10, 140, 24), animNames, ref animIndex, dropdownEditMode))
|
||||
{
|
||||
dropdownEditMode = !dropdownEditMode;
|
||||
}
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public unsafe void Unload()
|
||||
{
|
||||
UnloadModelAnimations(anims, animCount); // Unload model animation
|
||||
UnloadModel(model); // Unload model and meshes/material
|
||||
}
|
||||
|
||||
// Minimal immediate-mode slider (raygui replacement)
|
||||
private static bool GuiSlider(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
|
||||
{
|
||||
var mouse = GetMousePosition();
|
||||
var dragging = false;
|
||||
if (CheckCollisionPointRec(mouse, bounds) && IsMouseButtonDown(MouseButton.Left))
|
||||
{
|
||||
value = minValue + ((mouse.X - bounds.X) / bounds.Width) * (maxValue - minValue);
|
||||
if (value < minValue)
|
||||
{
|
||||
value = minValue;
|
||||
}
|
||||
if (value > maxValue)
|
||||
{
|
||||
value = maxValue;
|
||||
}
|
||||
dragging = true;
|
||||
}
|
||||
|
||||
DrawRectangleRec(bounds, Color.LightGray);
|
||||
var pct = (value - minValue) / (maxValue - minValue);
|
||||
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (!string.IsNullOrEmpty(textLeft))
|
||||
{
|
||||
DrawText(textLeft, (int)(bounds.X - MeasureText(textLeft, 10) - 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(textRight))
|
||||
{
|
||||
DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
return dragging;
|
||||
}
|
||||
|
||||
// Minimal immediate-mode progress bar (raygui replacement)
|
||||
private static void GuiProgressBar(Rectangle bounds, string textLeft, string textRight, float value, float minValue, float maxValue)
|
||||
{
|
||||
DrawRectangleRec(bounds, Color.LightGray);
|
||||
var pct = maxValue > minValue ? (value - minValue) / (maxValue - minValue) : 0.0f;
|
||||
if (pct < 0)
|
||||
{
|
||||
pct = 0;
|
||||
}
|
||||
if (pct > 1)
|
||||
{
|
||||
pct = 1;
|
||||
}
|
||||
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (!string.IsNullOrEmpty(textLeft))
|
||||
{
|
||||
DrawText(textLeft, (int)(bounds.X - MeasureText(textLeft, 10) - 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(textRight))
|
||||
{
|
||||
DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
}
|
||||
|
||||
// Minimal immediate-mode dropdown box (raygui replacement)
|
||||
private static bool GuiDropdownBox(Rectangle bounds, string[] items, ref int active, bool editMode)
|
||||
{
|
||||
var result = false;
|
||||
var mouse = GetMousePosition();
|
||||
|
||||
// Draw main box
|
||||
DrawRectangleRec(bounds, Color.LightGray);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (active >= 0 && active < items.Length)
|
||||
{
|
||||
DrawText(items[active], (int)bounds.X + 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
DrawText(editMode ? "^" : "v", (int)(bounds.X + bounds.Width - 12), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
|
||||
// Draw items when open
|
||||
if (editMode)
|
||||
{
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
Rectangle item = new(bounds.X, bounds.Y + bounds.Height * (i + 1), bounds.Width, bounds.Height);
|
||||
var hover = CheckCollisionPointRec(mouse, item);
|
||||
DrawRectangleRec(item, hover ? Color.SkyBlue : Color.LightGray);
|
||||
DrawRectangleLinesEx(item, 1, Color.Gray);
|
||||
DrawText(items[i], (int)item.X + 5, (int)(item.Y + item.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsMouseButtonPressed(MouseButton.Left))
|
||||
{
|
||||
if (editMode)
|
||||
{
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
Rectangle item = new(bounds.X, bounds.Y + bounds.Height * (i + 1), bounds.Width, bounds.Height);
|
||||
if (CheckCollisionPointRec(mouse, item))
|
||||
{
|
||||
active = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
result = true; // any click closes the dropdown
|
||||
}
|
||||
else if (CheckCollisionPointRec(mouse, bounds))
|
||||
{
|
||||
result = true; // open the dropdown
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - animation timing");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new AnimationTiming();
|
||||
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;
|
||||
}
|
||||
}
|
||||
202
Examples/Models/BasicVoxel.cs
Normal file
202
Examples/Models/BasicVoxel.cs
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - basic voxel
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.5
|
||||
*
|
||||
* Example contributed by Tim Little (@timlittle) 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 Tim Little (@timlittle)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class BasicVoxel : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int WorldSize = 8; // Size of our voxel world (8x8x8 cubes)
|
||||
|
||||
public string Name => "Models / Basic Voxel";
|
||||
|
||||
public string Title => "raylib [models] example - basic voxel";
|
||||
|
||||
public bool CursorDisabled => true;
|
||||
|
||||
private Camera3D camera;
|
||||
private Model cubeModel;
|
||||
private bool[,,] voxels;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world (first person)
|
||||
camera = new();
|
||||
camera.Position = new Vector3(-2.0f, 0.0f, -2.0f); // Camera position at ground level
|
||||
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
// Create a cube model
|
||||
var cubeMesh = GenMeshCube(1.0f, 1.0f, 1.0f); // Create a unit cube mesh
|
||||
cubeModel = LoadModelFromMesh(cubeMesh); // Convert mesh to a model
|
||||
cubeModel.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Color = Color.Beige;
|
||||
|
||||
// Initialize voxel world - fill with voxels
|
||||
voxels = new bool[WorldSize, WorldSize, WorldSize];
|
||||
for (var x = 0; x < WorldSize; x++)
|
||||
{
|
||||
for (var y = 0; y < WorldSize; y++)
|
||||
{
|
||||
for (var z = 0; z < WorldSize; z++)
|
||||
{
|
||||
voxels[x, y, z] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.FirstPerson);
|
||||
|
||||
// Handle voxel removal with mouse click
|
||||
// This method is quite inefficient. Ray marching through the voxel grid using DDA would be faster, but more complex.
|
||||
if (IsMouseButtonPressed(MouseButton.Left))
|
||||
{
|
||||
// Cast a ray from the screen center (where crosshair would be)
|
||||
Vector2 screenCenter = new(GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f);
|
||||
var ray = GetScreenToWorldRay(screenCenter, camera);
|
||||
|
||||
// Check ray collision with all voxels
|
||||
var closestDistance = 99999.0f;
|
||||
Vector3 closestVoxelPosition = new(-1, -1, -1);
|
||||
var voxelFound = false;
|
||||
for (var x = 0; x < WorldSize; x++)
|
||||
{
|
||||
for (var y = 0; y < WorldSize; y++)
|
||||
{
|
||||
for (var z = 0; z < WorldSize; z++)
|
||||
{
|
||||
if (!voxels[x, y, z])
|
||||
{
|
||||
continue; // Skip empty voxels
|
||||
}
|
||||
|
||||
// Build a bounding box for this voxel
|
||||
Vector3 position = new(x, y, z);
|
||||
BoundingBox box = new(
|
||||
new Vector3(position.X - 0.5f, position.Y - 0.5f, position.Z - 0.5f),
|
||||
new Vector3(position.X + 0.5f, position.Y + 0.5f, position.Z + 0.5f)
|
||||
);
|
||||
|
||||
// Check ray-box collision
|
||||
var collision = GetRayCollisionBox(ray, box);
|
||||
if (collision.Hit && (collision.Distance < closestDistance))
|
||||
{
|
||||
closestDistance = collision.Distance;
|
||||
closestVoxelPosition = new Vector3(x, y, z);
|
||||
voxelFound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the closest voxel if one was hit
|
||||
if (voxelFound)
|
||||
{
|
||||
voxels[(int)closestVoxelPosition.X,
|
||||
(int)closestVoxelPosition.Y,
|
||||
(int)closestVoxelPosition.Z] = false;
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
DrawGrid(10, 1.0f);
|
||||
|
||||
// Draw all voxels
|
||||
for (var x = 0; x < WorldSize; x++)
|
||||
{
|
||||
for (var y = 0; y < WorldSize; y++)
|
||||
{
|
||||
for (var z = 0; z < WorldSize; z++)
|
||||
{
|
||||
if (!voxels[x, y, z])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector3 position = new(x, y, z);
|
||||
DrawModel(cubeModel, position, 1.0f, Color.Beige);
|
||||
DrawCubeWires(position, 1.0f, 1.0f, 1.0f, Color.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EndMode3D();
|
||||
|
||||
// Draw reference point for raycasting to delete blocks
|
||||
DrawCircle(GetScreenWidth() / 2, GetScreenHeight() / 2, 4, Color.Red);
|
||||
|
||||
DrawText("Left-click a voxel to remove it!", 10, 10, 20, Color.DarkGray);
|
||||
DrawText("WASD to move, mouse to look around", 10, 35, 10, Color.Gray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadModel(cubeModel);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - basic voxel");
|
||||
|
||||
DisableCursor(); // Lock mouse to window center
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new BasicVoxel();
|
||||
game.Init();
|
||||
|
||||
// Main game loop
|
||||
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||
{
|
||||
game.Update();
|
||||
}
|
||||
|
||||
game.Unload();
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
CloseWindow();
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
246
Examples/Models/BoneSocket.cs
Normal file
246
Examples/Models/BoneSocket.cs
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - bone socket
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 4.5, last time updated with raylib 4.5
|
||||
*
|
||||
* Example contributed by iP (@ipzaur) 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) 2024-2025 iP (@ipzaur)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class BoneSocket : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int BoneSockets = 3;
|
||||
private const int BoneSocketHat = 0;
|
||||
private const int BoneSocketHandR = 1;
|
||||
private const int BoneSocketHandL = 2;
|
||||
|
||||
public string Name => "Models / Bone Socket";
|
||||
|
||||
public string Title => "raylib [models] example - bone socket";
|
||||
|
||||
public bool CursorDisabled => true;
|
||||
|
||||
private Camera3D camera;
|
||||
private Model characterModel;
|
||||
private Model[] equipModel;
|
||||
private bool[] showEquip;
|
||||
private int animsCount;
|
||||
private int animIndex;
|
||||
private int animCurrentFrame;
|
||||
private unsafe ModelAnimation* modelAnimations;
|
||||
private int[] boneSocketIndex;
|
||||
private Vector3 position;
|
||||
private int angle;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(5.0f, 5.0f, 5.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 2.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
// Load gltf model
|
||||
characterModel = LoadModel("resources/models/gltf/greenman.glb"); // Load character model
|
||||
equipModel = new Model[BoneSockets]
|
||||
{
|
||||
LoadModel("resources/models/gltf/greenman_hat.glb"), // Index for the hat model is the same as BONE_SOCKET_HAT
|
||||
LoadModel("resources/models/gltf/greenman_sword.glb"), // Index for the sword model is the same as BONE_SOCKET_HAND_R
|
||||
LoadModel("resources/models/gltf/greenman_shield.glb") // Index for the shield model is the same as BONE_SOCKET_HAND_L
|
||||
};
|
||||
|
||||
showEquip = new bool[3] { true, true, true }; // Toggle on/off equip
|
||||
|
||||
// Load gltf model animations
|
||||
animsCount = 0;
|
||||
animIndex = 0;
|
||||
animCurrentFrame = 0;
|
||||
modelAnimations = LoadModelAnimations("resources/models/gltf/greenman.glb", ref animsCount);
|
||||
|
||||
// Indices of bones for sockets
|
||||
boneSocketIndex = new int[BoneSockets] { -1, -1, -1 };
|
||||
|
||||
// Search bones for sockets
|
||||
for (var i = 0; i < characterModel.Skeleton.BoneCount; i++)
|
||||
{
|
||||
var boneName = characterModel.Skeleton.Bones[i].NameToString();
|
||||
|
||||
if (boneName == "socket_hat")
|
||||
{
|
||||
boneSocketIndex[BoneSocketHat] = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (boneName == "socket_hand_R")
|
||||
{
|
||||
boneSocketIndex[BoneSocketHandR] = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (boneName == "socket_hand_L")
|
||||
{
|
||||
boneSocketIndex[BoneSocketHandL] = i;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model position
|
||||
angle = 0; // Set angle for rotate character
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.ThirdPerson);
|
||||
|
||||
// Rotate character
|
||||
if (IsKeyDown(KeyboardKey.F))
|
||||
{
|
||||
angle = (angle + 1) % 360;
|
||||
}
|
||||
else if (IsKeyDown(KeyboardKey.H))
|
||||
{
|
||||
angle = (360 + angle - 1) % 360;
|
||||
}
|
||||
|
||||
// Select current animation
|
||||
if (IsKeyPressed(KeyboardKey.T))
|
||||
{
|
||||
animIndex = (animIndex + 1) % animsCount;
|
||||
}
|
||||
else if (IsKeyPressed(KeyboardKey.G))
|
||||
{
|
||||
animIndex = (animIndex + animsCount - 1) % animsCount;
|
||||
}
|
||||
|
||||
// Toggle shown of equip
|
||||
if (IsKeyPressed(KeyboardKey.One))
|
||||
{
|
||||
showEquip[BoneSocketHat] = !showEquip[BoneSocketHat];
|
||||
}
|
||||
if (IsKeyPressed(KeyboardKey.Two))
|
||||
{
|
||||
showEquip[BoneSocketHandR] = !showEquip[BoneSocketHandR];
|
||||
}
|
||||
if (IsKeyPressed(KeyboardKey.Three))
|
||||
{
|
||||
showEquip[BoneSocketHandL] = !showEquip[BoneSocketHandL];
|
||||
}
|
||||
|
||||
// Update model animation
|
||||
var anim = modelAnimations[animIndex];
|
||||
animCurrentFrame = (animCurrentFrame + 1) % anim.KeyFrameCount;
|
||||
UpdateModelAnimation(characterModel, anim, animCurrentFrame);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
// Draw character
|
||||
var characterRotate = QuaternionFromAxisAngle(new Vector3(0.0f, 1.0f, 0.0f), angle * DEG2RAD);
|
||||
characterModel.Transform = MatrixMultiply(QuaternionToMatrix(characterRotate), MatrixTranslate(position.X, position.Y, position.Z));
|
||||
UpdateModelAnimation(characterModel, anim, animCurrentFrame);
|
||||
DrawMesh(characterModel.Meshes[0], characterModel.Materials[1], characterModel.Transform);
|
||||
|
||||
// Draw equipments (hat, sword, shield)
|
||||
for (var i = 0; i < BoneSockets; i++)
|
||||
{
|
||||
if (!showEquip[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var transform = &anim.KeyframePoses[animCurrentFrame][boneSocketIndex[i]];
|
||||
var inRotation = characterModel.Skeleton.BindPose[boneSocketIndex[i]].Rotation;
|
||||
var outRotation = transform->Rotation;
|
||||
|
||||
// Calculate socket rotation (angle between bone in initial pose and same bone in current animation frame)
|
||||
var rotate = QuaternionMultiply(outRotation, QuaternionInvert(inRotation));
|
||||
var matrixTransform = QuaternionToMatrix(rotate);
|
||||
// Translate socket to its position in the current animation
|
||||
matrixTransform = MatrixMultiply(matrixTransform, MatrixTranslate(transform->Translation.X, transform->Translation.Y, transform->Translation.Z));
|
||||
// Transform the socket using the transform of the character (angle and translate)
|
||||
matrixTransform = MatrixMultiply(matrixTransform, characterModel.Transform);
|
||||
|
||||
// Draw mesh at socket position with socket angle rotation
|
||||
DrawMesh(equipModel[i].Meshes[0], equipModel[i].Materials[1], matrixTransform);
|
||||
}
|
||||
|
||||
DrawGrid(10, 1.0f);
|
||||
EndMode3D();
|
||||
|
||||
DrawText("Use the T/G to switch animation", 10, 10, 20, Color.Gray);
|
||||
DrawText("Use the F/H to rotate character left/right", 10, 35, 20, Color.Gray);
|
||||
DrawText("Use the 1,2,3 to toggle shown of hat, sword and shield", 10, 60, 20, Color.Gray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public unsafe void Unload()
|
||||
{
|
||||
UnloadModelAnimations(modelAnimations, animsCount);
|
||||
UnloadModel(characterModel); // Unload character model and meshes/material
|
||||
|
||||
// Unload equipment model and meshes/material
|
||||
for (var i = 0; i < BoneSockets; i++)
|
||||
{
|
||||
UnloadModel(equipModel[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - bone socket");
|
||||
|
||||
DisableCursor(); // Limit cursor to relative movement inside the window
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new BoneSocket();
|
||||
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;
|
||||
}
|
||||
}
|
||||
592
Examples/Models/Decals.cs
Normal file
592
Examples/Models/Decals.cs
Normal file
|
|
@ -0,0 +1,592 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - decals
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by JP Mortiboys (@themushroompirates) and reviewed by Ramon Santamaria (@raysan5)
|
||||
* Based on previous work by @mrdoob
|
||||
*
|
||||
* 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 JP Mortiboys (@themushroompirates) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class Decals : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MaxDecals = 256;
|
||||
|
||||
public string Name => "Models / Decals";
|
||||
|
||||
public string Title => "raylib [models] example - decals";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
private Camera3D camera;
|
||||
private Model model;
|
||||
private Texture2D modelTexture;
|
||||
private BoundingBox modelBBox;
|
||||
private float decalSize;
|
||||
private float decalOffset;
|
||||
private Model placementCube;
|
||||
private Material decalMaterial;
|
||||
private Texture2D decalTexture;
|
||||
private bool showModel;
|
||||
private readonly List<Model> decalModels = new();
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(5.0f, 5.0f, 5.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 1.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.6f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
// Load character model
|
||||
model = LoadModel("resources/models/obj/character.obj");
|
||||
|
||||
// Apply character skin
|
||||
modelTexture = LoadTexture("resources/models/obj/character_diffuse.png");
|
||||
SetTextureFilter(modelTexture, TextureFilter.Bilinear);
|
||||
model.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture = modelTexture;
|
||||
|
||||
modelBBox = GetMeshBoundingBox(model.Meshes[0]); // Get mesh bounding box
|
||||
|
||||
camera.Target = Vector3Lerp(modelBBox.Min, modelBBox.Max, 0.5f);
|
||||
camera.Position = modelBBox.Max * 1.0f;
|
||||
camera.Position.X *= 0.1f;
|
||||
|
||||
var modelSize = MathF.Min(
|
||||
MathF.Min(MathF.Abs(modelBBox.Max.X - modelBBox.Min.X), MathF.Abs(modelBBox.Max.Y - modelBBox.Min.Y)),
|
||||
MathF.Abs(modelBBox.Max.Z - modelBBox.Min.Z));
|
||||
|
||||
camera.Position = new Vector3(0.0f, modelBBox.Max.Y * 1.2f, modelSize * 3.0f);
|
||||
|
||||
decalSize = modelSize * 0.25f;
|
||||
decalOffset = 0.01f;
|
||||
|
||||
placementCube = LoadModelFromMesh(GenMeshCube(decalSize, decalSize, decalSize));
|
||||
placementCube.Materials[0].Maps[0].Color = Color.Lime;
|
||||
|
||||
decalMaterial = LoadMaterialDefault();
|
||||
decalMaterial.Maps[0].Color = Color.Yellow;
|
||||
|
||||
var decalImage = LoadImage("resources/raylib_logo.png");
|
||||
ImageResizeNN(ref decalImage, decalImage.Width / 4, decalImage.Height / 4);
|
||||
decalTexture = LoadTextureFromImage(decalImage);
|
||||
UnloadImage(decalImage);
|
||||
|
||||
SetTextureFilter(decalTexture, TextureFilter.Bilinear);
|
||||
decalMaterial.Maps[(int)MaterialMapIndex.Diffuse].Texture = decalTexture;
|
||||
decalMaterial.Maps[(int)MaterialMapIndex.Diffuse].Color = Color.RayWhite;
|
||||
|
||||
showModel = true;
|
||||
decalModels.Clear();
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsMouseButtonDown(MouseButton.Right))
|
||||
{
|
||||
UpdateCamera(ref camera, CameraMode.ThirdPerson);
|
||||
}
|
||||
|
||||
// Display information about closest hit
|
||||
RayCollision collision = new();
|
||||
collision.Distance = float.MaxValue;
|
||||
collision.Hit = false;
|
||||
|
||||
// Get mouse ray
|
||||
var ray = GetScreenToWorldRay(GetMousePosition(), camera);
|
||||
|
||||
// Check ray collision against bounding box first, before trying the full ray-mesh test
|
||||
var boxHitInfo = GetRayCollisionBox(ray, modelBBox);
|
||||
|
||||
RayCollision meshHitInfo = new();
|
||||
if (boxHitInfo.Hit && (decalModels.Count < MaxDecals))
|
||||
{
|
||||
// Check ray collision against model meshes
|
||||
for (var m = 0; m < model.MeshCount; m++)
|
||||
{
|
||||
// NOTE: We consider the model.transform for the collision check but
|
||||
// it can be checked against any transform Matrix, used when checking against same
|
||||
// model drawn multiple times with multiple transforms
|
||||
meshHitInfo = GetRayCollisionMesh(ray, model.Meshes[m], model.Transform);
|
||||
if (meshHitInfo.Hit)
|
||||
{
|
||||
// Save the closest hit mesh
|
||||
if (!collision.Hit || (collision.Distance > meshHitInfo.Distance))
|
||||
{
|
||||
collision = meshHitInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (meshHitInfo.Hit)
|
||||
{
|
||||
collision = meshHitInfo;
|
||||
}
|
||||
}
|
||||
|
||||
// Add decal to mesh on hit point
|
||||
if (collision.Hit && IsMouseButtonPressed(MouseButton.Left) && (decalModels.Count < MaxDecals))
|
||||
{
|
||||
// Create the transformation to project the decal
|
||||
var origin = collision.Point + (collision.Normal * 1.0f);
|
||||
var splat = MatrixLookAt(collision.Point, origin, new Vector3(0.0f, 1.0f, 0.0f));
|
||||
|
||||
// Spin the placement around a bit
|
||||
splat = MatrixMultiply(splat, MatrixRotateZ(DEG2RAD * GetRandomValue(-180, 180)));
|
||||
|
||||
var decalMesh = GenMeshDecal(model, splat, decalSize, decalOffset);
|
||||
|
||||
if (decalMesh.VertexCount > 0)
|
||||
{
|
||||
var decalModel = LoadModelFromMesh(decalMesh);
|
||||
decalModel.Materials[0].Maps[0] = decalMaterial.Maps[0];
|
||||
decalModels.Add(decalModel);
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
// Draw the model at the origin and default scale
|
||||
if (showModel)
|
||||
{
|
||||
DrawModel(model, new Vector3(0.0f, 0.0f, 0.0f), 1.0f, Color.White);
|
||||
}
|
||||
|
||||
// Draw the decal models
|
||||
for (var i = 0; i < decalModels.Count; i++)
|
||||
{
|
||||
DrawModel(decalModels[i], Vector3.Zero, 1.0f, Color.White);
|
||||
}
|
||||
|
||||
// If we hit the mesh, draw the box for the decal
|
||||
if (collision.Hit)
|
||||
{
|
||||
var origin = collision.Point + (collision.Normal * 1.0f);
|
||||
var splat = MatrixLookAt(collision.Point, origin, new Vector3(0, 1, 0));
|
||||
placementCube.Transform = MatrixInvert(splat);
|
||||
DrawModel(placementCube, Vector3.Zero, 1.0f, Fade(Color.White, 0.5f));
|
||||
}
|
||||
|
||||
DrawGrid(10, 10.0f);
|
||||
EndMode3D();
|
||||
|
||||
float yPos = 10;
|
||||
var x0 = GetScreenWidth() - 300.0f;
|
||||
var x1 = x0 + 100;
|
||||
var x2 = x1 + 100;
|
||||
|
||||
DrawText("Vertices", (int)x1, (int)yPos, 10, Color.Lime);
|
||||
DrawText("Triangles", (int)x2, (int)yPos, 10, Color.Lime);
|
||||
yPos += 15;
|
||||
|
||||
var vertexCount = 0;
|
||||
var triangleCount = 0;
|
||||
|
||||
for (var i = 0; i < model.MeshCount; i++)
|
||||
{
|
||||
vertexCount += model.Meshes[i].VertexCount;
|
||||
triangleCount += model.Meshes[i].TriangleCount;
|
||||
}
|
||||
|
||||
DrawText("Main model", (int)x0, (int)yPos, 10, Color.Lime);
|
||||
DrawText($"{vertexCount}", (int)x1, (int)yPos, 10, Color.Lime);
|
||||
DrawText($"{triangleCount}", (int)x2, (int)yPos, 10, Color.Lime);
|
||||
yPos += 15;
|
||||
|
||||
for (var i = 0; i < decalModels.Count; i++)
|
||||
{
|
||||
if (i == 20)
|
||||
{
|
||||
DrawText("...", (int)x0, (int)yPos, 10, Color.Lime);
|
||||
yPos += 15;
|
||||
}
|
||||
|
||||
if (i < 20)
|
||||
{
|
||||
DrawText($"Decal #{i + 1}", (int)x0, (int)yPos, 10, Color.Lime);
|
||||
DrawText($"{decalModels[i].Meshes[0].VertexCount}", (int)x1, (int)yPos, 10, Color.Lime);
|
||||
DrawText($"{decalModels[i].Meshes[0].TriangleCount}", (int)x2, (int)yPos, 10, Color.Lime);
|
||||
yPos += 15;
|
||||
}
|
||||
|
||||
vertexCount += decalModels[i].Meshes[0].VertexCount;
|
||||
triangleCount += decalModels[i].Meshes[0].TriangleCount;
|
||||
}
|
||||
|
||||
DrawText("TOTAL", (int)x0, (int)yPos, 10, Color.Lime);
|
||||
DrawText($"{vertexCount}", (int)x1, (int)yPos, 10, Color.Lime);
|
||||
DrawText($"{triangleCount}", (int)x2, (int)yPos, 10, Color.Lime);
|
||||
yPos += 15;
|
||||
|
||||
DrawText("Hold RMB to move camera", 10, 430, 10, Color.Gray);
|
||||
DrawText("(c) Character model and texture from kenney.nl", screenWidth - 260, screenHeight - 20, 10, Color.Gray);
|
||||
|
||||
// UI elements
|
||||
if (GuiButton(new Rectangle(10, screenHeight - 1000.0f, 100, 60), showModel ? "Hide Model" : "Show Model"))
|
||||
{
|
||||
showModel = !showModel;
|
||||
}
|
||||
|
||||
if (GuiButton(new Rectangle(10 + 110, screenHeight - 100.0f, 100, 60), "Clear Decals"))
|
||||
{
|
||||
// Clear decals, unload all decal models
|
||||
for (var i = 0; i < decalModels.Count; i++)
|
||||
{
|
||||
UnloadModel(decalModels[i]);
|
||||
}
|
||||
decalModels.Clear();
|
||||
}
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadModel(model);
|
||||
UnloadTexture(modelTexture);
|
||||
|
||||
// Unload decal models
|
||||
for (var i = 0; i < decalModels.Count; i++)
|
||||
{
|
||||
UnloadModel(decalModels[i]);
|
||||
}
|
||||
decalModels.Clear();
|
||||
|
||||
UnloadTexture(decalTexture);
|
||||
UnloadModel(placementCube);
|
||||
}
|
||||
|
||||
// Clip segment
|
||||
private static Vector3 ClipSegment(Vector3 v0, Vector3 v1, Vector3 p, float s)
|
||||
{
|
||||
var d0 = Vector3.Dot(v0, p) - s;
|
||||
var d1 = Vector3.Dot(v1, p) - s;
|
||||
var s0 = d0 / (d0 - d1);
|
||||
|
||||
return Vector3.Lerp(v0, v1, s0);
|
||||
}
|
||||
|
||||
// Generate mesh decals for provided model
|
||||
private static unsafe Mesh GenMeshDecal(Model target, Matrix4x4 projection, float decalSize, float decalOffset)
|
||||
{
|
||||
// We're going to use these to build up our decal meshes
|
||||
var meshBuilders = new List<Vector3>[2] { new(), new() };
|
||||
|
||||
// We're going to need the inverse matrix
|
||||
var invProj = MatrixInvert(projection);
|
||||
|
||||
// We'll be flip-flopping between the two mesh builders
|
||||
// Reading from one and writing to the other, then swapping
|
||||
var mbIndex = 0;
|
||||
|
||||
// First pass, just get any triangle inside the bounding box (for each mesh of the model)
|
||||
for (var meshIndex = 0; meshIndex < target.MeshCount; meshIndex++)
|
||||
{
|
||||
var mesh = target.Meshes[meshIndex];
|
||||
for (var tri = 0; tri < mesh.TriangleCount; tri++)
|
||||
{
|
||||
var vertices = new Vector3[3];
|
||||
|
||||
// The way we calculate the vertices of the mesh triangle
|
||||
// depend on whether the mesh vertices are indexed or not
|
||||
if (mesh.Indices == null)
|
||||
{
|
||||
for (var v = 0; v < 3; v++)
|
||||
{
|
||||
vertices[v] = new Vector3(
|
||||
mesh.Vertices[3 * 3 * tri + 3 * v + 0],
|
||||
mesh.Vertices[3 * 3 * tri + 3 * v + 1],
|
||||
mesh.Vertices[3 * 3 * tri + 3 * v + 2]
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var v = 0; v < 3; v++)
|
||||
{
|
||||
vertices[v] = new Vector3(
|
||||
mesh.Vertices[3 * mesh.Indices[3 * tri + 0] + v],
|
||||
mesh.Vertices[3 * mesh.Indices[3 * tri + 1] + v],
|
||||
mesh.Vertices[3 * mesh.Indices[3 * tri + 2] + v]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Transform all 3 vertices of the triangle
|
||||
// and check if they are inside our decal box
|
||||
var insideCount = 0;
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
// To projection space
|
||||
var v = Vector3Transform(vertices[i], projection);
|
||||
|
||||
if ((MathF.Abs(v.X) < decalSize) || (MathF.Abs(v.Y) <= decalSize) || (MathF.Abs(v.Z) <= decalSize))
|
||||
{
|
||||
insideCount++;
|
||||
}
|
||||
|
||||
// We need to keep the transformed vertex
|
||||
vertices[i] = v;
|
||||
}
|
||||
|
||||
// If any of them are inside, we add the triangle - we'll clip it later
|
||||
if (insideCount > 0)
|
||||
{
|
||||
meshBuilders[mbIndex].Add(vertices[0]);
|
||||
meshBuilders[mbIndex].Add(vertices[1]);
|
||||
meshBuilders[mbIndex].Add(vertices[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clipping time! We need to clip against all 6 directions
|
||||
Vector3[] planes =
|
||||
{
|
||||
new(1, 0, 0),
|
||||
new(-1, 0, 0),
|
||||
new(0, 1, 0),
|
||||
new(0, -1, 0),
|
||||
new(0, 0, 1),
|
||||
new(0, 0, -1)
|
||||
};
|
||||
|
||||
for (var face = 0; face < 6; face++)
|
||||
{
|
||||
// Swap current model builder (so we read from the one we just wrote to)
|
||||
mbIndex = 1 - mbIndex;
|
||||
|
||||
var inMesh = meshBuilders[1 - mbIndex];
|
||||
var outMesh = meshBuilders[mbIndex];
|
||||
|
||||
// Reset write builder
|
||||
outMesh.Clear();
|
||||
|
||||
var s = 0.5f * decalSize;
|
||||
|
||||
for (var i = 0; i < inMesh.Count; i += 3)
|
||||
{
|
||||
Vector3 nV1, nV2, nV3, nV4;
|
||||
|
||||
var d1 = Vector3.Dot(inMesh[i + 0], planes[face]) - s;
|
||||
var d2 = Vector3.Dot(inMesh[i + 1], planes[face]) - s;
|
||||
var d3 = Vector3.Dot(inMesh[i + 2], planes[face]) - s;
|
||||
|
||||
var v1Out = d1 > 0;
|
||||
var v2Out = d2 > 0;
|
||||
var v3Out = d3 > 0;
|
||||
|
||||
// Calculate, how many vertices of the face lie outside of the clipping plane
|
||||
var total = (v1Out ? 1 : 0) + (v2Out ? 1 : 0) + (v3Out ? 1 : 0);
|
||||
|
||||
switch (total)
|
||||
{
|
||||
case 0:
|
||||
// The entire face lies inside of the plane, no clipping needed
|
||||
outMesh.Add(inMesh[i]);
|
||||
outMesh.Add(inMesh[i + 1]);
|
||||
outMesh.Add(inMesh[i + 2]);
|
||||
break;
|
||||
case 1:
|
||||
// One vertex lies outside of the plane, perform clipping
|
||||
if (v2Out)
|
||||
{
|
||||
nV1 = inMesh[i];
|
||||
nV2 = inMesh[i + 2];
|
||||
nV3 = ClipSegment(inMesh[i + 1], nV1, planes[face], s);
|
||||
nV4 = ClipSegment(inMesh[i + 1], nV2, planes[face], s);
|
||||
|
||||
outMesh.Add(nV3); outMesh.Add(nV2); outMesh.Add(nV1);
|
||||
outMesh.Add(nV2); outMesh.Add(nV3); outMesh.Add(nV4);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v1Out)
|
||||
{
|
||||
nV1 = inMesh[i + 1];
|
||||
nV2 = inMesh[i + 2];
|
||||
nV3 = ClipSegment(inMesh[i], nV1, planes[face], s);
|
||||
nV4 = ClipSegment(inMesh[i], nV2, planes[face], s);
|
||||
}
|
||||
else // v3Out
|
||||
{
|
||||
nV1 = inMesh[i];
|
||||
nV2 = inMesh[i + 1];
|
||||
nV3 = ClipSegment(inMesh[i + 2], nV1, planes[face], s);
|
||||
nV4 = ClipSegment(inMesh[i + 2], nV2, planes[face], s);
|
||||
}
|
||||
|
||||
outMesh.Add(nV1); outMesh.Add(nV2); outMesh.Add(nV3);
|
||||
outMesh.Add(nV4); outMesh.Add(nV3); outMesh.Add(nV2);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
// Two vertices lies outside of the plane, perform clipping
|
||||
if (!v1Out)
|
||||
{
|
||||
nV1 = inMesh[i];
|
||||
nV2 = ClipSegment(nV1, inMesh[i + 1], planes[face], s);
|
||||
nV3 = ClipSegment(nV1, inMesh[i + 2], planes[face], s);
|
||||
outMesh.Add(nV1); outMesh.Add(nV2); outMesh.Add(nV3);
|
||||
}
|
||||
|
||||
if (!v2Out)
|
||||
{
|
||||
nV1 = inMesh[i + 1];
|
||||
nV2 = ClipSegment(nV1, inMesh[i + 2], planes[face], s);
|
||||
nV3 = ClipSegment(nV1, inMesh[i], planes[face], s);
|
||||
outMesh.Add(nV1); outMesh.Add(nV2); outMesh.Add(nV3);
|
||||
}
|
||||
|
||||
if (!v3Out)
|
||||
{
|
||||
nV1 = inMesh[i + 2];
|
||||
nV2 = ClipSegment(nV1, inMesh[i], planes[face], s);
|
||||
nV3 = ClipSegment(nV1, inMesh[i + 1], planes[face], s);
|
||||
outMesh.Add(nV1); outMesh.Add(nV2); outMesh.Add(nV3);
|
||||
}
|
||||
break;
|
||||
default: // The entire face lies outside of the plane, so let's discard the corresponding vertices
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we just need to re-transform the vertices
|
||||
var theMesh = meshBuilders[mbIndex];
|
||||
|
||||
// Allocate room for UVs
|
||||
if (theMesh.Count > 0)
|
||||
{
|
||||
var uvs = new Vector2[theMesh.Count];
|
||||
|
||||
for (var i = 0; i < theMesh.Count; i++)
|
||||
{
|
||||
var vert = theMesh[i];
|
||||
|
||||
// Calculate the UVs based on the projected coords
|
||||
// They are clipped to (-decalSize .. decalSize) and we want them (0..1)
|
||||
uvs[i] = new Vector2(vert.X / decalSize + 0.5f, vert.Y / decalSize + 0.5f);
|
||||
|
||||
// Tiny nudge in the normal direction so it renders properly over the mesh
|
||||
vert.Z -= decalOffset;
|
||||
|
||||
// From projection space to world space
|
||||
theMesh[i] = Vector3Transform(vert, invProj);
|
||||
}
|
||||
|
||||
// Decal model data ready, create the mesh and return it
|
||||
return BuildMesh(theMesh, uvs);
|
||||
}
|
||||
|
||||
// Return a blank mesh as there's nothing to add
|
||||
return new Mesh();
|
||||
}
|
||||
|
||||
// Build a Mesh from builder data
|
||||
private static unsafe Mesh BuildMesh(List<Vector3> builderVertices, Vector2[] uvs)
|
||||
{
|
||||
Mesh outMesh = new(builderVertices.Count, builderVertices.Count / 3);
|
||||
outMesh.AllocVertices();
|
||||
outMesh.AllocTexCoords();
|
||||
|
||||
var vertices = outMesh.VerticesAs<Vector3>();
|
||||
var texcoords = outMesh.TexCoordsAs<Vector2>();
|
||||
|
||||
for (var i = 0; i < builderVertices.Count; i++)
|
||||
{
|
||||
vertices[i] = builderVertices[i];
|
||||
texcoords[i] = uvs[i];
|
||||
}
|
||||
|
||||
UploadMesh(ref outMesh, false);
|
||||
|
||||
return outMesh;
|
||||
}
|
||||
|
||||
// Button UI element
|
||||
private static bool GuiButton(Rectangle rec, string label)
|
||||
{
|
||||
var bgColor = Color.Gray;
|
||||
var pressed = false;
|
||||
|
||||
if (CheckCollisionPointRec(GetMousePosition(), rec))
|
||||
{
|
||||
bgColor = Color.LightGray;
|
||||
if (IsMouseButtonPressed(MouseButton.Left))
|
||||
{
|
||||
pressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
DrawRectangleRec(rec, bgColor);
|
||||
DrawRectangleLinesEx(rec, 2.0f, Color.DarkGray);
|
||||
|
||||
var fontSize = 10;
|
||||
var textWidth = MeasureText(label, fontSize);
|
||||
|
||||
DrawText(label, (int)(rec.X + rec.Width * 0.5f - textWidth * 0.5f), (int)(rec.Y + rec.Height * 0.5f - fontSize * 0.5f), fontSize, Color.DarkGray);
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - decals");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new Decals();
|
||||
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;
|
||||
}
|
||||
}
|
||||
148
Examples/Models/DirectionalBillboard.cs
Normal file
148
Examples/Models/DirectionalBillboard.cs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - directional billboard
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Robin (@RobinsAviary) 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 Robin (@RobinsAviary)
|
||||
* Killbot art by patvanmackelberg https://opengameart.org/content/killbot-8-directional under CC0
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class DirectionalBillboard : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Models / Directional Billboard";
|
||||
|
||||
public string Title => "raylib [models] example - directional billboard";
|
||||
|
||||
private Camera3D camera;
|
||||
private Texture2D skillbot;
|
||||
private float animTimer;
|
||||
private uint anim;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Set up the camera
|
||||
camera = new();
|
||||
camera.Position = new Vector3(2.0f, 1.0f, 2.0f); // Starting position
|
||||
camera.Target = new Vector3(0.0f, 0.5f, 0.0f); // Target position
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Up vector
|
||||
camera.FovY = 45.0f; // FOV
|
||||
camera.Projection = CameraProjection.Perspective; // Projection type (Standard 3D perspective)
|
||||
|
||||
// Load billboard texture
|
||||
skillbot = LoadTexture("resources/skillbot.png");
|
||||
|
||||
// Timer to update animation
|
||||
animTimer = 0.0f;
|
||||
// Animation frame
|
||||
anim = 0;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
// Update timer with delta time
|
||||
animTimer += GetFrameTime();
|
||||
|
||||
// Update frame index after a certain amount of time (half a second)
|
||||
if (animTimer > 0.5f)
|
||||
{
|
||||
animTimer = 0.0f;
|
||||
anim += 1;
|
||||
}
|
||||
|
||||
// Reset frame index to zero on overflow
|
||||
if (anim >= 4)
|
||||
{
|
||||
anim = 0;
|
||||
}
|
||||
|
||||
// Find the current direction frame based on the camera position to the billboard object
|
||||
var dir = (float)Math.Floor(((Vector2Angle(new Vector2(2.0f, 0.0f), new Vector2(camera.Position.X, camera.Position.Z)) / MathF.PI) * 4.0f) + 0.25f);
|
||||
|
||||
// Correct frame index if angle is negative
|
||||
if (dir < 0.0f)
|
||||
{
|
||||
dir = 8.0f - Math.Abs((int)dir);
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
DrawGrid(10, 1.0f);
|
||||
|
||||
// Draw billboard pointing straight up to the sky, rotated relative to the camera and offset from the bottom
|
||||
DrawBillboardPro(camera, skillbot, new Rectangle(0.0f + (anim * 24.0f), 0.0f + (dir * 24.0f), 24.0f, 24.0f),
|
||||
Vector3.Zero, new Vector3(0.0f, 1.0f, 0.0f), Vector2.One, new Vector2(0.5f, 0.0f), 0, Color.White);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
// Render various variables for reference
|
||||
DrawText($"animation: {anim}", 10, 10, 20, Color.DarkGray);
|
||||
DrawText($"direction frame: {dir:0}", 10, 40, 20, Color.DarkGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
// Unload billboard texture
|
||||
UnloadTexture(skillbot);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - directional billboard");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new DirectionalBillboard();
|
||||
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;
|
||||
}
|
||||
}
|
||||
173
Examples/Models/LoadingM3d.cs
Normal file
173
Examples/Models/LoadingM3d.cs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - loading m3d
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 4.5, last time updated with raylib 4.5
|
||||
*
|
||||
* Example contributed by bzt (@bztsrc) and reviewed by Ramon Santamaria (@raysan5)
|
||||
*
|
||||
* NOTES:
|
||||
* - Model3D (M3D) fileformat specs: https://gitlab.com/bztsrc/model3d
|
||||
* - Bender M3D exported: https://gitlab.com/bztsrc/model3d/-/tree/master/blender
|
||||
*
|
||||
* 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) 2022-2025 bzt (@bztsrc)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class LoadingM3d : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Models / Loading M3D";
|
||||
|
||||
public string Title => "raylib [models] example - loading m3d";
|
||||
|
||||
private Camera3D camera;
|
||||
private Model model;
|
||||
private Vector3 position;
|
||||
private unsafe ModelAnimation* anims;
|
||||
private int animCount;
|
||||
private int animIndex;
|
||||
private float animCurrentFrame;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(1.5f, 1.5f, 1.5f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 0.4f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
// Load model
|
||||
model = LoadModel("resources/models/m3d/cesium_man.m3d"); // Load the animated model mesh and basic data
|
||||
position = new Vector3(0.0f, 0.0f, 0.0f); // Set model position
|
||||
|
||||
// Load animation data
|
||||
animCount = 0;
|
||||
anims = LoadModelAnimations("resources/models/m3d/cesium_man.m3d", ref animCount);
|
||||
|
||||
// Animation playing variables
|
||||
animIndex = 0; // Current animation playing
|
||||
animCurrentFrame = 0.0f; // Current animation frame (supporting interpolated frames)
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
// Select current animation
|
||||
if (IsKeyPressed(KeyboardKey.Right))
|
||||
{
|
||||
animIndex = (animIndex + 1) % animCount;
|
||||
}
|
||||
else if (IsKeyPressed(KeyboardKey.Left))
|
||||
{
|
||||
animIndex = (animIndex + animCount - 1) % animCount;
|
||||
}
|
||||
|
||||
// Update model animation
|
||||
animCurrentFrame += 1.0f;
|
||||
if (animCurrentFrame >= anims[animIndex].KeyFrameCount)
|
||||
{
|
||||
animCurrentFrame = 0.0f;
|
||||
}
|
||||
UpdateModelAnimation(model, anims[animIndex], animCurrentFrame);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
// Draw 3d model with texture
|
||||
if (!IsKeyDown(KeyboardKey.Space))
|
||||
{
|
||||
DrawModel(model, position, 1.0f, Color.White);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw the animated skeleton
|
||||
DrawModelSkeleton(model.Skeleton, anims[animIndex].KeyframePoses[(int)animCurrentFrame], 1.0f, Color.Red);
|
||||
}
|
||||
|
||||
DrawGrid(10, 1.0f);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
DrawText($"Current animation: {anims[animIndex].NameToString()}", 10, 10, 20, Color.LightGray);
|
||||
DrawText("Press SPACE to draw skeleton", 10, 40, 20, Color.Maroon);
|
||||
DrawText("(c) CesiumMan model by KhronosGroup", GetScreenWidth() - 210, GetScreenHeight() - 20, 10, Color.Gray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public unsafe void Unload()
|
||||
{
|
||||
UnloadModelAnimations(anims, animCount); // Unload model animations data
|
||||
UnloadModel(model); // Unload model
|
||||
}
|
||||
|
||||
// Draw model skeleton
|
||||
private static unsafe void DrawModelSkeleton(ModelSkeleton skeleton, Transform* pose, float scale, Color color)
|
||||
{
|
||||
// Loop to (boneCount - 1) because the last one is a special "no bone" bone,
|
||||
// needed to workaround buggy models without a -1, a cube is always drawn at the origin
|
||||
for (var i = 0; i < skeleton.BoneCount - 1; i++)
|
||||
{
|
||||
// Display the frame-pose skeleton
|
||||
DrawCube(pose[i].Translation, scale * 0.05f, scale * 0.05f, scale * 0.05f, color);
|
||||
|
||||
if (skeleton.Bones[i].Parent >= 0)
|
||||
{
|
||||
DrawLine3D(pose[i].Translation, pose[skeleton.Bones[i].Parent].Translation, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - loading m3d");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new LoadingM3d();
|
||||
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;
|
||||
}
|
||||
}
|
||||
246
Examples/Models/LoadingVox.cs
Normal file
246
Examples/Models/LoadingVox.cs
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - loading vox
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 4.0, last time updated with raylib 4.0
|
||||
*
|
||||
* Example contributed by Johann Nadalutti (@procfxgen) 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) 2021-2025 Johann Nadalutti (@procfxgen) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
using Examples.Shared;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class LoadingVox : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MaxVoxFiles = 4;
|
||||
private const int MaxLights = 4;
|
||||
|
||||
#if BROWSER
|
||||
private const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Models / Loading VOX";
|
||||
|
||||
public string Title => "raylib [models] example - loading vox";
|
||||
|
||||
private static readonly string[] VoxFileNames =
|
||||
{
|
||||
"resources/models/vox/chr_knight.vox",
|
||||
"resources/models/vox/chr_sword.vox",
|
||||
"resources/models/vox/monu9.vox",
|
||||
"resources/models/vox/fez.vox"
|
||||
};
|
||||
|
||||
private Camera3D camera;
|
||||
private Model[] models;
|
||||
private int currentModel;
|
||||
private Vector3 modelpos;
|
||||
private Vector3 camerarot;
|
||||
private Shader shader;
|
||||
private Light[] lights;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(10.0f, 10.0f, 10.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
// Load MagicaVoxel files
|
||||
models = new Model[MaxVoxFiles];
|
||||
|
||||
for (var i = 0; i < MaxVoxFiles; i++)
|
||||
{
|
||||
// Load VOX file and measure time
|
||||
var t0 = GetTime() * 1000.0;
|
||||
models[i] = LoadModel(VoxFileNames[i]);
|
||||
var t1 = GetTime() * 1000.0;
|
||||
|
||||
TraceLog(TraceLogLevel.Info, $"[{VoxFileNames[i]}] Model file loaded in {t1 - t0:0.000} ms");
|
||||
|
||||
// Compute model translation matrix to center model on draw position (0, 0 , 0)
|
||||
var bb = GetModelBoundingBox(models[i]);
|
||||
Vector3 center = new();
|
||||
center.X = bb.Min.X + ((bb.Max.X - bb.Min.X) / 2);
|
||||
center.Z = bb.Min.Z + ((bb.Max.Z - bb.Min.Z) / 2);
|
||||
|
||||
var matTranslate = MatrixTranslate(-center.X, 0, -center.Z);
|
||||
models[i].Transform = matTranslate;
|
||||
}
|
||||
|
||||
currentModel = 0;
|
||||
modelpos = new Vector3(0, 0, 0);
|
||||
camerarot = new Vector3(0, 0, 0);
|
||||
|
||||
// Load voxel shader
|
||||
shader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/voxel_lighting.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/voxel_lighting.fs"
|
||||
);
|
||||
|
||||
// Get some required shader locations
|
||||
shader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(shader, "viewPos");
|
||||
// NOTE: "matModel" location name is automatically assigned on shader loading,
|
||||
// no need to get the location again if using that uniform name
|
||||
//shader.Locs[(int)ShaderLocationIndex.MatrixModel] = GetShaderLocation(shader, "matModel");
|
||||
|
||||
// Ambient light level (some basic lighting)
|
||||
var ambientLoc = GetShaderLocation(shader, "ambient");
|
||||
Raylib.SetShaderValue(shader, ambientLoc, new[] { 0.1f, 0.1f, 0.1f, 1.0f }, ShaderUniformDataType.Vec4);
|
||||
|
||||
// Assign out lighting shader to model
|
||||
for (var i = 0; i < MaxVoxFiles; i++)
|
||||
{
|
||||
for (var j = 0; j < models[i].MaterialCount; j++)
|
||||
{
|
||||
models[i].Materials[j].Shader = shader;
|
||||
}
|
||||
}
|
||||
|
||||
// Create lights
|
||||
lights = new Light[MaxLights];
|
||||
lights[0] = Rlights.CreateLight(0, LightType.Point, new Vector3(-20, 20, -20), Vector3.Zero, Color.Gray, shader);
|
||||
lights[1] = Rlights.CreateLight(1, LightType.Point, new Vector3(20, -20, 20), Vector3.Zero, Color.Gray, shader);
|
||||
lights[2] = Rlights.CreateLight(2, LightType.Point, new Vector3(-20, 20, 20), Vector3.Zero, Color.Gray, shader);
|
||||
lights[3] = Rlights.CreateLight(3, LightType.Point, new Vector3(20, -20, -20), Vector3.Zero, Color.Gray, shader);
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsMouseButtonDown(MouseButton.Middle))
|
||||
{
|
||||
var mouseDelta = GetMouseDelta();
|
||||
camerarot.X = mouseDelta.X * 0.05f;
|
||||
camerarot.Y = mouseDelta.Y * 0.05f;
|
||||
}
|
||||
else
|
||||
{
|
||||
camerarot.X = 0;
|
||||
camerarot.Y = 0;
|
||||
}
|
||||
|
||||
// Update camere movement, custom controls
|
||||
UpdateCameraPro(ref camera,
|
||||
new Vector3(
|
||||
(IsKeyDown(KeyboardKey.W) || IsKeyDown(KeyboardKey.Up) ? 0.1f : 0.0f) - (IsKeyDown(KeyboardKey.S) || IsKeyDown(KeyboardKey.Down) ? 0.1f : 0.0f), // Move forward-backward
|
||||
(IsKeyDown(KeyboardKey.D) || IsKeyDown(KeyboardKey.Right) ? 0.1f : 0.0f) - (IsKeyDown(KeyboardKey.A) || IsKeyDown(KeyboardKey.Left) ? 0.1f : 0.0f), // Move right-left
|
||||
0.0f), // Move up-down
|
||||
camerarot, // Camera rotation
|
||||
GetMouseWheelMove() * -2.0f); // Move to target (zoom)
|
||||
|
||||
// Cycle between models on mouse click
|
||||
if (IsMouseButtonPressed(MouseButton.Left))
|
||||
{
|
||||
currentModel = (currentModel + 1) % MaxVoxFiles;
|
||||
}
|
||||
|
||||
// Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f })
|
||||
Raylib.SetShaderValue(shader, shader.Locs[(int)ShaderLocationIndex.VectorView], camera.Position, ShaderUniformDataType.Vec3);
|
||||
|
||||
// Update light values (actually, only enable/disable them)
|
||||
for (var i = 0; i < MaxLights; i++)
|
||||
{
|
||||
Rlights.UpdateLightValues(shader, lights[i]);
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw 3D model
|
||||
BeginMode3D(camera);
|
||||
|
||||
DrawModel(models[currentModel], modelpos, 1.0f, Color.White);
|
||||
DrawGrid(10, 1.0f);
|
||||
|
||||
// Draw spheres to show where the lights are
|
||||
for (var i = 0; i < MaxLights; i++)
|
||||
{
|
||||
if (lights[i].Enabled)
|
||||
{
|
||||
DrawSphereEx(lights[i].Position, 0.2f, 8, 8, lights[i].Color);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawSphereWires(lights[i].Position, 0.2f, 8, 8, ColorAlpha(lights[i].Color, 0.3f));
|
||||
}
|
||||
}
|
||||
|
||||
EndMode3D();
|
||||
|
||||
// Display info
|
||||
DrawRectangle(10, 40, 340, 70, Fade(Color.SkyBlue, 0.5f));
|
||||
DrawRectangleLines(10, 40, 340, 70, Fade(Color.DarkBlue, 0.5f));
|
||||
DrawText("- MOUSE LEFT BUTTON: CYCLE VOX MODELS", 20, 50, 10, Color.Blue);
|
||||
DrawText("- MOUSE MIDDLE BUTTON: ZOOM OR ROTATE CAMERA", 20, 70, 10, Color.Blue);
|
||||
DrawText("- UP-DOWN-LEFT-RIGHT KEYS: MOVE CAMERA", 20, 90, 10, Color.Blue);
|
||||
DrawText($"VOX model file: {GetFileName(VoxFileNames[currentModel])}", 10, 10, 20, Color.Gray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
// Unload models data (GPU VRAM)
|
||||
for (var i = 0; i < MaxVoxFiles; i++)
|
||||
{
|
||||
UnloadModel(models[i]);
|
||||
}
|
||||
|
||||
UnloadShader(shader);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - loading vox");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new LoadingVox();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -30,8 +30,6 @@ public partial class MeshPicking : IExample
|
|||
|
||||
public string Title => "raylib [models] example - mesh picking";
|
||||
|
||||
public bool CursorDisabled => true;
|
||||
|
||||
private Camera3D camera;
|
||||
private Ray ray;
|
||||
private Model tower;
|
||||
|
|
|
|||
|
|
@ -79,8 +79,7 @@ public partial class ModelLoading : IExample
|
|||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
#if BROWSER
|
||||
// NOTE: Drag & drop file loading (IsFileDropped) is not available in the browser
|
||||
// host, so it is skipped here. The default model stays loaded.
|
||||
// NOTE: drag-and-drop model loading is not supported in the browser host; default loaded model is kept.
|
||||
#else
|
||||
// Load new models/textures on drag&drop
|
||||
if (IsFileDropped())
|
||||
|
|
|
|||
235
Examples/Models/PointRendering.cs
Normal file
235
Examples/Models/PointRendering.cs
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - point rendering
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 5.0, last time updated with raylib 5.0
|
||||
*
|
||||
* Example contributed by Reese Gallagher (@satchelfrost) 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) 2024-2025 Reese Gallagher (@satchelfrost)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
[ExcludeFromBrowser("rlEnablePointMode needs glPolygonMode, which OpenGL ES/WebGL lacks (renders as triangles)")]
|
||||
public partial class PointRendering : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MaxPoints = 10000000; // 10 million
|
||||
private const int MinPoints = 1000; // 1 thousand
|
||||
|
||||
public string Name => "Models / Point Rendering";
|
||||
|
||||
public string Title => "raylib [models] example - point rendering";
|
||||
|
||||
private static readonly Random Rand = new();
|
||||
|
||||
private Camera3D camera;
|
||||
private Vector3 position;
|
||||
private bool useDrawModelPoints;
|
||||
private bool numPointsChanged;
|
||||
private int numPoints;
|
||||
private Mesh mesh;
|
||||
private Model model;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
camera = new()
|
||||
{
|
||||
Position = new Vector3(3.0f, 3.0f, 3.0f),
|
||||
Target = new Vector3(0.0f, 0.0f, 0.0f),
|
||||
Up = new Vector3(0.0f, 1.0f, 0.0f),
|
||||
FovY = 45.0f,
|
||||
Projection = CameraProjection.Perspective
|
||||
};
|
||||
|
||||
position = new Vector3(0.0f, 0.0f, 0.0f);
|
||||
useDrawModelPoints = true;
|
||||
numPointsChanged = false;
|
||||
numPoints = 1000;
|
||||
|
||||
mesh = GenMeshPoints(numPoints);
|
||||
model = LoadModelFromMesh(mesh);
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Space))
|
||||
{
|
||||
useDrawModelPoints = !useDrawModelPoints;
|
||||
}
|
||||
if (IsKeyPressed(KeyboardKey.Up))
|
||||
{
|
||||
numPoints = (numPoints * 10 > MaxPoints) ? MaxPoints : numPoints * 10;
|
||||
numPointsChanged = true;
|
||||
}
|
||||
if (IsKeyPressed(KeyboardKey.Down))
|
||||
{
|
||||
numPoints = (numPoints / 10 < MinPoints) ? MinPoints : numPoints / 10;
|
||||
numPointsChanged = true;
|
||||
}
|
||||
|
||||
// Upload a different point cloud size
|
||||
if (numPointsChanged)
|
||||
{
|
||||
UnloadModel(model);
|
||||
mesh = GenMeshPoints(numPoints);
|
||||
model = LoadModelFromMesh(mesh);
|
||||
numPointsChanged = false;
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.Black);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
// The new method only uploads the points once to the GPU
|
||||
if (useDrawModelPoints)
|
||||
{
|
||||
DrawModelPoints(model, position, 1.0f, Color.White);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The old method must continually draw the "points" (lines)
|
||||
for (var i = 0; i < numPoints; i++)
|
||||
{
|
||||
Vector3 pos = new(
|
||||
mesh.Vertices[i * 3 + 0],
|
||||
mesh.Vertices[i * 3 + 1],
|
||||
mesh.Vertices[i * 3 + 2]
|
||||
);
|
||||
Color color = new(
|
||||
mesh.Colors[i * 4 + 0],
|
||||
mesh.Colors[i * 4 + 1],
|
||||
mesh.Colors[i * 4 + 2],
|
||||
mesh.Colors[i * 4 + 3]
|
||||
);
|
||||
|
||||
DrawPoint3D(pos, color);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a unit sphere for reference
|
||||
DrawSphereWires(position, 1.0f, 10, 10, Color.Yellow);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
// Draw UI text
|
||||
DrawText($"Point Count: {numPoints}", 10, screenHeight - 50, 40, Color.White);
|
||||
DrawText("UP - Increase points", 10, 40, 20, Color.White);
|
||||
DrawText("DOWN - Decrease points", 10, 70, 20, Color.White);
|
||||
DrawText("SPACE - Drawing function", 10, 100, 20, Color.White);
|
||||
|
||||
if (useDrawModelPoints)
|
||||
{
|
||||
DrawText("Using: DrawModelPoints()", 10, 130, 20, Color.Green);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawText("Using: DrawPoint3D()", 10, 130, 20, Color.Red);
|
||||
}
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadModel(model);
|
||||
}
|
||||
|
||||
// Generate a spherical point cloud
|
||||
private static unsafe Mesh GenMeshPoints(int numPoints)
|
||||
{
|
||||
Mesh mesh = new(numPoints, 1);
|
||||
mesh.AllocVertices();
|
||||
mesh.AllocColors();
|
||||
|
||||
// REF: https://en.wikipedia.org/wiki/Spherical_coordinate_system
|
||||
for (var i = 0; i < numPoints; i++)
|
||||
{
|
||||
var theta = MathF.PI * (float)Rand.NextDouble();
|
||||
var phi = 2.0f * MathF.PI * (float)Rand.NextDouble();
|
||||
var r = 10.0f * (float)Rand.NextDouble();
|
||||
|
||||
mesh.Vertices[i * 3 + 0] = r * MathF.Sin(theta) * MathF.Cos(phi);
|
||||
mesh.Vertices[i * 3 + 1] = r * MathF.Sin(theta) * MathF.Sin(phi);
|
||||
mesh.Vertices[i * 3 + 2] = r * MathF.Cos(theta);
|
||||
|
||||
var color = ColorFromHSV(r * 360.0f, 1.0f, 1.0f);
|
||||
|
||||
mesh.Colors[i * 4 + 0] = color.R;
|
||||
mesh.Colors[i * 4 + 1] = color.G;
|
||||
mesh.Colors[i * 4 + 2] = color.B;
|
||||
mesh.Colors[i * 4 + 3] = color.A;
|
||||
}
|
||||
|
||||
// Upload mesh data from CPU (RAM) to GPU (VRAM) memory
|
||||
UploadMesh(ref mesh, false);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// Draw a model points
|
||||
// WARNING: OpenGL ES 2.0 does not support point mode drawing
|
||||
private static void DrawModelPoints(Model model, Vector3 position, float scale, Color tint)
|
||||
{
|
||||
Rlgl.EnablePointMode();
|
||||
Rlgl.DisableBackfaceCulling();
|
||||
|
||||
DrawModel(model, position, scale, tint);
|
||||
|
||||
Rlgl.EnableBackfaceCulling();
|
||||
Rlgl.DisablePointMode();
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - point rendering");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new PointRendering();
|
||||
game.Init();
|
||||
|
||||
// Main game loop
|
||||
while (!WindowShouldClose())
|
||||
{
|
||||
game.Update();
|
||||
}
|
||||
|
||||
game.Unload();
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
CloseWindow();
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
122
Examples/Models/RotatingCube.cs
Normal file
122
Examples/Models/RotatingCube.cs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - rotating cube
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Jopestpe (@jopestpe)
|
||||
*
|
||||
* 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 Jopestpe (@jopestpe)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class RotatingCube : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Models / Rotating Cube";
|
||||
|
||||
public string Title => "raylib [models] example - rotating cube";
|
||||
|
||||
private Camera3D camera;
|
||||
private Model model;
|
||||
private Texture2D texture;
|
||||
private float rotation;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(0.0f, 3.0f, 3.0f);
|
||||
camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
camera.FovY = 45.0f;
|
||||
camera.Projection = CameraProjection.Perspective;
|
||||
|
||||
// Load image to create texture for the cube
|
||||
model = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f));
|
||||
var img = LoadImage("resources/cubicmap_atlas.png");
|
||||
var crop = ImageFromImage(img, new Rectangle(0, img.Height / 2.0f, img.Width / 2.0f, img.Height / 2.0f));
|
||||
texture = LoadTextureFromImage(crop);
|
||||
UnloadImage(img);
|
||||
UnloadImage(crop);
|
||||
|
||||
model.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture = texture;
|
||||
|
||||
rotation = 0.0f;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
rotation += 1.0f;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
// Draw model defining: position, size, rotation-axis, rotation (degrees), size, and tint-color
|
||||
DrawModelEx(model, new Vector3(0.0f, 0.0f, 0.0f), new Vector3(0.5f, 1.0f, 0.0f),
|
||||
rotation, new Vector3(1.0f, 1.0f, 1.0f), Color.White);
|
||||
|
||||
DrawGrid(10, 1.0f);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadTexture(texture); // Unload texture
|
||||
UnloadModel(model); // Unload model
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - rotating cube");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new RotatingCube();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,13 +19,18 @@ using static Raylib_cs.Raylib;
|
|||
|
||||
namespace Examples.Models;
|
||||
|
||||
[ExcludeFromBrowser("cubemap generation is too memory-heavy on web (upstream note)")]
|
||||
public partial class SkyboxDemo : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
// GLSL version used for shaders (330 desktop, 100 web/GLES)
|
||||
#if BROWSER
|
||||
public const int GlslVersion = 100;
|
||||
#else
|
||||
public const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Models / Skybox Demo";
|
||||
|
||||
|
|
|
|||
163
Examples/Models/TesseractView.cs
Normal file
163
Examples/Models/TesseractView.cs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [models] example - tesseract view
|
||||
*
|
||||
* NOTE: This example only works on platforms that support drag & drop (Windows, Linux, OSX, Html5?)
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Timothy van der Valk (@arceryz) 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) 2024-2025 Timothy van der Valk (@arceryz) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Models;
|
||||
|
||||
public partial class TesseractView : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Models / Tesseract View";
|
||||
|
||||
public string Title => "raylib [models] example - tesseract view";
|
||||
|
||||
// Define the camera to look into our 3d world
|
||||
private Camera3D camera;
|
||||
|
||||
// Find the coordinates by setting XYZW to +-1
|
||||
private Vector4[] tesseract;
|
||||
|
||||
private float rotation;
|
||||
private Vector3[] transformed;
|
||||
private float[] wValues;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new Camera3D();
|
||||
camera.Position = new Vector3(4.0f, 4.0f, 4.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 0.0f, 1.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 50.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera mode type
|
||||
|
||||
// Find the coordinates by setting XYZW to +-1
|
||||
tesseract = new Vector4[16]
|
||||
{
|
||||
new( 1, 1, 1, 1 ), new( 1, 1, 1, -1 ),
|
||||
new( 1, 1, -1, 1 ), new( 1, 1, -1, -1 ),
|
||||
new( 1, -1, 1, 1 ), new( 1, -1, 1, -1 ),
|
||||
new( 1, -1, -1, 1 ), new( 1, -1, -1, -1 ),
|
||||
new(-1, 1, 1, 1 ), new(-1, 1, 1, -1 ),
|
||||
new(-1, 1, -1, 1 ), new(-1, 1, -1, -1 ),
|
||||
new(-1, -1, 1, 1 ), new(-1, -1, 1, -1 ),
|
||||
new(-1, -1, -1, 1 ), new(-1, -1, -1, -1 ),
|
||||
};
|
||||
|
||||
rotation = 0.0f;
|
||||
transformed = new Vector3[16];
|
||||
wValues = new float[16];
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
rotation = DEG2RAD * 45.0f * (float)GetTime();
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
Vector4 p = tesseract[i];
|
||||
|
||||
// Rotate the XW part of the vector
|
||||
Vector2 rotXW = Vector2Rotate(new Vector2(p.X, p.W), rotation);
|
||||
p.X = rotXW.X;
|
||||
p.W = rotXW.Y;
|
||||
|
||||
// Projection from XYZW to XYZ from perspective point (0, 0, 0, 3)
|
||||
// NOTE: Trace a ray from (0, 0, 0, 3) > p and continue until W = 0
|
||||
float c = 3.0f / (3.0f - p.W);
|
||||
p.X = c * p.X;
|
||||
p.Y = c * p.Y;
|
||||
p.Z = c * p.Z;
|
||||
|
||||
// Split XYZ coordinate and W values later for drawing
|
||||
transformed[i] = new Vector3(p.X, p.Y, p.Z);
|
||||
wValues[i] = p.W;
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
// Draw spheres to indicate the W value
|
||||
DrawSphere(transformed[i], MathF.Abs(wValues[i] * 0.1f), Color.Red);
|
||||
|
||||
for (int j = 0; j < 16; j++)
|
||||
{
|
||||
// Two lines are connected if they differ by 1 coordinate
|
||||
// This way we dont have to keep an edge list
|
||||
Vector4 v1 = tesseract[i];
|
||||
Vector4 v2 = tesseract[j];
|
||||
int diff = (v1.X == v2.X ? 1 : 0) + (v1.Y == v2.Y ? 1 : 0) + (v1.Z == v2.Z ? 1 : 0) + (v1.W == v2.W ? 1 : 0);
|
||||
|
||||
// Draw only differing by 1 coordinate and the lower index only (duplicate lines)
|
||||
if (diff == 3 && i < j) DrawLine3D(transformed[i], transformed[j], Color.Maroon);
|
||||
}
|
||||
}
|
||||
EndMode3D();
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [models] example - tesseract view");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new TesseractView();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,6 @@ namespace Examples;
|
|||
|
||||
internal static class Program
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private static unsafe void Main(string[] args)
|
||||
{
|
||||
Raylib.SetTraceLogCallback(&Logging.LogConsole);
|
||||
|
|
@ -50,7 +47,7 @@ internal static class Program
|
|||
SetConfigFlags(example.ConfigFlags);
|
||||
}
|
||||
|
||||
InitWindow(screenWidth, screenHeight, example.Title);
|
||||
InitWindow(example.Width, example.Height, example.Title);
|
||||
|
||||
if (example.CursorDisabled)
|
||||
{
|
||||
|
|
@ -63,17 +60,27 @@ internal static class Program
|
|||
|
||||
SetTargetFPS(example.TargetFps);
|
||||
|
||||
try
|
||||
{
|
||||
example.Init();
|
||||
|
||||
while (!WindowShouldClose())
|
||||
while (!example.ShouldClose)
|
||||
{
|
||||
example.Update();
|
||||
}
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
example.Unload();
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseWindow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RunExampleProcess(
|
||||
string fileName,
|
||||
|
|
|
|||
166
Examples/Shaders/AsciiRendering.cs
Normal file
166
Examples/Shaders/AsciiRendering.cs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - ascii rendering
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Maicon Santana (@maiconpintoabreu) 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 Maicon Santana (@maiconpintoabreu)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public partial class AsciiRendering : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Shaders / Ascii Rendering";
|
||||
|
||||
public string Title => "raylib [shaders] example - ascii rendering";
|
||||
|
||||
private Texture2D fudesumi;
|
||||
private Texture2D raysan;
|
||||
private Shader shader;
|
||||
private int resolutionLoc;
|
||||
private int fontSizeLoc;
|
||||
private float fontSize;
|
||||
private Vector2 circlePos;
|
||||
private float circleSpeed;
|
||||
private RenderTexture2D target;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Texture to test static drawing
|
||||
fudesumi = LoadTexture("resources/fudesumi.png");
|
||||
// Texture to test moving drawing
|
||||
raysan = LoadTexture("resources/raysan.png");
|
||||
|
||||
// Load shader to be used on postprocessing
|
||||
shader = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/ascii.fs");
|
||||
|
||||
// These locations are used to send data to the GPU
|
||||
resolutionLoc = GetShaderLocation(shader, "resolution");
|
||||
fontSizeLoc = GetShaderLocation(shader, "fontSize");
|
||||
|
||||
// Set the character size for the ASCII effect
|
||||
// Fontsize should be 9 or more
|
||||
fontSize = 9.0f;
|
||||
|
||||
// Send the updated values to the shader
|
||||
var resolution = new[] { (float)screenWidth, (float)screenHeight };
|
||||
Raylib.SetShaderValue(shader, resolutionLoc, resolution, ShaderUniformDataType.Vec2);
|
||||
|
||||
circlePos = new Vector2(40.0f, screenHeight * 0.5f);
|
||||
circleSpeed = 1.0f;
|
||||
|
||||
// RenderTexture to apply the postprocessing later
|
||||
target = LoadRenderTexture(screenWidth, screenHeight);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
circlePos.X += circleSpeed;
|
||||
if ((circlePos.X > 200.0f) || (circlePos.X < 40.0f))
|
||||
{
|
||||
circleSpeed *= -1; // Revert speed
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Left) && (fontSize > 9.0))
|
||||
{
|
||||
fontSize -= 1; // Reduce fontSize
|
||||
}
|
||||
if (IsKeyPressed(KeyboardKey.Right) && (fontSize < 15.0))
|
||||
{
|
||||
fontSize += 1; // Increase fontSize
|
||||
}
|
||||
|
||||
// Set fontsize for the shader
|
||||
Raylib.SetShaderValue(shader, fontSizeLoc, fontSize, ShaderUniformDataType.Float);
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginTextureMode(target);
|
||||
ClearBackground(Color.White);
|
||||
|
||||
// Draw scene in our render texture
|
||||
DrawTexture(fudesumi, 500, -30, Color.White);
|
||||
DrawTextureV(raysan, circlePos, Color.White);
|
||||
EndTextureMode();
|
||||
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginShaderMode(shader);
|
||||
// Draw the scene texture (that we rendered earlier) to the screen
|
||||
// The shader will process every pixel of this texture
|
||||
DrawTextureRec(
|
||||
target.Texture,
|
||||
new Rectangle(0, 0, target.Texture.Width, -target.Texture.Height),
|
||||
new Vector2(0, 0),
|
||||
Color.White
|
||||
);
|
||||
EndShaderMode();
|
||||
|
||||
DrawRectangle(0, 0, screenWidth, 40, Color.Black);
|
||||
DrawText($"Ascii effect - FontSize:{fontSize,2:F0} - [Left] -1 [Right] +1 ", 120, 10, 20, Color.LightGray);
|
||||
DrawFPS(10, 10);
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadRenderTexture(target); // Unload render texture
|
||||
|
||||
UnloadShader(shader); // Unload shader
|
||||
UnloadTexture(fudesumi); // Unload texture
|
||||
UnloadTexture(raysan); // Unload texture
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - ascii rendering");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new AsciiRendering();
|
||||
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;
|
||||
}
|
||||
}
|
||||
252
Examples/Shaders/CelShading.cs
Normal file
252
Examples/Shaders/CelShading.cs
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - cel shading
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
|
||||
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
|
||||
*
|
||||
* NOTE: Shaders used in this example are #version 330 (OpenGL 3.3)
|
||||
*
|
||||
* Example contributed by Gleb A (@ggrizzly) 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) 2026 Gleb A (@ggrizzly)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
using static Raylib_cs.Rlgl;
|
||||
using Examples.Shared;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public partial class CelShading : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MaxLights = 4;
|
||||
|
||||
// rlgl cull face modes (rlgl.h: RL_CULL_FACE_FRONT = 0, RL_CULL_FACE_BACK = 1)
|
||||
private const int CullFaceFront = 0;
|
||||
private const int CullFaceBack = 1;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Shaders / Cel Shading";
|
||||
|
||||
public string Title => "raylib [shaders] example - cel shading";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
private Camera3D camera;
|
||||
private Model model;
|
||||
private Shader celShader;
|
||||
private Shader defaultShader;
|
||||
private Shader outlineShader;
|
||||
private float numBands;
|
||||
private int numBandsLoc;
|
||||
private int outlineThicknessLoc;
|
||||
private Light[] lights;
|
||||
private bool celEnabled;
|
||||
private bool outlineEnabled;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
camera = new();
|
||||
camera.Position = new Vector3(9.0f, 6.0f, 9.0f);
|
||||
camera.Target = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
camera.FovY = 45.0f;
|
||||
camera.Projection = CameraProjection.Perspective;
|
||||
|
||||
// Load model
|
||||
model = LoadModel("resources/models/old_car_new.glb");
|
||||
|
||||
// Load cel shader
|
||||
celShader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/cel.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/cel.fs"
|
||||
);
|
||||
celShader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(celShader, "viewPos");
|
||||
|
||||
// Apply cel shader to model, keep copy of default shader
|
||||
defaultShader = model.Materials[0].Shader;
|
||||
model.Materials[0].Shader = celShader;
|
||||
|
||||
// numBands: controls toon quantization steps (2 = hard binary, 20 = near-smooth)
|
||||
numBands = 10.0f;
|
||||
numBandsLoc = GetShaderLocation(celShader, "numBands");
|
||||
Raylib.SetShaderValue(celShader, numBandsLoc, numBands, ShaderUniformDataType.Float);
|
||||
|
||||
// Inverted-hull outline shader: draws back faces extruded along normals
|
||||
outlineShader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/outline_hull.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/outline_hull.fs"
|
||||
);
|
||||
outlineThicknessLoc = GetShaderLocation(outlineShader, "outlineThickness");
|
||||
|
||||
// Single directional white light, angled so toon bands are visible on the model sides.
|
||||
// Spins opposite to CAMERA_ORBITAL (0.5 rad/s) so lighting changes as you watch.
|
||||
lights = new Light[MaxLights];
|
||||
lights[0] = Rlights.CreateLight(
|
||||
0,
|
||||
LightType.Directorional,
|
||||
new Vector3(50.0f, 50.0f, 50.0f),
|
||||
Vector3.Zero,
|
||||
Color.White,
|
||||
celShader
|
||||
);
|
||||
|
||||
celEnabled = true;
|
||||
outlineEnabled = true;
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
Raylib.SetShaderValue(
|
||||
celShader,
|
||||
celShader.Locs[(int)ShaderLocationIndex.VectorView],
|
||||
camera.Position,
|
||||
ShaderUniformDataType.Vec3
|
||||
);
|
||||
|
||||
// [Z] Toggle cel shading on/off
|
||||
if (IsKeyPressed(KeyboardKey.Z))
|
||||
{
|
||||
celEnabled = !celEnabled;
|
||||
if (celEnabled)
|
||||
{
|
||||
model.Materials[0].Shader = celShader; // Apply cel shader to model
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Materials[0].Shader = defaultShader; // Apply default shader to model
|
||||
}
|
||||
}
|
||||
|
||||
// [C] Toggle outline on/off
|
||||
if (IsKeyPressed(KeyboardKey.C))
|
||||
{
|
||||
outlineEnabled = !outlineEnabled;
|
||||
}
|
||||
|
||||
// [Q/E] Decrease/increase toon band count (press or hold to repeat)
|
||||
if (IsKeyPressed(KeyboardKey.E) || IsKeyPressedRepeat(KeyboardKey.E))
|
||||
{
|
||||
numBands = Clamp(numBands + 1.0f, 2.0f, 20.0f);
|
||||
}
|
||||
if (IsKeyPressed(KeyboardKey.Q) || IsKeyPressedRepeat(KeyboardKey.Q))
|
||||
{
|
||||
numBands = Clamp(numBands - 1.0f, 2.0f, 20.0f);
|
||||
}
|
||||
Raylib.SetShaderValue(celShader, numBandsLoc, numBands, ShaderUniformDataType.Float);
|
||||
|
||||
// Spin light opposite to CAMERA_ORBITAL (0.5 rad/s), angled 45 degrees off vertical
|
||||
float t = (float)GetTime();
|
||||
lights[0].Position = new Vector3(MathF.Sin(-t * 0.3f) * 5.0f, 5.0f, MathF.Cos(-t * 0.3f) * 5.0f);
|
||||
|
||||
for (var i = 0; i < MaxLights; i++)
|
||||
{
|
||||
Rlights.UpdateLightValues(celShader, lights[i]);
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
if (outlineEnabled)
|
||||
{
|
||||
// Outline pass: cull front faces, draw extruded back faces as silhouette
|
||||
float thickness = 0.005f;
|
||||
Raylib.SetShaderValue(outlineShader, outlineThicknessLoc, thickness, ShaderUniformDataType.Float);
|
||||
|
||||
SetCullFace(CullFaceFront);
|
||||
|
||||
model.Materials[0].Shader = outlineShader;
|
||||
|
||||
DrawModel(model, Vector3.Zero, 0.75f, Color.White);
|
||||
|
||||
if (celEnabled)
|
||||
{
|
||||
model.Materials[0].Shader = celShader; // Apply cel shader to model
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Materials[0].Shader = defaultShader; // Apply default shader to model
|
||||
}
|
||||
|
||||
SetCullFace(CullFaceBack);
|
||||
}
|
||||
|
||||
DrawModel(model, Vector3.Zero, 0.75f, Color.White);
|
||||
DrawSphereEx(lights[0].Position, 0.2f, 50, 50, Color.Yellow); // Light position indicator
|
||||
DrawGrid(10, 10.0f);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
DrawFPS(10, 10);
|
||||
DrawText($"Cel: {(celEnabled ? "ON" : "OFF")} [Z]", 10, 65, 20, celEnabled ? Color.DarkGreen : Color.DarkGray);
|
||||
DrawText($"Outline: {(outlineEnabled ? "ON" : "OFF")} [C]", 10, 90, 20, outlineEnabled ? Color.DarkGreen : Color.DarkGray);
|
||||
DrawText($"Bands: {numBands:0} [Q/E]", 10, 115, 20, Color.DarkGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadModel(model);
|
||||
UnloadShader(celShader);
|
||||
UnloadShader(outlineShader);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - cel shading");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new CelShading();
|
||||
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;
|
||||
}
|
||||
}
|
||||
184
Examples/Shaders/ColorCorrection.cs
Normal file
184
Examples/Shaders/ColorCorrection.cs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - color correction
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
|
||||
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Jordi Santonja (@JordSant) 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 Jordi Santonja (@JordSant)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public partial class ColorCorrection : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
private const int MaxTextures = 4;
|
||||
|
||||
public string Name => "Shaders / Color Correction";
|
||||
|
||||
public string Title => "raylib [shaders] example - color correction";
|
||||
|
||||
private Texture2D[] texture;
|
||||
private Shader shdrColorCorrection;
|
||||
private int imageIndex;
|
||||
private int resetButtonClicked;
|
||||
private float contrast;
|
||||
private float saturation;
|
||||
private float brightness;
|
||||
private int contrastLoc;
|
||||
private int saturationLoc;
|
||||
private int brightnessLoc;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
texture = new[]
|
||||
{
|
||||
LoadTexture("resources/parrots.png"),
|
||||
LoadTexture("resources/cat.png"),
|
||||
LoadTexture("resources/mandrill.png"),
|
||||
LoadTexture("resources/fudesumi.png")
|
||||
};
|
||||
|
||||
shdrColorCorrection = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/color_correction.fs");
|
||||
|
||||
imageIndex = 0;
|
||||
resetButtonClicked = 0;
|
||||
|
||||
contrast = 0.0f;
|
||||
saturation = 0.0f;
|
||||
brightness = 0.0f;
|
||||
|
||||
// Get shader locations
|
||||
contrastLoc = GetShaderLocation(shdrColorCorrection, "contrast");
|
||||
saturationLoc = GetShaderLocation(shdrColorCorrection, "saturation");
|
||||
brightnessLoc = GetShaderLocation(shdrColorCorrection, "brightness");
|
||||
|
||||
// Set shader values (they can be changed later)
|
||||
Raylib.SetShaderValue(shdrColorCorrection, contrastLoc, contrast, ShaderUniformDataType.Float);
|
||||
Raylib.SetShaderValue(shdrColorCorrection, saturationLoc, saturation, ShaderUniformDataType.Float);
|
||||
Raylib.SetShaderValue(shdrColorCorrection, brightnessLoc, brightness, ShaderUniformDataType.Float);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Select texture to draw
|
||||
if (IsKeyPressed(KeyboardKey.One)) imageIndex = 0;
|
||||
else if (IsKeyPressed(KeyboardKey.Two)) imageIndex = 1;
|
||||
else if (IsKeyPressed(KeyboardKey.Three)) imageIndex = 2;
|
||||
else if (IsKeyPressed(KeyboardKey.Four)) imageIndex = 3;
|
||||
|
||||
// Reset values to 0
|
||||
if (IsKeyPressed(KeyboardKey.R) || resetButtonClicked != 0)
|
||||
{
|
||||
contrast = 0.0f;
|
||||
saturation = 0.0f;
|
||||
brightness = 0.0f;
|
||||
}
|
||||
|
||||
// Send the values to the shader
|
||||
Raylib.SetShaderValue(shdrColorCorrection, contrastLoc, contrast, ShaderUniformDataType.Float);
|
||||
Raylib.SetShaderValue(shdrColorCorrection, saturationLoc, saturation, ShaderUniformDataType.Float);
|
||||
Raylib.SetShaderValue(shdrColorCorrection, brightnessLoc, brightness, ShaderUniformDataType.Float);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginShaderMode(shdrColorCorrection);
|
||||
|
||||
DrawTexture(texture[imageIndex], 580 / 2 - texture[imageIndex].Width / 2, GetScreenHeight() / 2 - texture[imageIndex].Height / 2, Color.White);
|
||||
|
||||
EndShaderMode();
|
||||
|
||||
DrawLine(580, 0, 580, GetScreenHeight(), new Color(218, 218, 218, 255));
|
||||
DrawRectangle(580, 0, GetScreenWidth(), GetScreenHeight(), new Color(232, 232, 232, 255));
|
||||
|
||||
// Draw UI info text
|
||||
DrawText("Color Correction", 585, 40, 20, Color.Gray);
|
||||
|
||||
DrawText("Picture", 602, 75, 10, Color.Gray);
|
||||
DrawText("Press [1] - [4] to Change Picture", 600, 230, 8, Color.Gray);
|
||||
DrawText("Press [R] to Reset Values", 600, 250, 8, Color.Gray);
|
||||
|
||||
// Draw GUI controls
|
||||
//------------------------------------------------------------------------------
|
||||
// NOTE: raygui is not bound in raylib-cs; controls are kept for reference. Use
|
||||
// keyboard shortcuts ([1]-[4], [R]) to interact with the example.
|
||||
/*GuiToggleGroup(new Rectangle( 645, 70, 20, 20 ), "1;2;3;4", ref imageIndex);
|
||||
|
||||
GuiSliderBar(new Rectangle( 645, 100, 120, 20 ), "Contrast", TextFormat("%.0f", contrast), ref contrast, -100.0f, 100.0f);
|
||||
GuiSliderBar(new Rectangle( 645, 130, 120, 20 ), "Saturation", TextFormat("%.0f", saturation), ref saturation, -100.0f, 100.0f);
|
||||
GuiSliderBar(new Rectangle( 645, 160, 120, 20 ), "Brightness", TextFormat("%.0f", brightness), ref brightness, -100.0f, 100.0f);
|
||||
|
||||
resetButtonClicked = GuiButton(new Rectangle( 645, 190, 40, 20 ), "Reset");*/
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
DrawFPS(710, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
for (var i = 0; i < MaxTextures; i++)
|
||||
{
|
||||
UnloadTexture(texture[i]);
|
||||
}
|
||||
UnloadShader(shdrColorCorrection);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - color correction");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new ColorCorrection();
|
||||
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;
|
||||
}
|
||||
}
|
||||
400
Examples/Shaders/DeferredRendering.cs
Normal file
400
Examples/Shaders/DeferredRendering.cs
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - deferred rendering
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* NOTE: This example requires raylib OpenGL 3.3 or OpenGL ES 3.0
|
||||
*
|
||||
* Example originally created with raylib 4.5, last time updated with raylib 4.5
|
||||
*
|
||||
* Example contributed by Justin Andreas Lacoste (@27justin) 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) 2023-2025 Justin Andreas Lacoste (@27justin)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using Examples.Shared;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
[ExcludeFromBrowser("multiple-render-target G-buffer, unsupported on WebGL1/GLSL100")]
|
||||
public partial class DeferredRendering : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
private const int MaxCubes = 30;
|
||||
private const int MaxLights = 4;
|
||||
private const float CubeScale = 0.25f;
|
||||
|
||||
// GL_READ_FRAMEBUFFER / GL_DRAW_FRAMEBUFFER / GL_DEPTH_BUFFER_BIT
|
||||
private const uint RlReadFramebuffer = 0x8CA8;
|
||||
private const uint RlDrawFramebuffer = 0x8CA9;
|
||||
private const int GlDepthBufferBit = 0x00000100;
|
||||
|
||||
public string Name => "Shaders / Deferred Rendering";
|
||||
|
||||
public string Title => "raylib [shaders] example - deferred rendering";
|
||||
|
||||
// GBuffer data
|
||||
private struct GBuffer
|
||||
{
|
||||
public uint FramebufferId;
|
||||
|
||||
public uint PositionTextureId;
|
||||
public uint NormalTextureId;
|
||||
public uint AlbedoSpecTextureId;
|
||||
|
||||
public uint DepthRenderbufferId;
|
||||
}
|
||||
|
||||
// Deferred mode passes
|
||||
private enum DeferredMode
|
||||
{
|
||||
Position,
|
||||
Normal,
|
||||
Albedo,
|
||||
Shading
|
||||
}
|
||||
|
||||
private Camera3D camera;
|
||||
private Model model;
|
||||
private Model cube;
|
||||
private Shader gbufferShader;
|
||||
private Shader deferredShader;
|
||||
private GBuffer gBuffer;
|
||||
private Light[] lights;
|
||||
private Vector3[] cubePositions;
|
||||
private float[] cubeRotations;
|
||||
private DeferredMode mode;
|
||||
|
||||
// Texture units our g-buffer textures are bound to
|
||||
private const int TexUnitPosition = 0;
|
||||
private const int TexUnitNormal = 1;
|
||||
private const int TexUnitAlbedoSpec = 2;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
camera = new();
|
||||
camera.Position = new Vector3(5.0f, 4.0f, 5.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 1.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 60.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
// Load plane model from a generated mesh
|
||||
model = LoadModelFromMesh(GenMeshPlane(10.0f, 10.0f, 3, 3));
|
||||
cube = LoadModelFromMesh(GenMeshCube(2.0f, 2.0f, 2.0f));
|
||||
|
||||
// Load geometry buffer (G-buffer) shader and deferred shader
|
||||
gbufferShader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/gbuffer.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/gbuffer.fs"
|
||||
);
|
||||
|
||||
deferredShader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/deferred_shading.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/deferred_shading.fs"
|
||||
);
|
||||
deferredShader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(deferredShader, "viewPosition");
|
||||
|
||||
// Initialize the G-buffer
|
||||
gBuffer = new();
|
||||
gBuffer.FramebufferId = Rlgl.LoadFramebuffer();
|
||||
if (gBuffer.FramebufferId == 0)
|
||||
{
|
||||
TraceLog(TraceLogLevel.Warning, "Failed to create framebufferId");
|
||||
}
|
||||
|
||||
Rlgl.EnableFramebuffer(gBuffer.FramebufferId);
|
||||
|
||||
// NOTE: Vertex positions are stored in a texture for simplicity. A better approach would use a depth texture
|
||||
// (instead of a detph renderbuffer) to reconstruct world positions in the final render shader via clip-space position,
|
||||
// depth, and the inverse view/projection matrices
|
||||
|
||||
// 16-bit precision ensures OpenGL ES 3 compatibility, though it may lack precision for real scenarios
|
||||
gBuffer.PositionTextureId = Rlgl.LoadTexture(null, screenWidth, screenHeight, PixelFormat.UncompressedR16G16B16, 1);
|
||||
|
||||
// Similarly, 16-bit precision is used for normals ensures OpenGL ES 3 compatibility
|
||||
gBuffer.NormalTextureId = Rlgl.LoadTexture(null, screenWidth, screenHeight, PixelFormat.UncompressedR16G16B16, 1);
|
||||
|
||||
// Albedo (diffuse color) and specular strength can be combined into one texture
|
||||
// The color in RGB, and the specular strength in the alpha channel
|
||||
gBuffer.AlbedoSpecTextureId = Rlgl.LoadTexture(null, screenWidth, screenHeight, PixelFormat.UncompressedR8G8B8A8, 1);
|
||||
|
||||
// Activate the draw buffers for our framebufferId
|
||||
Rlgl.ActiveDrawBuffers(3);
|
||||
|
||||
// Now we attach our textures to the framebufferId
|
||||
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.PositionTextureId, FramebufferAttachType.ColorChannel0, FramebufferAttachTextureType.Texture2D, 0);
|
||||
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.NormalTextureId, FramebufferAttachType.ColorChannel1, FramebufferAttachTextureType.Texture2D, 0);
|
||||
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.AlbedoSpecTextureId, FramebufferAttachType.ColorChannel2, FramebufferAttachTextureType.Texture2D, 0);
|
||||
|
||||
// Finally we attach the depth buffer
|
||||
gBuffer.DepthRenderbufferId = Rlgl.LoadTextureDepth(screenWidth, screenHeight, true);
|
||||
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.DepthRenderbufferId, FramebufferAttachType.Depth, FramebufferAttachTextureType.Renderbuffer, 0);
|
||||
|
||||
// Make sure our framebufferId is complete
|
||||
// NOTE: rlFramebufferComplete() automatically unbinds the framebufferId, so we don't have to rlDisableFramebuffer() here
|
||||
if (Rlgl.FramebufferComplete(gBuffer.FramebufferId) == 0)
|
||||
{
|
||||
TraceLog(TraceLogLevel.Warning, "Framebuffer is not complete");
|
||||
}
|
||||
|
||||
// Now we initialize the sampler2D uniform's in the deferred shader
|
||||
// We do this by setting the uniform's values to the texture units that
|
||||
// we later bind our g-buffer textures to
|
||||
Rlgl.EnableShader(deferredShader.Id);
|
||||
int texUnitPosition = TexUnitPosition;
|
||||
int texUnitNormal = TexUnitNormal;
|
||||
int texUnitAlbedoSpec = TexUnitAlbedoSpec;
|
||||
Raylib.SetShaderValue(deferredShader, GetShaderLocation(deferredShader, "gPosition"), texUnitPosition, ShaderUniformDataType.Sampler2D);
|
||||
Raylib.SetShaderValue(deferredShader, GetShaderLocation(deferredShader, "gNormal"), texUnitNormal, ShaderUniformDataType.Sampler2D);
|
||||
Raylib.SetShaderValue(deferredShader, GetShaderLocation(deferredShader, "gAlbedoSpec"), texUnitAlbedoSpec, ShaderUniformDataType.Sampler2D);
|
||||
Rlgl.DisableShader();
|
||||
|
||||
// Assign out lighting shader to model
|
||||
model.Materials[0].Shader = gbufferShader;
|
||||
cube.Materials[0].Shader = gbufferShader;
|
||||
|
||||
// Create lights
|
||||
lights = new Light[MaxLights];
|
||||
lights[0] = Rlights.CreateLight(0, LightType.Point, new Vector3(-2, 1, -2), Vector3.Zero, Color.Yellow, deferredShader);
|
||||
lights[1] = Rlights.CreateLight(1, LightType.Point, new Vector3(2, 1, 2), Vector3.Zero, Color.Red, deferredShader);
|
||||
lights[2] = Rlights.CreateLight(2, LightType.Point, new Vector3(-2, 1, 2), Vector3.Zero, Color.Green, deferredShader);
|
||||
lights[3] = Rlights.CreateLight(3, LightType.Point, new Vector3(2, 1, -2), Vector3.Zero, Color.Blue, deferredShader);
|
||||
|
||||
var rand = new Random();
|
||||
cubePositions = new Vector3[MaxCubes];
|
||||
cubeRotations = new float[MaxCubes];
|
||||
|
||||
for (var i = 0; i < MaxCubes; i++)
|
||||
{
|
||||
cubePositions[i] = new Vector3(
|
||||
(float)(rand.Next() % 10) - 5,
|
||||
(float)(rand.Next() % 5),
|
||||
(float)(rand.Next() % 10) - 5
|
||||
);
|
||||
|
||||
cubeRotations[i] = (float)(rand.Next() % 360);
|
||||
}
|
||||
|
||||
mode = DeferredMode.Shading;
|
||||
|
||||
Rlgl.EnableDepthTest();
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
// Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f })
|
||||
Raylib.SetShaderValue(
|
||||
deferredShader,
|
||||
deferredShader.Locs[(int)ShaderLocationIndex.VectorView],
|
||||
camera.Position,
|
||||
ShaderUniformDataType.Vec3
|
||||
);
|
||||
|
||||
// Check key inputs to enable/disable lights
|
||||
if (IsKeyPressed(KeyboardKey.Y)) { lights[0].Enabled = !lights[0].Enabled; }
|
||||
if (IsKeyPressed(KeyboardKey.R)) { lights[1].Enabled = !lights[1].Enabled; }
|
||||
if (IsKeyPressed(KeyboardKey.G)) { lights[2].Enabled = !lights[2].Enabled; }
|
||||
if (IsKeyPressed(KeyboardKey.B)) { lights[3].Enabled = !lights[3].Enabled; }
|
||||
|
||||
// Check key inputs to switch between G-buffer textures
|
||||
if (IsKeyPressed(KeyboardKey.One)) mode = DeferredMode.Position;
|
||||
if (IsKeyPressed(KeyboardKey.Two)) mode = DeferredMode.Normal;
|
||||
if (IsKeyPressed(KeyboardKey.Three)) mode = DeferredMode.Albedo;
|
||||
if (IsKeyPressed(KeyboardKey.Four)) mode = DeferredMode.Shading;
|
||||
|
||||
// Update light values (actually, only enable/disable them)
|
||||
for (var i = 0; i < MaxLights; i++) Rlights.UpdateLightValues(deferredShader, lights[i]);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
// Draw to the geometry buffer by first activating it
|
||||
Rlgl.EnableFramebuffer(gBuffer.FramebufferId);
|
||||
Rlgl.ClearColor(0, 0, 0, 0);
|
||||
Rlgl.ClearScreenBuffers(); // Clear color and depth buffer
|
||||
Rlgl.DisableColorBlend();
|
||||
|
||||
BeginMode3D(camera);
|
||||
// NOTE: We have to use rlEnableShader here. `BeginShaderMode` or thus `rlSetShader`
|
||||
// will not work, as they won't immediately load the shader program
|
||||
Rlgl.EnableShader(gbufferShader.Id);
|
||||
// When drawing a model here, make sure that the material's shaders are set to the gbuffer shader!
|
||||
DrawModel(model, Vector3.Zero, 1.0f, Color.White);
|
||||
DrawModel(cube, new Vector3(0.0f, 1.0f, 0.0f), 1.0f, Color.White);
|
||||
|
||||
for (var i = 0; i < MaxCubes; i++)
|
||||
{
|
||||
var position = cubePositions[i];
|
||||
DrawModelEx(cube, position, new Vector3(1, 1, 1), cubeRotations[i], new Vector3(CubeScale, CubeScale, CubeScale), Color.White);
|
||||
}
|
||||
Rlgl.DisableShader();
|
||||
EndMode3D();
|
||||
|
||||
Rlgl.EnableColorBlend();
|
||||
|
||||
// Go back to the default framebufferId (0) and draw our deferred shading
|
||||
Rlgl.DisableFramebuffer();
|
||||
Rlgl.ClearScreenBuffers(); // Clear color & depth buffer
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case DeferredMode.Shading:
|
||||
{
|
||||
BeginMode3D(camera);
|
||||
Rlgl.DisableColorBlend();
|
||||
Rlgl.EnableShader(deferredShader.Id);
|
||||
// Bind our g-buffer textures
|
||||
// We are binding them to locations that we earlier set in sampler2D uniforms `gPosition`, `gNormal`,
|
||||
// and `gAlbedoSpec`
|
||||
Rlgl.ActiveTextureSlot(TexUnitPosition);
|
||||
Rlgl.EnableTexture(gBuffer.PositionTextureId);
|
||||
Rlgl.ActiveTextureSlot(TexUnitNormal);
|
||||
Rlgl.EnableTexture(gBuffer.NormalTextureId);
|
||||
Rlgl.ActiveTextureSlot(TexUnitAlbedoSpec);
|
||||
Rlgl.EnableTexture(gBuffer.AlbedoSpecTextureId);
|
||||
|
||||
// Finally, we draw a fullscreen quad to our default framebufferId
|
||||
// This will now be shaded using our deferred shader
|
||||
Rlgl.LoadDrawQuad();
|
||||
Rlgl.DisableShader();
|
||||
Rlgl.EnableColorBlend();
|
||||
EndMode3D();
|
||||
|
||||
// As a last step, we now copy over the depth buffer from our g-buffer to the default framebufferId
|
||||
Rlgl.BindFramebuffer(RlReadFramebuffer, gBuffer.FramebufferId);
|
||||
Rlgl.BindFramebuffer(RlDrawFramebuffer, 0);
|
||||
Rlgl.BlitFramebuffer(0, 0, screenWidth, screenHeight, 0, 0, screenWidth, screenHeight, GlDepthBufferBit);
|
||||
Rlgl.DisableFramebuffer();
|
||||
|
||||
// Since our shader is now done and disabled, we can draw spheres
|
||||
// that represent light positions in default forward rendering
|
||||
BeginMode3D(camera);
|
||||
Rlgl.EnableShader(Rlgl.GetShaderIdDefault());
|
||||
for (var i = 0; i < MaxLights; i++)
|
||||
{
|
||||
if (lights[i].Enabled) DrawSphereEx(lights[i].Position, 0.2f, 8, 8, lights[i].Color);
|
||||
else DrawSphereWires(lights[i].Position, 0.2f, 8, 8, ColorAlpha(lights[i].Color, 0.3f));
|
||||
}
|
||||
Rlgl.DisableShader();
|
||||
EndMode3D();
|
||||
|
||||
DrawText("FINAL RESULT", 10, screenHeight - 30, 20, Color.DarkGreen);
|
||||
}
|
||||
break;
|
||||
case DeferredMode.Position:
|
||||
{
|
||||
DrawTextureRec(
|
||||
new Texture2D { Id = gBuffer.PositionTextureId, Width = screenWidth, Height = screenHeight },
|
||||
new Rectangle(0, 0, screenWidth, -screenHeight),
|
||||
Vector2.Zero,
|
||||
Color.RayWhite
|
||||
);
|
||||
|
||||
DrawText("POSITION TEXTURE", 10, screenHeight - 30, 20, Color.DarkGreen);
|
||||
}
|
||||
break;
|
||||
case DeferredMode.Normal:
|
||||
{
|
||||
DrawTextureRec(
|
||||
new Texture2D { Id = gBuffer.NormalTextureId, Width = screenWidth, Height = screenHeight },
|
||||
new Rectangle(0, 0, screenWidth, -screenHeight),
|
||||
Vector2.Zero,
|
||||
Color.RayWhite
|
||||
);
|
||||
|
||||
DrawText("NORMAL TEXTURE", 10, screenHeight - 30, 20, Color.DarkGreen);
|
||||
}
|
||||
break;
|
||||
case DeferredMode.Albedo:
|
||||
{
|
||||
DrawTextureRec(
|
||||
new Texture2D { Id = gBuffer.AlbedoSpecTextureId, Width = screenWidth, Height = screenHeight },
|
||||
new Rectangle(0, 0, screenWidth, -screenHeight),
|
||||
Vector2.Zero,
|
||||
Color.RayWhite
|
||||
);
|
||||
|
||||
DrawText("ALBEDO TEXTURE", 10, screenHeight - 30, 20, Color.DarkGreen);
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
DrawText("Toggle lights keys: [Y][R][G][B]", 10, 40, 20, Color.DarkGray);
|
||||
DrawText("Switch G-buffer textures: [1][2][3][4]", 10, 70, 20, Color.DarkGray);
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
// Unload the models
|
||||
UnloadModel(model);
|
||||
UnloadModel(cube);
|
||||
|
||||
// Unload shaders
|
||||
UnloadShader(deferredShader);
|
||||
UnloadShader(gbufferShader);
|
||||
|
||||
// Unload geometry buffer and all attached textures
|
||||
Rlgl.UnloadFramebuffer(gBuffer.FramebufferId);
|
||||
Rlgl.UnloadTexture(gBuffer.PositionTextureId);
|
||||
Rlgl.UnloadTexture(gBuffer.NormalTextureId);
|
||||
Rlgl.UnloadTexture(gBuffer.AlbedoSpecTextureId);
|
||||
Rlgl.UnloadTexture(gBuffer.DepthRenderbufferId);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - deferred rendering");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new DeferredRendering();
|
||||
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;
|
||||
}
|
||||
}
|
||||
226
Examples/Shaders/DepthRendering.cs
Normal file
226
Examples/Shaders/DepthRendering.cs
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - depth rendering
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Luís Almeida (@luis605) 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 Luís Almeida (@luis605)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public class DepthRendering : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Shaders / Depth Rendering";
|
||||
|
||||
public string Title => "raylib [shaders] example - depth rendering";
|
||||
|
||||
public bool CursorDisabled => true;
|
||||
|
||||
private Camera3D camera;
|
||||
private RenderTexture2D target;
|
||||
private Shader depthShader;
|
||||
private int depthLoc;
|
||||
private Model cube;
|
||||
private Model floor;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(4.0f, 1.0f, 5.0f);
|
||||
camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
camera.FovY = 45.0f;
|
||||
camera.Projection = CameraProjection.Perspective;
|
||||
|
||||
// Load render texture with a depth texture attached
|
||||
target = LoadRenderTextureDepthTex(screenWidth, screenHeight);
|
||||
|
||||
// Load depth shader and get depth texture shader location
|
||||
depthShader = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/depth_render.fs");
|
||||
depthLoc = GetShaderLocation(depthShader, "depthTexture");
|
||||
var flipTextureLoc = GetShaderLocation(depthShader, "flipY");
|
||||
Raylib.SetShaderValue(depthShader, flipTextureLoc, 1, ShaderUniformDataType.Int); // Flip Y texture
|
||||
|
||||
// Load scene models
|
||||
cube = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f));
|
||||
floor = LoadModelFromMesh(GenMeshPlane(20.0f, 20.0f, 1, 1));
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Free);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginTextureMode(target);
|
||||
ClearBackground(Color.White);
|
||||
|
||||
BeginMode3D(camera);
|
||||
DrawModel(cube, new Vector3(0.0f, 0.0f, 0.0f), 3.0f, Color.Yellow);
|
||||
DrawModel(floor, new Vector3(10.0f, 0.0f, 2.0f), 2.0f, Color.Red);
|
||||
EndMode3D();
|
||||
EndTextureMode();
|
||||
|
||||
// Draw into screen (main framebuffer)
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginShaderMode(depthShader);
|
||||
SetShaderValueTexture(depthShader, depthLoc, target.Depth);
|
||||
DrawTexture(target.Depth, 0, 0, Color.White);
|
||||
EndShaderMode();
|
||||
|
||||
DrawRectangle(10, 10, 320, 93, Fade(Color.SkyBlue, 0.5f));
|
||||
DrawRectangleLines(10, 10, 320, 93, Color.Blue);
|
||||
|
||||
DrawText("Camera Controls:", 20, 20, 10, Color.Black);
|
||||
DrawText("- WASD to move", 40, 40, 10, Color.DarkGray);
|
||||
DrawText("- Mouse Wheel Pressed to Pan", 40, 60, 10, Color.DarkGray);
|
||||
DrawText("- Z to zoom to (0, 0, 0)", 40, 80, 10, Color.DarkGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadModel(cube); // Unload model
|
||||
UnloadModel(floor); // Unload model
|
||||
UnloadRenderTextureDepthTex(target);
|
||||
UnloadShader(depthShader); // Unload shader
|
||||
}
|
||||
|
||||
// Load custom render texture, create a writable depth texture buffer
|
||||
private static unsafe RenderTexture2D LoadRenderTextureDepthTex(int width, int height)
|
||||
{
|
||||
RenderTexture2D target = new();
|
||||
|
||||
// Load an empty framebuffer
|
||||
target.Id = Rlgl.LoadFramebuffer();
|
||||
|
||||
if (target.Id > 0)
|
||||
{
|
||||
Rlgl.EnableFramebuffer(target.Id);
|
||||
|
||||
// Create color texture (default to RGBA)
|
||||
target.Texture.Id = Rlgl.LoadTexture(
|
||||
null,
|
||||
width,
|
||||
height,
|
||||
PixelFormat.UncompressedR8G8B8A8,
|
||||
1
|
||||
);
|
||||
target.Texture.Width = width;
|
||||
target.Texture.Height = height;
|
||||
target.Texture.Format = PixelFormat.UncompressedR8G8B8A8;
|
||||
target.Texture.Mipmaps = 1;
|
||||
|
||||
// Create depth texture buffer (instead of raylib default renderbuffer)
|
||||
target.Depth.Id = Rlgl.LoadTextureDepth(width, height, false);
|
||||
target.Depth.Width = width;
|
||||
target.Depth.Height = height;
|
||||
target.Depth.Format = PixelFormat.CompressedPvrtRgba; // DEPTH_COMPONENT_24BIT: Not defined in raylib
|
||||
target.Depth.Mipmaps = 1;
|
||||
|
||||
// Attach color texture and depth texture to FBO
|
||||
Rlgl.FramebufferAttach(
|
||||
target.Id,
|
||||
target.Texture.Id,
|
||||
FramebufferAttachType.ColorChannel0,
|
||||
FramebufferAttachTextureType.Texture2D,
|
||||
0
|
||||
);
|
||||
Rlgl.FramebufferAttach(
|
||||
target.Id,
|
||||
target.Depth.Id,
|
||||
FramebufferAttachType.Depth,
|
||||
FramebufferAttachTextureType.Texture2D,
|
||||
0
|
||||
);
|
||||
|
||||
// Check if fbo is complete with attachments (valid)
|
||||
if (Rlgl.FramebufferComplete(target.Id) != 0)
|
||||
{
|
||||
TraceLog(TraceLogLevel.Info, $"FBO: [ID {target.Id}] Framebuffer object created successfully");
|
||||
}
|
||||
|
||||
Rlgl.DisableFramebuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLog(TraceLogLevel.Warning, "FBO: Framebuffer object can not be created");
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
// Unload render texture from GPU memory (VRAM)
|
||||
private static void UnloadRenderTextureDepthTex(RenderTexture2D target)
|
||||
{
|
||||
if (target.Id > 0)
|
||||
{
|
||||
// Color texture attached to FBO is deleted
|
||||
Rlgl.UnloadTexture(target.Texture.Id);
|
||||
Rlgl.UnloadTexture(target.Depth.Id);
|
||||
|
||||
// NOTE: Depth texture is automatically
|
||||
// queried and deleted before deleting framebuffer
|
||||
Rlgl.UnloadFramebuffer(target.Id);
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - depth rendering");
|
||||
|
||||
DisableCursor(); // Limit cursor to relative movement inside the window
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new DepthRendering();
|
||||
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;
|
||||
}
|
||||
}
|
||||
433
Examples/Shaders/GameOfLife.cs
Normal file
433
Examples/Shaders/GameOfLife.cs
Normal file
|
|
@ -0,0 +1,433 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - game of life
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
|
||||
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Jordi Santonja (@JordSant) 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 Jordi Santonja (@JordSant)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public partial class GameOfLife : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Shaders / Game of Life";
|
||||
|
||||
public string Title => "raylib [shaders] example - game of life";
|
||||
|
||||
// Interaction mode
|
||||
private enum InteractionMode
|
||||
{
|
||||
Run = 0,
|
||||
Pause,
|
||||
Draw,
|
||||
}
|
||||
|
||||
// Struct to store example preset patterns
|
||||
private struct PresetPattern
|
||||
{
|
||||
public string Name;
|
||||
public Vector2 Position;
|
||||
|
||||
public PresetPattern(string name, Vector2 position)
|
||||
{
|
||||
Name = name;
|
||||
Position = position;
|
||||
}
|
||||
}
|
||||
|
||||
private const int menuWidth = 100;
|
||||
private const int windowWidth = screenWidth - menuWidth;
|
||||
private const int windowHeight = screenHeight;
|
||||
|
||||
private const int worldWidth = 2048;
|
||||
private const int worldHeight = 2048;
|
||||
|
||||
private const int randomTiles = 8; // Random preset: divide the world to compute random points in each tile
|
||||
|
||||
private static readonly PresetPattern[] presetPatterns =
|
||||
{
|
||||
new("Glider", new Vector2(0.5f, 0.5f)), new("R-pentomino", new Vector2(0.5f, 0.5f)), new("Acorn", new Vector2(0.5f, 0.5f)),
|
||||
new("Spaceships", new Vector2(0.1f, 0.5f)), new("Still lifes", new Vector2(0.5f, 0.5f)), new("Oscillators", new Vector2(0.5f, 0.5f)),
|
||||
new("Puffer train", new Vector2(0.1f, 0.5f)), new("Glider Gun", new Vector2(0.2f, 0.2f)), new("Breeder", new Vector2(0.1f, 0.5f)),
|
||||
new("Random", new Vector2(0.5f, 0.5f))
|
||||
};
|
||||
|
||||
private static readonly int numberOfPresets = presetPatterns.Length;
|
||||
|
||||
private Rectangle worldRectSource;
|
||||
private Rectangle worldRectDest;
|
||||
private Rectangle textureOnScreen;
|
||||
|
||||
private int zoom;
|
||||
private float offsetX;
|
||||
private float offsetY;
|
||||
private int framesPerStep;
|
||||
private int frame;
|
||||
|
||||
private int preset;
|
||||
private InteractionMode mode;
|
||||
private bool buttonZoomIn;
|
||||
private bool buttonZomOut;
|
||||
private bool buttonFaster;
|
||||
private bool buttonSlower;
|
||||
|
||||
private Shader shdrGameOfLife;
|
||||
private int resolutionLoc;
|
||||
|
||||
private RenderTexture2D world1;
|
||||
private RenderTexture2D world2;
|
||||
private RenderTexture2D currentWorld;
|
||||
private RenderTexture2D previousWorld;
|
||||
|
||||
// Image to be used in DRAW mode, to be changed with mouse input
|
||||
private Image imageToDraw;
|
||||
private bool imageToDrawValid;
|
||||
|
||||
// Static locals in the original loop, promoted to fields
|
||||
private Vector2 previousMousePosition;
|
||||
private int firstColor;
|
||||
|
||||
private void FreeImageToDraw()
|
||||
{
|
||||
if (imageToDrawValid)
|
||||
{
|
||||
UnloadImage(imageToDraw);
|
||||
imageToDrawValid = false;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
worldRectSource = new Rectangle(0, 0, worldWidth, -worldHeight);
|
||||
worldRectDest = new Rectangle(0, 0, worldWidth, worldHeight);
|
||||
textureOnScreen = new Rectangle(0, 0, windowWidth, windowHeight);
|
||||
|
||||
zoom = 1;
|
||||
offsetX = (worldWidth - windowWidth) / 2.0f; // Centered on window
|
||||
offsetY = (worldHeight - windowHeight) / 2.0f; // Centered on window
|
||||
framesPerStep = 1;
|
||||
frame = 0;
|
||||
|
||||
preset = -1; // No button pressed for preset
|
||||
mode = InteractionMode.Run; // Starting mode: running
|
||||
buttonZoomIn = false; // Button states: false not pressed
|
||||
buttonZomOut = false;
|
||||
buttonFaster = false;
|
||||
buttonSlower = false;
|
||||
|
||||
previousMousePosition = new Vector2(0.0f, 0.0f);
|
||||
firstColor = -1;
|
||||
imageToDrawValid = false;
|
||||
|
||||
// Load shader
|
||||
shdrGameOfLife = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/game_of_life.fs");
|
||||
|
||||
// Set shader uniform size of the world
|
||||
resolutionLoc = GetShaderLocation(shdrGameOfLife, "resolution");
|
||||
var resolution = new[] { (float)worldWidth, (float)worldHeight };
|
||||
Raylib.SetShaderValue(shdrGameOfLife, resolutionLoc, resolution, ShaderUniformDataType.Vec2);
|
||||
|
||||
// Define two textures: the current world and the previous world
|
||||
world1 = LoadRenderTexture(worldWidth, worldHeight);
|
||||
world2 = LoadRenderTexture(worldWidth, worldHeight);
|
||||
BeginTextureMode(world2);
|
||||
ClearBackground(Color.RayWhite);
|
||||
EndTextureMode();
|
||||
|
||||
var startPattern = LoadImage("resources/game_of_life/r_pentomino.png");
|
||||
UpdateTextureRec(
|
||||
world2.Texture,
|
||||
new Rectangle(worldWidth / 2.0f, worldHeight / 2.0f, startPattern.Width, startPattern.Height),
|
||||
startPattern.Data
|
||||
);
|
||||
UnloadImage(startPattern);
|
||||
|
||||
// References to the two textures, to be swapped
|
||||
currentWorld = world2;
|
||||
previousWorld = world1;
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
frame++;
|
||||
|
||||
// Change zoom: both by buttons or by mouse wheel
|
||||
var mouseWheelMove = GetMouseWheelMove();
|
||||
if (buttonZoomIn || (buttonZomOut && (zoom > 1)) || (mouseWheelMove != 0.0f))
|
||||
{
|
||||
FreeImageToDraw(); // Zoom change: free the image to draw to be recreated again
|
||||
|
||||
var centerX = offsetX + (windowWidth / 2.0f) / zoom;
|
||||
var centerY = offsetY + (windowHeight / 2.0f) / zoom;
|
||||
if (buttonZoomIn || (mouseWheelMove > 0.0f)) zoom *= 2;
|
||||
if ((buttonZomOut || (mouseWheelMove < 0.0f)) && (zoom > 1)) zoom /= 2;
|
||||
offsetX = centerX - (windowWidth / 2.0f) / zoom;
|
||||
offsetY = centerY - (windowHeight / 2.0f) / zoom;
|
||||
}
|
||||
|
||||
// Change speed: number of frames per step
|
||||
if (buttonFaster && framesPerStep > 1) framesPerStep--;
|
||||
if (buttonSlower) framesPerStep++;
|
||||
|
||||
// Mouse management
|
||||
if ((mode == InteractionMode.Run) || (mode == InteractionMode.Pause))
|
||||
{
|
||||
FreeImageToDraw(); // Free the image to draw: no longer needed in these modes
|
||||
|
||||
// Pan with mouse left button
|
||||
var mousePosition = GetMousePosition();
|
||||
if (IsMouseButtonDown(MouseButton.Left) && (mousePosition.X < windowWidth))
|
||||
{
|
||||
offsetX -= (mousePosition.X - previousMousePosition.X) / zoom;
|
||||
offsetY -= (mousePosition.Y - previousMousePosition.Y) / zoom;
|
||||
}
|
||||
previousMousePosition = mousePosition;
|
||||
}
|
||||
else // MODE_DRAW
|
||||
{
|
||||
var offsetDecimalX = offsetX - MathF.Floor(offsetX);
|
||||
var offsetDecimalY = offsetY - MathF.Floor(offsetY);
|
||||
var sizeInWorldX = (int)(MathF.Ceiling((windowWidth + offsetDecimalX * zoom) / zoom));
|
||||
var sizeInWorldY = (int)(MathF.Ceiling((windowHeight + offsetDecimalY * zoom) / zoom));
|
||||
if (offsetX + sizeInWorldX >= worldWidth) sizeInWorldX = worldWidth - (int)MathF.Floor(offsetX);
|
||||
if (offsetY + sizeInWorldY >= worldHeight) sizeInWorldY = worldHeight - (int)MathF.Floor(offsetY);
|
||||
|
||||
// Create image to draw if not created yet
|
||||
if (!imageToDrawValid)
|
||||
{
|
||||
var worldOnScreen = LoadRenderTexture(sizeInWorldX, sizeInWorldY);
|
||||
BeginTextureMode(worldOnScreen);
|
||||
DrawTexturePro(
|
||||
currentWorld.Texture,
|
||||
new Rectangle(MathF.Floor(offsetX), MathF.Floor(offsetY), sizeInWorldX, -sizeInWorldY),
|
||||
new Rectangle(0, 0, sizeInWorldX, sizeInWorldY),
|
||||
new Vector2(0, 0), 0.0f, Color.White
|
||||
);
|
||||
EndTextureMode();
|
||||
imageToDraw = LoadImageFromTexture(worldOnScreen.Texture);
|
||||
imageToDrawValid = true;
|
||||
|
||||
UnloadRenderTexture(worldOnScreen);
|
||||
}
|
||||
|
||||
var mousePosition = GetMousePosition();
|
||||
if (IsMouseButtonDown(MouseButton.Left) && (mousePosition.X < windowWidth))
|
||||
{
|
||||
var mouseX = (int)(mousePosition.X + offsetDecimalX * zoom) / zoom;
|
||||
var mouseY = (int)(mousePosition.Y + offsetDecimalY * zoom) / zoom;
|
||||
if (mouseX >= sizeInWorldX) mouseX = sizeInWorldX - 1;
|
||||
if (mouseY >= sizeInWorldY) mouseY = sizeInWorldY - 1;
|
||||
if (firstColor == -1) firstColor = (GetImageColor(imageToDraw, mouseX, mouseY).R < 5) ? 0 : 1;
|
||||
var prevColor = (GetImageColor(imageToDraw, mouseX, mouseY).R < 5) ? 0 : 1;
|
||||
|
||||
ImageDrawPixel(ref imageToDraw, mouseX, mouseY, (firstColor != 0) ? Color.Black : Color.RayWhite);
|
||||
|
||||
if (prevColor != firstColor)
|
||||
{
|
||||
UpdateTextureRec(
|
||||
currentWorld.Texture,
|
||||
new Rectangle(MathF.Floor(offsetX), MathF.Floor(offsetY), sizeInWorldX, sizeInWorldY),
|
||||
imageToDraw.Data
|
||||
);
|
||||
}
|
||||
}
|
||||
else firstColor = -1;
|
||||
}
|
||||
|
||||
// Load selected preset
|
||||
if (preset >= 0)
|
||||
{
|
||||
Image pattern;
|
||||
if (preset < numberOfPresets - 1) // Preset with pattern image to load
|
||||
{
|
||||
pattern = preset switch
|
||||
{
|
||||
0 => LoadImage("resources/game_of_life/glider.png"),
|
||||
1 => LoadImage("resources/game_of_life/r_pentomino.png"),
|
||||
2 => LoadImage("resources/game_of_life/acorn.png"),
|
||||
3 => LoadImage("resources/game_of_life/spaceships.png"),
|
||||
4 => LoadImage("resources/game_of_life/still_lifes.png"),
|
||||
5 => LoadImage("resources/game_of_life/oscillators.png"),
|
||||
6 => LoadImage("resources/game_of_life/puffer_train.png"),
|
||||
7 => LoadImage("resources/game_of_life/glider_gun.png"),
|
||||
8 => LoadImage("resources/game_of_life/breeder.png"),
|
||||
_ => default,
|
||||
};
|
||||
BeginTextureMode(currentWorld);
|
||||
ClearBackground(Color.RayWhite);
|
||||
EndTextureMode();
|
||||
|
||||
UpdateTextureRec(
|
||||
currentWorld.Texture,
|
||||
new Rectangle(
|
||||
worldWidth * presetPatterns[preset].Position.X - pattern.Width / 2.0f,
|
||||
worldHeight * presetPatterns[preset].Position.Y - pattern.Height / 2.0f,
|
||||
pattern.Width, pattern.Height
|
||||
),
|
||||
pattern.Data
|
||||
);
|
||||
}
|
||||
else // Last preset: Random values
|
||||
{
|
||||
pattern = GenImageColor(worldWidth / randomTiles, worldHeight / randomTiles, Color.RayWhite);
|
||||
for (var i = 0; i < randomTiles; i++)
|
||||
{
|
||||
for (var j = 0; j < randomTiles; j++)
|
||||
{
|
||||
ImageClearBackground(ref pattern, Color.RayWhite);
|
||||
for (var x = 0; x < pattern.Width; x++)
|
||||
{
|
||||
for (var y = 0; y < pattern.Height; y++)
|
||||
{
|
||||
if (GetRandomValue(0, 100) < 15) ImageDrawPixel(ref pattern, x, y, Color.Black);
|
||||
}
|
||||
}
|
||||
UpdateTextureRec(
|
||||
currentWorld.Texture,
|
||||
new Rectangle(pattern.Width * i, pattern.Height * j, pattern.Width, pattern.Height),
|
||||
pattern.Data
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UnloadImage(pattern);
|
||||
|
||||
mode = InteractionMode.Pause;
|
||||
offsetX = worldWidth * presetPatterns[preset].Position.X - (float)windowWidth / zoom / 2.0f;
|
||||
offsetY = worldHeight * presetPatterns[preset].Position.Y - (float)windowHeight / zoom / 2.0f;
|
||||
}
|
||||
|
||||
// Check window draw inside world limits
|
||||
if (offsetX < 0) offsetX = 0;
|
||||
if (offsetY < 0) offsetY = 0;
|
||||
if (offsetX > worldWidth - (float)windowWidth / zoom) offsetX = worldWidth - (float)windowWidth / zoom;
|
||||
if (offsetY > worldHeight - (float)windowHeight / zoom) offsetY = worldHeight - (float)windowHeight / zoom;
|
||||
|
||||
// Rectangles for drawing texture portion to screen
|
||||
var textureSourceToScreen = new Rectangle(offsetX, offsetY, (float)windowWidth / zoom, (float)windowHeight / zoom);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw to texture
|
||||
//----------------------------------------------------------------------------------
|
||||
if ((mode == InteractionMode.Run) && ((frame % framesPerStep) == 0))
|
||||
{
|
||||
// Swap worlds
|
||||
var tempWorld = currentWorld;
|
||||
currentWorld = previousWorld;
|
||||
previousWorld = tempWorld;
|
||||
|
||||
// Draw to texture
|
||||
BeginTextureMode(currentWorld);
|
||||
BeginShaderMode(shdrGameOfLife);
|
||||
DrawTexturePro(previousWorld.Texture, worldRectSource, worldRectDest, new Vector2(0, 0), 0.0f, Color.RayWhite);
|
||||
EndShaderMode();
|
||||
EndTextureMode();
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw to screen
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
DrawTexturePro(currentWorld.Texture, textureSourceToScreen, textureOnScreen, new Vector2(0, 0), 0.0f, Color.White);
|
||||
|
||||
DrawLine(windowWidth, 0, windowWidth, screenHeight, new Color(218, 218, 218, 255));
|
||||
DrawRectangle(windowWidth, 0, screenWidth - windowWidth, screenHeight, new Color(232, 232, 232, 255));
|
||||
|
||||
DrawText("Conway's", 704, 4, 20, Color.DarkBlue);
|
||||
DrawText(" game of", 704, 19, 20, Color.DarkBlue);
|
||||
DrawText(" life", 708, 34, 20, Color.DarkBlue);
|
||||
DrawText("in raylib", 757, 42, 6, Color.Black);
|
||||
|
||||
DrawText("Presets", 710, 58, 8, Color.Gray);
|
||||
preset = -1;
|
||||
|
||||
// Draw GUI controls
|
||||
//------------------------------------------------------------------------------
|
||||
// NOTE: raygui is not bound in raylib-cs; controls are kept for reference. The
|
||||
// simulation still runs automatically and mouse wheel zoom / left-drag pan work.
|
||||
/*for (int i = 0; i < numberOfPresets; i++)
|
||||
if (GuiButton(new Rectangle( 710.0f, 70.0f + 18*i, 80.0f, 16.0f ), presetPatterns[i].Name)) preset = i;
|
||||
|
||||
GuiToggleGroup(new Rectangle( 710, 258, 80, 16 ), "Run\nPause\nDraw", ref mode);*/
|
||||
|
||||
DrawText($"Zoom: {zoom}x", 710, 316, 8, Color.Gray);
|
||||
/*buttonZoomIn = GuiButton(new Rectangle( 710, 328, 80, 16 ), "Zoom in");
|
||||
buttonZomOut = GuiButton(new Rectangle( 710, 346, 80, 16 ), "Zoom out");*/
|
||||
|
||||
DrawText($"Speed: {framesPerStep} frame{((framesPerStep > 1) ? "s" : "")}", 710, 370, 8, Color.Gray);
|
||||
/*buttonFaster = GuiButton(new Rectangle( 710, 382, 80, 16 ), "Faster");
|
||||
buttonSlower = GuiButton(new Rectangle( 710, 400, 80, 16 ), "Slower");*/
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
DrawFPS(712, 426);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadShader(shdrGameOfLife);
|
||||
UnloadRenderTexture(world1);
|
||||
UnloadRenderTexture(world2);
|
||||
|
||||
FreeImageToDraw();
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - game of life");
|
||||
|
||||
SetTargetFPS(60); // Set at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new GameOfLife();
|
||||
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;
|
||||
}
|
||||
}
|
||||
213
Examples/Shaders/LightmapRendering.cs
Normal file
213
Examples/Shaders/LightmapRendering.cs
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - lightmap rendering
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
|
||||
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
|
||||
*
|
||||
* NOTE: Shaders used in this example are #version 330 (OpenGL 3.3)
|
||||
*
|
||||
* Example originally created with raylib 4.5, last time updated with raylib 4.5
|
||||
*
|
||||
* Example contributed by Jussi Viitala (@nullstare) 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) 2019-2025 Jussi Viitala (@nullstare) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Rlgl;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public partial class LightmapRendering : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MapSize = 16;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Shaders / Lightmap Rendering";
|
||||
|
||||
public string Title => "raylib [shaders] example - lightmap rendering";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
private Camera3D camera;
|
||||
private Mesh mesh;
|
||||
private Shader shader;
|
||||
private Texture2D texture;
|
||||
private Texture2D light;
|
||||
private RenderTexture2D lightmap;
|
||||
private Material material;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(4.0f, 6.0f, 8.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
mesh = GenMeshPlane((float)MapSize, (float)MapSize, 1, 1);
|
||||
|
||||
// GenMeshPlane doesn't generate texcoords2 so we will upload them separately
|
||||
mesh.AllocTexCoords2();
|
||||
|
||||
// X // Y
|
||||
mesh.TexCoords2[0] = 0.0f; mesh.TexCoords2[1] = 0.0f;
|
||||
mesh.TexCoords2[2] = 1.0f; mesh.TexCoords2[3] = 0.0f;
|
||||
mesh.TexCoords2[4] = 0.0f; mesh.TexCoords2[5] = 1.0f;
|
||||
mesh.TexCoords2[6] = 1.0f; mesh.TexCoords2[7] = 1.0f;
|
||||
|
||||
// Load a new texcoords2 attributes buffer
|
||||
mesh.VboId[(int)ShaderLocationIndex.VertexTexcoord02] =
|
||||
LoadVertexBuffer(mesh.TexCoords2, mesh.VertexCount * 2 * sizeof(float), false);
|
||||
EnableVertexArray(mesh.VaoId);
|
||||
|
||||
// Index 5 is for texcoords2
|
||||
SetVertexAttribute(5, 2, Rlgl.FLOAT, false, 0, 0);
|
||||
EnableVertexAttribute(5);
|
||||
DisableVertexArray();
|
||||
|
||||
// Load lightmap shader
|
||||
shader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/lightmap.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/lightmap.fs"
|
||||
);
|
||||
|
||||
texture = LoadTexture("resources/cubicmap_atlas.png");
|
||||
light = LoadTexture("resources/spark_flame.png");
|
||||
|
||||
GenTextureMipmaps(ref texture);
|
||||
SetTextureFilter(texture, TextureFilter.Trilinear);
|
||||
|
||||
lightmap = LoadRenderTexture(MapSize, MapSize);
|
||||
|
||||
material = LoadMaterialDefault();
|
||||
material.Shader = shader;
|
||||
material.Maps[(int)MaterialMapIndex.Albedo].Texture = texture;
|
||||
material.Maps[(int)MaterialMapIndex.Metalness].Texture = lightmap.Texture;
|
||||
|
||||
// Drawing to lightmap
|
||||
BeginTextureMode(lightmap);
|
||||
ClearBackground(Color.Black);
|
||||
|
||||
BeginBlendMode(BlendMode.Additive);
|
||||
DrawTexturePro(
|
||||
light,
|
||||
new Rectangle(0, 0, (float)light.Width, (float)light.Height),
|
||||
new Rectangle(0, 0, 2.0f * MapSize, 2.0f * MapSize),
|
||||
new Vector2((float)MapSize, (float)MapSize),
|
||||
0.0f,
|
||||
Color.Red
|
||||
);
|
||||
DrawTexturePro(
|
||||
light,
|
||||
new Rectangle(0, 0, (float)light.Width, (float)light.Height),
|
||||
new Rectangle((float)MapSize * 0.8f, (float)MapSize / 2.0f, 2.0f * MapSize, 2.0f * MapSize),
|
||||
new Vector2((float)MapSize, (float)MapSize),
|
||||
0.0f,
|
||||
Color.Blue
|
||||
);
|
||||
DrawTexturePro(
|
||||
light,
|
||||
new Rectangle(0, 0, (float)light.Width, (float)light.Height),
|
||||
new Rectangle((float)MapSize * 0.8f, (float)MapSize * 0.8f, (float)MapSize, (float)MapSize),
|
||||
new Vector2((float)MapSize / 2.0f, (float)MapSize / 2.0f),
|
||||
0.0f,
|
||||
Color.Green
|
||||
);
|
||||
BeginBlendMode(BlendMode.Alpha);
|
||||
EndTextureMode();
|
||||
|
||||
// NOTE: To enable trilinear filtering we need mipmaps available for texture
|
||||
GenTextureMipmaps(ref lightmap.Texture);
|
||||
SetTextureFilter(lightmap.Texture, TextureFilter.Trilinear);
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
DrawMesh(mesh, material, Matrix4x4.Identity);
|
||||
EndMode3D();
|
||||
|
||||
DrawTexturePro(
|
||||
lightmap.Texture,
|
||||
new Rectangle(0, 0, -MapSize, -MapSize),
|
||||
new Rectangle((float)GetRenderWidth() - MapSize * 8 - 10, 10, (float)MapSize * 8, (float)MapSize * 8),
|
||||
new Vector2(0.0f, 0.0f),
|
||||
0.0f,
|
||||
Color.White
|
||||
);
|
||||
|
||||
DrawText($"LIGHTMAP: {MapSize}x{MapSize} pixels", GetRenderWidth() - 130, 20 + MapSize * 8, 10, Color.Green);
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadMesh(mesh); // Unload the mesh
|
||||
UnloadShader(shader); // Unload shader
|
||||
UnloadTexture(texture); // Unload texture
|
||||
UnloadTexture(light); // Unload texture
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint); // Enable Multi Sampling Anti Aliasing 4x (if available)
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - lightmap rendering");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new LightmapRendering();
|
||||
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;
|
||||
}
|
||||
}
|
||||
265
Examples/Shaders/MandelbrotSet.cs
Normal file
265
Examples/Shaders/MandelbrotSet.cs
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - mandelbrot set
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
|
||||
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
|
||||
*
|
||||
* NOTE: Shaders used in this example are #version 330 (OpenGL 3.3)
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Jordi Santonja (@JordSant)
|
||||
* Based on previous work by Josh Colclough (@joshcol9232)
|
||||
*
|
||||
* 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 Jordi Santonja (@JordSant)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public partial class MandelbrotSet : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Shaders / Mandelbrot Set";
|
||||
|
||||
public string Title => "raylib [shaders] example - mandelbrot set";
|
||||
|
||||
// A few good interesting places
|
||||
private static readonly float[][] pointsOfInterest = new[]
|
||||
{
|
||||
new[] { -1.76826775f, -0.00422996283f, 28435.9238f },
|
||||
new[] { 0.322004497f, -0.0357099883f, 56499.7266f },
|
||||
new[] { -0.748880744f, -0.0562955774f, 9237.59082f },
|
||||
new[] { -1.78385007f, -0.0156200649f, 14599.5283f },
|
||||
new[] { -0.0985441282f, -0.924688697f, 26259.8535f },
|
||||
new[] { 0.317785531f, -0.0322612226f, 29297.9258f },
|
||||
};
|
||||
|
||||
private const float zoomSpeed = 1.01f;
|
||||
private const float offsetSpeedMul = 2.0f;
|
||||
|
||||
private const float startingZoom = 0.6f;
|
||||
private static readonly float[] startingOffset = { -0.5f, 0.0f };
|
||||
|
||||
private Shader shader;
|
||||
private RenderTexture2D target;
|
||||
private float[] offset;
|
||||
private float zoom;
|
||||
private int maxIterations;
|
||||
private float maxIterationsMultiplier;
|
||||
private int zoomLoc;
|
||||
private int offsetLoc;
|
||||
private int maxIterationsLoc;
|
||||
private bool showControls;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Load mandelbrot set shader
|
||||
// NOTE: Defining null (NULL) for vertex shader forces usage of internal default vertex shader
|
||||
shader = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/mandelbrot_set.fs");
|
||||
|
||||
// Create a RenderTexture2D to be used for render to texture
|
||||
target = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());
|
||||
|
||||
// Offset and zoom to draw the mandelbrot set at. (centered on screen and default size)
|
||||
offset = new[] { startingOffset[0], startingOffset[1] };
|
||||
zoom = startingZoom;
|
||||
|
||||
// Depending on the zoom the maximum number of iterations must be adapted to get more detail as we zoom in
|
||||
// The solution is not perfect, so a control has been added to increase/decrease the number of iterations with UP/DOWN keys
|
||||
#if BROWSER
|
||||
maxIterations = 43;
|
||||
maxIterationsMultiplier = 22.0f;
|
||||
#else
|
||||
maxIterations = 333;
|
||||
maxIterationsMultiplier = 166.5f;
|
||||
#endif
|
||||
|
||||
// Get variable (uniform) locations on the shader to connect with the program
|
||||
// NOTE: If uniform variable could not be found in the shader, function returns -1
|
||||
zoomLoc = GetShaderLocation(shader, "zoom");
|
||||
offsetLoc = GetShaderLocation(shader, "offset");
|
||||
maxIterationsLoc = GetShaderLocation(shader, "maxIterations");
|
||||
|
||||
// Upload the shader uniform values!
|
||||
Raylib.SetShaderValue(shader, zoomLoc, zoom, ShaderUniformDataType.Float);
|
||||
Raylib.SetShaderValue(shader, offsetLoc, offset, ShaderUniformDataType.Vec2);
|
||||
Raylib.SetShaderValue(shader, maxIterationsLoc, maxIterations, ShaderUniformDataType.Int);
|
||||
|
||||
showControls = true; // Show controls
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
var updateShader = false;
|
||||
|
||||
// Press [1 - 6] to reset c to a point of interest
|
||||
if (IsKeyPressed(KeyboardKey.One) ||
|
||||
IsKeyPressed(KeyboardKey.Two) ||
|
||||
IsKeyPressed(KeyboardKey.Three) ||
|
||||
IsKeyPressed(KeyboardKey.Four) ||
|
||||
IsKeyPressed(KeyboardKey.Five) ||
|
||||
IsKeyPressed(KeyboardKey.Six))
|
||||
{
|
||||
var interestIndex = 0;
|
||||
if (IsKeyPressed(KeyboardKey.One)) interestIndex = 0;
|
||||
else if (IsKeyPressed(KeyboardKey.Two)) interestIndex = 1;
|
||||
else if (IsKeyPressed(KeyboardKey.Three)) interestIndex = 2;
|
||||
else if (IsKeyPressed(KeyboardKey.Four)) interestIndex = 3;
|
||||
else if (IsKeyPressed(KeyboardKey.Five)) interestIndex = 4;
|
||||
else if (IsKeyPressed(KeyboardKey.Six)) interestIndex = 5;
|
||||
|
||||
offset[0] = pointsOfInterest[interestIndex][0];
|
||||
offset[1] = pointsOfInterest[interestIndex][1];
|
||||
zoom = pointsOfInterest[interestIndex][2];
|
||||
updateShader = true;
|
||||
}
|
||||
|
||||
// If "R" is pressed, reset zoom and offset
|
||||
if (IsKeyPressed(KeyboardKey.R))
|
||||
{
|
||||
offset[0] = startingOffset[0];
|
||||
offset[1] = startingOffset[1];
|
||||
zoom = startingZoom;
|
||||
updateShader = true;
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.F1)) showControls = !showControls; // Toggle whether or not to show controls
|
||||
|
||||
// Change number of max iterations with UP and DOWN keys
|
||||
// WARNING: Increasing the number of max iterations greatly impacts performance
|
||||
if (IsKeyPressed(KeyboardKey.Up))
|
||||
{
|
||||
maxIterationsMultiplier *= 1.4f;
|
||||
updateShader = true;
|
||||
}
|
||||
else if (IsKeyPressed(KeyboardKey.Down))
|
||||
{
|
||||
maxIterationsMultiplier /= 1.4f;
|
||||
updateShader = true;
|
||||
}
|
||||
|
||||
// If either left or right button is pressed, zoom in/out
|
||||
if (IsMouseButtonDown(MouseButton.Left) || IsMouseButtonDown(MouseButton.Right))
|
||||
{
|
||||
// Change zoom. If Mouse left -> zoom in. Mouse right -> zoom out
|
||||
zoom *= IsMouseButtonDown(MouseButton.Left) ? zoomSpeed : (1.0f / zoomSpeed);
|
||||
|
||||
var mousePos = GetMousePosition();
|
||||
Vector2 offsetVelocity;
|
||||
// Find the velocity at which to change the camera. Take the distance of the mouse
|
||||
// From the center of the screen as the direction, and adjust magnitude based on the current zoom
|
||||
offsetVelocity.X = (mousePos.X / (float)screenWidth - 0.5f) * offsetSpeedMul / zoom;
|
||||
offsetVelocity.Y = (mousePos.Y / (float)screenHeight - 0.5f) * offsetSpeedMul / zoom;
|
||||
|
||||
// Apply move velocity to camera
|
||||
offset[0] += GetFrameTime() * offsetVelocity.X;
|
||||
offset[1] += GetFrameTime() * offsetVelocity.Y;
|
||||
|
||||
updateShader = true;
|
||||
}
|
||||
|
||||
// In case a parameter has been changed, update the shader values
|
||||
if (updateShader)
|
||||
{
|
||||
// As we zoom in, increase the number of max iterations to get more detail
|
||||
// Aproximate formula, but it works-ish
|
||||
maxIterations = (int)(MathF.Sqrt(2.0f * MathF.Sqrt(MathF.Abs(1.0f - MathF.Sqrt(37.5f * zoom)))) * maxIterationsMultiplier);
|
||||
|
||||
// Update the shader uniform values!
|
||||
Raylib.SetShaderValue(shader, zoomLoc, zoom, ShaderUniformDataType.Float);
|
||||
Raylib.SetShaderValue(shader, offsetLoc, offset, ShaderUniformDataType.Vec2);
|
||||
Raylib.SetShaderValue(shader, maxIterationsLoc, maxIterations, ShaderUniformDataType.Int);
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
// Using a render texture to draw Mandelbrot set
|
||||
BeginTextureMode(target); // Enable drawing to texture
|
||||
ClearBackground(Color.Black); // Clear the render texture
|
||||
|
||||
// Draw a rectangle in shader mode to be used as shader canvas
|
||||
// NOTE: Rectangle uses font white character texture coordinates,
|
||||
// So shader can not be applied here directly because input vertexTexCoord
|
||||
// Do not represent full screen coordinates (space where want to apply shader)
|
||||
DrawRectangle(0, 0, GetScreenWidth(), GetScreenHeight(), Color.Black);
|
||||
EndTextureMode();
|
||||
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.Black); // Clear screen background
|
||||
|
||||
// Draw the saved texture and rendered mandelbrot set with shader
|
||||
// NOTE: We do not invert texture on Y, already considered inside shader
|
||||
BeginShaderMode(shader);
|
||||
// WARNING: If FLAG_WINDOW_HIGHDPI is enabled, HighDPI monitor scaling should be considered
|
||||
// When rendering the RenderTexture2D to fit in the HighDPI scaled Window
|
||||
DrawTextureEx(target.Texture, new Vector2(0.0f, 0.0f), 0.0f, 1.0f, Color.White);
|
||||
EndShaderMode();
|
||||
|
||||
if (showControls)
|
||||
{
|
||||
DrawText("Press Mouse buttons right/left to zoom in/out and move", 10, 15, 10, Color.RayWhite);
|
||||
DrawText("Press F1 to toggle these controls", 10, 30, 10, Color.RayWhite);
|
||||
DrawText("Press [1 - 6] to change point of interest", 10, 45, 10, Color.RayWhite);
|
||||
DrawText("Press UP | DOWN to change number of iterations", 10, 60, 10, Color.RayWhite);
|
||||
DrawText("Press R to recenter the camera", 10, 75, 10, Color.RayWhite);
|
||||
}
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadShader(shader); // Unload shader
|
||||
UnloadRenderTexture(target); // Unload render texture
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - mandelbrot set");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new MandelbrotSet();
|
||||
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;
|
||||
}
|
||||
}
|
||||
233
Examples/Shaders/NormalmapRendering.cs
Normal file
233
Examples/Shaders/NormalmapRendering.cs
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - normalmap rendering
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
|
||||
* OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Jeremy Montgomery (@Sir_Irk) 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 Jeremy Montgomery (@Sir_Irk) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public partial class NormalmapRendering : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Shaders / Normalmap Rendering";
|
||||
|
||||
public string Title => "raylib [shaders] example - normalmap rendering";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
private Camera3D camera;
|
||||
private Shader shader;
|
||||
private Model plane;
|
||||
private Vector3 lightPosition;
|
||||
private int lightPosLoc;
|
||||
private float specularExponent;
|
||||
private int specularExponentLoc;
|
||||
private int useNormalMap;
|
||||
private int useNormalMapLoc;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
camera = new();
|
||||
camera.Position = new Vector3(0.0f, 2.0f, -4.0f);
|
||||
camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
camera.FovY = 45.0f;
|
||||
camera.Projection = CameraProjection.Perspective;
|
||||
|
||||
// Load basic normal map lighting shader
|
||||
shader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/normalmap.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/normalmap.fs"
|
||||
);
|
||||
|
||||
// Get some required shader locations
|
||||
shader.Locs[(int)ShaderLocationIndex.MapNormal] = GetShaderLocation(shader, "normalMap");
|
||||
shader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(shader, "viewPos");
|
||||
|
||||
// NOTE: "matModel" location name is automatically assigned on shader loading,
|
||||
// no need to get the location again if using that uniform name
|
||||
// shader.Locs[(int)ShaderLocationIndex.MatrixModel] = GetShaderLocation(shader, "matModel");
|
||||
|
||||
// This example uses just 1 point light
|
||||
lightPosition = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
lightPosLoc = GetShaderLocation(shader, "lightPos");
|
||||
|
||||
// Load a plane model that has proper normals and tangents
|
||||
plane = LoadModel("resources/models/plane.glb");
|
||||
|
||||
// Set the plane model's shader and texture maps
|
||||
plane.Materials[0].Shader = shader;
|
||||
plane.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture = LoadTexture("resources/tiles_diffuse.png");
|
||||
plane.Materials[0].Maps[(int)MaterialMapIndex.Normal].Texture = LoadTexture("resources/tiles_normal.png");
|
||||
|
||||
// Generate Mipmaps and use TRILINEAR filtering to help with texture aliasing
|
||||
GenTextureMipmaps(ref plane.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture);
|
||||
GenTextureMipmaps(ref plane.Materials[0].Maps[(int)MaterialMapIndex.Normal].Texture);
|
||||
|
||||
SetTextureFilter(plane.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture, TextureFilter.Trilinear);
|
||||
SetTextureFilter(plane.Materials[0].Maps[(int)MaterialMapIndex.Normal].Texture, TextureFilter.Trilinear);
|
||||
|
||||
// Specular exponent AKA shininess of the material
|
||||
specularExponent = 8.0f;
|
||||
specularExponentLoc = GetShaderLocation(shader, "specularExponent");
|
||||
|
||||
// Allow toggling the normal map on and off for comparison purposes
|
||||
useNormalMap = 1;
|
||||
useNormalMapLoc = GetShaderLocation(shader, "useNormalMap");
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Move the light around on the X and Z axis using WASD keys
|
||||
Vector3 direction = new(0.0f, 0.0f, 0.0f);
|
||||
if (IsKeyDown(KeyboardKey.W))
|
||||
{
|
||||
direction = Vector3Add(direction, new Vector3(0.0f, 0.0f, 1.0f));
|
||||
}
|
||||
if (IsKeyDown(KeyboardKey.S))
|
||||
{
|
||||
direction = Vector3Add(direction, new Vector3(0.0f, 0.0f, -1.0f));
|
||||
}
|
||||
if (IsKeyDown(KeyboardKey.D))
|
||||
{
|
||||
direction = Vector3Add(direction, new Vector3(-1.0f, 0.0f, 0.0f));
|
||||
}
|
||||
if (IsKeyDown(KeyboardKey.A))
|
||||
{
|
||||
direction = Vector3Add(direction, new Vector3(1.0f, 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
direction = Vector3Normalize(direction);
|
||||
lightPosition = Vector3Add(lightPosition, Vector3Scale(direction, GetFrameTime() * 3.0f));
|
||||
|
||||
// Increase/Decrease the specular exponent(shininess)
|
||||
if (IsKeyDown(KeyboardKey.Up))
|
||||
{
|
||||
specularExponent = Clamp(specularExponent + 40.0f * GetFrameTime(), 2.0f, 128.0f);
|
||||
}
|
||||
if (IsKeyDown(KeyboardKey.Down))
|
||||
{
|
||||
specularExponent = Clamp(specularExponent - 40.0f * GetFrameTime(), 2.0f, 128.0f);
|
||||
}
|
||||
|
||||
// Toggle normal map on and off
|
||||
if (IsKeyPressed(KeyboardKey.N))
|
||||
{
|
||||
useNormalMap = (useNormalMap != 0) ? 0 : 1;
|
||||
}
|
||||
|
||||
// Spin plane model at a constant rate
|
||||
plane.Transform = MatrixRotateY((float)GetTime() * 0.5f);
|
||||
|
||||
// Update shader values
|
||||
Raylib.SetShaderValue(shader, lightPosLoc, lightPosition, ShaderUniformDataType.Vec3);
|
||||
|
||||
Raylib.SetShaderValue(
|
||||
shader,
|
||||
shader.Locs[(int)ShaderLocationIndex.VectorView],
|
||||
camera.Position,
|
||||
ShaderUniformDataType.Vec3
|
||||
);
|
||||
|
||||
Raylib.SetShaderValue(shader, specularExponentLoc, specularExponent, ShaderUniformDataType.Float);
|
||||
|
||||
Raylib.SetShaderValue(shader, useNormalMapLoc, useNormalMap, ShaderUniformDataType.Int);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
BeginShaderMode(shader);
|
||||
|
||||
DrawModel(plane, Vector3.Zero, 2.0f, Color.White);
|
||||
|
||||
EndShaderMode();
|
||||
|
||||
// Draw sphere to show light position
|
||||
DrawSphereWires(lightPosition, 0.2f, 8, 8, Color.Orange);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
Color textColor = (useNormalMap != 0) ? Color.DarkGreen : Color.Red;
|
||||
string toggleStr = (useNormalMap != 0) ? "On" : "Off";
|
||||
DrawText($"Use key [N] to toggle normal map: {toggleStr}", 10, 10, 10, textColor);
|
||||
|
||||
int yOffset = 24;
|
||||
DrawText("Use keys [W][A][S][D] to move the light", 10, 10 + yOffset * 1, 10, Color.Black);
|
||||
DrawText("Use keys [Up][Down] to change specular exponent", 10, 10 + yOffset * 2, 10, Color.Black);
|
||||
DrawText($"Specular Exponent: {specularExponent:F2}", 10, 10 + yOffset * 3, 10, Color.Blue);
|
||||
|
||||
DrawFPS(screenWidth - 90, 10);
|
||||
|
||||
EndDrawing();
|
||||
//--------------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadShader(shader);
|
||||
UnloadModel(plane);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - normalmap rendering");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new NormalmapRendering();
|
||||
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;
|
||||
}
|
||||
}
|
||||
242
Examples/Shaders/RlglCompute.cs
Normal file
242
Examples/Shaders/RlglCompute.cs
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - rlgl compute
|
||||
*
|
||||
* WARNING: This example requires raylib compiled with OpenGL 4.3 version for
|
||||
* compute shaders support, shaders used in this example are #version 430
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 4.0, last time updated with raylib 4.0
|
||||
*
|
||||
* Example contributed by Teddy Astie (@tsnake41) 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) 2021-2025 Teddy Astie (@tsnake41)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
[ExcludeFromBrowser("compute shaders are not available on WebGL")]
|
||||
public partial class RlglCompute : IExample
|
||||
{
|
||||
// IMPORTANT: This must match gol*.glsl GOL_WIDTH constant
|
||||
// This must be a multiple of 16 (check golLogic compute dispatch)
|
||||
private const int GolWidth = 768;
|
||||
|
||||
// Maximum amount of queued draw commands (squares draw from mouse down events)
|
||||
private const int MaxBufferedTransferts = 48;
|
||||
|
||||
private const int screenWidth = GolWidth;
|
||||
private const int screenHeight = GolWidth;
|
||||
|
||||
public string Name => "Shaders / Rlgl Compute";
|
||||
|
||||
public string Title => "raylib [shaders] example - rlgl compute";
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// Game Of Life Update Command
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct GolUpdateCmd
|
||||
{
|
||||
public uint X; // x coordinate of the gol command
|
||||
public uint Y; // y coordinate of the gol command
|
||||
public uint W; // width of the filled zone
|
||||
public uint Enabled; // whether to enable or disable zone
|
||||
}
|
||||
|
||||
// Inline fixed-size array of GolUpdateCmd (MAX_BUFFERED_TRANSFERTS entries)
|
||||
[InlineArray(MaxBufferedTransferts)]
|
||||
private struct GolUpdateCmdBuffer
|
||||
{
|
||||
private GolUpdateCmd _element0;
|
||||
}
|
||||
|
||||
// Game Of Life Update Commands SSBO
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct GolUpdateSSBO
|
||||
{
|
||||
public uint Count;
|
||||
public GolUpdateCmdBuffer Commands;
|
||||
}
|
||||
|
||||
private Vector2 resolution;
|
||||
private uint brushSize;
|
||||
private uint golLogicShader;
|
||||
private uint golLogicProgram;
|
||||
private uint golTransfertShader;
|
||||
private uint golTransfertProgram;
|
||||
private Shader golRenderShader;
|
||||
private int resUniformLoc;
|
||||
private uint ssboA;
|
||||
private uint ssboB;
|
||||
private uint ssboTransfert;
|
||||
private GolUpdateSSBO transfertBuffer;
|
||||
private Texture2D whiteTex;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
resolution = new Vector2(screenWidth, screenHeight);
|
||||
brushSize = 8;
|
||||
|
||||
// Game of Life logic compute shader
|
||||
var golLogicCode = LoadFileText("resources/shaders/glsl430/gol.glsl");
|
||||
var golLogicBytes = Encoding.UTF8.GetBytes(golLogicCode + "\0");
|
||||
fixed (byte* p = golLogicBytes)
|
||||
{
|
||||
golLogicShader = Rlgl.LoadShader((sbyte*)p, (int)ShaderType.Compute);
|
||||
}
|
||||
golLogicProgram = Rlgl.LoadShaderProgramCompute(golLogicShader);
|
||||
|
||||
// Game of Life logic render shader
|
||||
golRenderShader = LoadShader(null, "resources/shaders/glsl430/gol_render.glsl");
|
||||
resUniformLoc = GetShaderLocation(golRenderShader, "resolution");
|
||||
|
||||
// Game of Life transfert shader (CPU<->GPU download and upload)
|
||||
var golTransfertCode = LoadFileText("resources/shaders/glsl430/gol_transfert.glsl");
|
||||
var golTransfertBytes = Encoding.UTF8.GetBytes(golTransfertCode + "\0");
|
||||
fixed (byte* p = golTransfertBytes)
|
||||
{
|
||||
golTransfertShader = Rlgl.LoadShader((sbyte*)p, (int)ShaderType.Compute);
|
||||
}
|
||||
golTransfertProgram = Rlgl.LoadShaderProgramCompute(golTransfertShader);
|
||||
|
||||
// Load shader storage buffer object (SSBO), id returned
|
||||
ssboA = Rlgl.LoadShaderBuffer((uint)(GolWidth * GolWidth * sizeof(uint)), null, Rlgl.DYNAMIC_COPY);
|
||||
ssboB = Rlgl.LoadShaderBuffer((uint)(GolWidth * GolWidth * sizeof(uint)), null, Rlgl.DYNAMIC_COPY);
|
||||
ssboTransfert = Rlgl.LoadShaderBuffer((uint)sizeof(GolUpdateSSBO), null, Rlgl.DYNAMIC_COPY);
|
||||
|
||||
transfertBuffer = new();
|
||||
|
||||
// Create a white texture of the size of the window to update
|
||||
// each pixel of the window using the fragment shader: golRenderShader
|
||||
var whiteImage = GenImageColor(GolWidth, GolWidth, Color.White);
|
||||
whiteTex = LoadTextureFromImage(whiteImage);
|
||||
UnloadImage(whiteImage);
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
brushSize += (uint)(int)GetMouseWheelMove();
|
||||
|
||||
if ((IsMouseButtonDown(MouseButton.Left) || IsMouseButtonDown(MouseButton.Right))
|
||||
&& (transfertBuffer.Count < MaxBufferedTransferts))
|
||||
{
|
||||
// Buffer a new command
|
||||
transfertBuffer.Commands[(int)transfertBuffer.Count].X = (uint)GetMouseX() - brushSize / 2;
|
||||
transfertBuffer.Commands[(int)transfertBuffer.Count].Y = (uint)GetMouseY() - brushSize / 2;
|
||||
transfertBuffer.Commands[(int)transfertBuffer.Count].W = brushSize;
|
||||
transfertBuffer.Commands[(int)transfertBuffer.Count].Enabled = IsMouseButtonDown(MouseButton.Left) ? 1u : 0u;
|
||||
transfertBuffer.Count++;
|
||||
}
|
||||
else if (transfertBuffer.Count > 0) // Process transfert buffer
|
||||
{
|
||||
// Send SSBO buffer to GPU
|
||||
fixed (GolUpdateSSBO* ptr = &transfertBuffer)
|
||||
{
|
||||
Rlgl.UpdateShaderBuffer(ssboTransfert, ptr, (uint)sizeof(GolUpdateSSBO), 0);
|
||||
}
|
||||
|
||||
// Process SSBO commands on GPU
|
||||
Rlgl.EnableShader(golTransfertProgram);
|
||||
Rlgl.BindShaderBuffer(ssboA, 1);
|
||||
Rlgl.BindShaderBuffer(ssboTransfert, 3);
|
||||
Rlgl.ComputeShaderDispatch(transfertBuffer.Count, 1, 1); // Each GPU unit will process a command!
|
||||
Rlgl.DisableShader();
|
||||
|
||||
transfertBuffer.Count = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Process game of life logic
|
||||
Rlgl.EnableShader(golLogicProgram);
|
||||
Rlgl.BindShaderBuffer(ssboA, 1);
|
||||
Rlgl.BindShaderBuffer(ssboB, 2);
|
||||
Rlgl.ComputeShaderDispatch(GolWidth / 16, GolWidth / 16, 1);
|
||||
Rlgl.DisableShader();
|
||||
|
||||
// ssboA <-> ssboB
|
||||
var temp = ssboA;
|
||||
ssboA = ssboB;
|
||||
ssboB = temp;
|
||||
}
|
||||
|
||||
Rlgl.BindShaderBuffer(ssboA, 1);
|
||||
Raylib.SetShaderValue(golRenderShader, resUniformLoc, resolution, ShaderUniformDataType.Vec2);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.Blank);
|
||||
|
||||
BeginShaderMode(golRenderShader);
|
||||
DrawTexture(whiteTex, 0, 0, Color.White);
|
||||
EndShaderMode();
|
||||
|
||||
DrawRectangleLines(GetMouseX() - (int)(brushSize / 2), GetMouseY() - (int)(brushSize / 2), (int)brushSize, (int)brushSize, Color.Red);
|
||||
|
||||
DrawText("Use Mouse wheel to increase/decrease brush size", 10, 10, 20, Color.White);
|
||||
DrawFPS(GetScreenWidth() - 100, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
// Unload shader buffers objects
|
||||
Rlgl.UnloadShaderBuffer(ssboA);
|
||||
Rlgl.UnloadShaderBuffer(ssboB);
|
||||
Rlgl.UnloadShaderBuffer(ssboTransfert);
|
||||
|
||||
// Unload compute shader
|
||||
Rlgl.UnloadShader(golLogicShader);
|
||||
Rlgl.UnloadShader(golTransfertShader);
|
||||
Rlgl.UnloadShaderProgram(golTransfertProgram);
|
||||
Rlgl.UnloadShaderProgram(golLogicProgram);
|
||||
|
||||
UnloadTexture(whiteTex); // Unload white texture
|
||||
UnloadShader(golRenderShader); // Unload rendering fragment shader
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - rlgl compute");
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new RlglCompute();
|
||||
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;
|
||||
}
|
||||
}
|
||||
243
Examples/Shaders/RoundedRectangle.cs
Normal file
243
Examples/Shaders/RoundedRectangle.cs
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - rounded rectangle
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.5
|
||||
*
|
||||
* Example contributed by Anstro Pleuton (@anstropleuton) 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 Anstro Pleuton (@anstropleuton)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public class RoundedRectangle : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Shaders / Rounded Rectangle";
|
||||
|
||||
public string Title => "raylib [shaders] example - rounded rectangle";
|
||||
|
||||
// Rounded rectangle data
|
||||
private struct RoundedRect
|
||||
{
|
||||
public Vector4 CornerRadius; // Individual corner radius (top-left, top-right, bottom-left, bottom-right)
|
||||
|
||||
// Shadow variables
|
||||
public float ShadowRadius;
|
||||
public Vector2 ShadowOffset;
|
||||
public float ShadowScale;
|
||||
|
||||
// Border variables
|
||||
public float BorderThickness; // Inner-border thickness
|
||||
|
||||
// Shader locations
|
||||
public int RectangleLoc;
|
||||
public int RadiusLoc;
|
||||
public int ColorLoc;
|
||||
public int ShadowRadiusLoc;
|
||||
public int ShadowOffsetLoc;
|
||||
public int ShadowScaleLoc;
|
||||
public int ShadowColorLoc;
|
||||
public int BorderThicknessLoc;
|
||||
public int BorderColorLoc;
|
||||
}
|
||||
|
||||
private Shader shader;
|
||||
private RoundedRect roundedRectangle;
|
||||
|
||||
private readonly Color rectangleColor = Color.Blue;
|
||||
private readonly Color shadowColor = Color.DarkBlue;
|
||||
private readonly Color borderColor = Color.SkyBlue;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Load the shader
|
||||
shader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/base.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/rounded_rectangle.fs"
|
||||
);
|
||||
|
||||
// Create a rounded rectangle
|
||||
roundedRectangle = CreateRoundedRectangle(
|
||||
new Vector4(5.0f, 10.0f, 15.0f, 20.0f), // Corner radius
|
||||
20.0f, // Shadow radius
|
||||
new Vector2(0.0f, -5.0f), // Shadow offset
|
||||
0.95f, // Shadow scale
|
||||
5.0f, // Border thickness
|
||||
shader // Shader
|
||||
);
|
||||
|
||||
// Update shader uniforms
|
||||
UpdateRoundedRectangle(roundedRectangle, shader);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw rectangle box with rounded corners using shader
|
||||
Rectangle rec = new(50, 70, 110, 60);
|
||||
DrawRectangleLines((int)rec.X - 20, (int)rec.Y - 20, (int)rec.Width + 40, (int)rec.Height + 40, Color.DarkGray);
|
||||
DrawText("Rounded rectangle", (int)rec.X - 20, (int)rec.Y - 35, 10, Color.DarkGray);
|
||||
|
||||
// Flip Y axis to match shader coordinate system
|
||||
rec.Y = screenHeight - rec.Y - rec.Height;
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.RectangleLoc, new[] { rec.X, rec.Y, rec.Width, rec.Height }, ShaderUniformDataType.Vec4);
|
||||
|
||||
// Only rectangle color
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.ColorLoc, new[] { rectangleColor.R / 255.0f, rectangleColor.G / 255.0f, rectangleColor.B / 255.0f, rectangleColor.A / 255.0f }, ShaderUniformDataType.Vec4);
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.ShadowColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.BorderColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
|
||||
|
||||
BeginShaderMode(shader);
|
||||
DrawRectangle(0, 0, screenWidth, screenHeight, Color.White);
|
||||
EndShaderMode();
|
||||
|
||||
// Draw rectangle shadow using shader
|
||||
rec = new Rectangle(50, 200, 110, 60);
|
||||
DrawRectangleLines((int)rec.X - 20, (int)rec.Y - 20, (int)rec.Width + 40, (int)rec.Height + 40, Color.DarkGray);
|
||||
DrawText("Rounded rectangle shadow", (int)rec.X - 20, (int)rec.Y - 35, 10, Color.DarkGray);
|
||||
|
||||
rec.Y = screenHeight - rec.Y - rec.Height;
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.RectangleLoc, new[] { rec.X, rec.Y, rec.Width, rec.Height }, ShaderUniformDataType.Vec4);
|
||||
|
||||
// Only shadow color
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.ColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.ShadowColorLoc, new[] { shadowColor.R / 255.0f, shadowColor.G / 255.0f, shadowColor.B / 255.0f, shadowColor.A / 255.0f }, ShaderUniformDataType.Vec4);
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.BorderColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
|
||||
|
||||
BeginShaderMode(shader);
|
||||
DrawRectangle(0, 0, screenWidth, screenHeight, Color.White);
|
||||
EndShaderMode();
|
||||
|
||||
// Draw rectangle's border using shader
|
||||
rec = new Rectangle(50, 330, 110, 60);
|
||||
DrawRectangleLines((int)rec.X - 20, (int)rec.Y - 20, (int)rec.Width + 40, (int)rec.Height + 40, Color.DarkGray);
|
||||
DrawText("Rounded rectangle border", (int)rec.X - 20, (int)rec.Y - 35, 10, Color.DarkGray);
|
||||
|
||||
rec.Y = screenHeight - rec.Y - rec.Height;
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.RectangleLoc, new[] { rec.X, rec.Y, rec.Width, rec.Height }, ShaderUniformDataType.Vec4);
|
||||
|
||||
// Only border color
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.ColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.ShadowColorLoc, new[] { 0.0f, 0.0f, 0.0f, 0.0f }, ShaderUniformDataType.Vec4);
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.BorderColorLoc, new[] { borderColor.R / 255.0f, borderColor.G / 255.0f, borderColor.B / 255.0f, borderColor.A / 255.0f }, ShaderUniformDataType.Vec4);
|
||||
|
||||
BeginShaderMode(shader);
|
||||
DrawRectangle(0, 0, screenWidth, screenHeight, Color.White);
|
||||
EndShaderMode();
|
||||
|
||||
// Draw one more rectangle with all three colors
|
||||
rec = new Rectangle(240, 80, 500, 300);
|
||||
DrawRectangleLines((int)rec.X - 30, (int)rec.Y - 30, (int)rec.Width + 60, (int)rec.Height + 60, Color.DarkGray);
|
||||
DrawText("Rectangle with all three combined", (int)rec.X - 30, (int)rec.Y - 45, 10, Color.DarkGray);
|
||||
|
||||
rec.Y = screenHeight - rec.Y - rec.Height;
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.RectangleLoc, new[] { rec.X, rec.Y, rec.Width, rec.Height }, ShaderUniformDataType.Vec4);
|
||||
|
||||
// All three colors
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.ColorLoc, new[] { rectangleColor.R / 255.0f, rectangleColor.G / 255.0f, rectangleColor.B / 255.0f, rectangleColor.A / 255.0f }, ShaderUniformDataType.Vec4);
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.ShadowColorLoc, new[] { shadowColor.R / 255.0f, shadowColor.G / 255.0f, shadowColor.B / 255.0f, shadowColor.A / 255.0f }, ShaderUniformDataType.Vec4);
|
||||
Raylib.SetShaderValue(shader, roundedRectangle.BorderColorLoc, new[] { borderColor.R / 255.0f, borderColor.G / 255.0f, borderColor.B / 255.0f, borderColor.A / 255.0f }, ShaderUniformDataType.Vec4);
|
||||
|
||||
BeginShaderMode(shader);
|
||||
DrawRectangle(0, 0, screenWidth, screenHeight, Color.White);
|
||||
EndShaderMode();
|
||||
|
||||
DrawText("(c) Rounded rectangle SDF by Iñigo Quilez. MIT License.", screenWidth - 300, screenHeight - 20, 10, Color.Black);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadShader(shader); // Unload shader
|
||||
}
|
||||
|
||||
// Create a rounded rectangle and set uniform locations
|
||||
private static RoundedRect CreateRoundedRectangle(Vector4 cornerRadius, float shadowRadius, Vector2 shadowOffset, float shadowScale, float borderThickness, Shader shader)
|
||||
{
|
||||
RoundedRect rec;
|
||||
rec.CornerRadius = cornerRadius;
|
||||
rec.ShadowRadius = shadowRadius;
|
||||
rec.ShadowOffset = shadowOffset;
|
||||
rec.ShadowScale = shadowScale;
|
||||
rec.BorderThickness = borderThickness;
|
||||
|
||||
// Get shader uniform locations
|
||||
rec.RectangleLoc = GetShaderLocation(shader, "rectangle");
|
||||
rec.RadiusLoc = GetShaderLocation(shader, "radius");
|
||||
rec.ColorLoc = GetShaderLocation(shader, "color");
|
||||
rec.ShadowRadiusLoc = GetShaderLocation(shader, "shadowRadius");
|
||||
rec.ShadowOffsetLoc = GetShaderLocation(shader, "shadowOffset");
|
||||
rec.ShadowScaleLoc = GetShaderLocation(shader, "shadowScale");
|
||||
rec.ShadowColorLoc = GetShaderLocation(shader, "shadowColor");
|
||||
rec.BorderThicknessLoc = GetShaderLocation(shader, "borderThickness");
|
||||
rec.BorderColorLoc = GetShaderLocation(shader, "borderColor");
|
||||
|
||||
UpdateRoundedRectangle(rec, shader);
|
||||
|
||||
return rec;
|
||||
}
|
||||
|
||||
// Update rounded rectangle uniforms
|
||||
private static void UpdateRoundedRectangle(RoundedRect rec, Shader shader)
|
||||
{
|
||||
Raylib.SetShaderValue(shader, rec.RadiusLoc, new[] { rec.CornerRadius.X, rec.CornerRadius.Y, rec.CornerRadius.Z, rec.CornerRadius.W }, ShaderUniformDataType.Vec4);
|
||||
Raylib.SetShaderValue(shader, rec.ShadowRadiusLoc, rec.ShadowRadius, ShaderUniformDataType.Float);
|
||||
Raylib.SetShaderValue(shader, rec.ShadowOffsetLoc, new[] { rec.ShadowOffset.X, rec.ShadowOffset.Y }, ShaderUniformDataType.Vec2);
|
||||
Raylib.SetShaderValue(shader, rec.ShadowScaleLoc, rec.ShadowScale, ShaderUniformDataType.Float);
|
||||
Raylib.SetShaderValue(shader, rec.BorderThicknessLoc, rec.BorderThickness, ShaderUniformDataType.Float);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - rounded rectangle");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new RoundedRectangle();
|
||||
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;
|
||||
}
|
||||
}
|
||||
316
Examples/Shaders/ShadowmapRendering.cs
Normal file
316
Examples/Shaders/ShadowmapRendering.cs
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - shadowmap rendering
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 5.0, last time updated with raylib 5.0
|
||||
*
|
||||
* Example contributed by TheManTheMythTheGameDev (@TheManTheMythTheGameDev) 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) 2023-2025 TheManTheMythTheGameDev (@TheManTheMythTheGameDev)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
using static Raylib_cs.Rlgl;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public partial class ShadowmapRendering : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
private const int ShadowmapResolution = 1024;
|
||||
|
||||
public string Name => "Shaders / Shadowmap Rendering";
|
||||
|
||||
public string Title => "raylib [shaders] example - shadowmap rendering";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
private Camera3D camera;
|
||||
private Shader shadowShader;
|
||||
private Vector3 lightDir;
|
||||
private int lightDirLoc;
|
||||
private int lightVPLoc;
|
||||
private int shadowMapLoc;
|
||||
private Model cube;
|
||||
private Model robot;
|
||||
private unsafe ModelAnimation* anims;
|
||||
private int animCount;
|
||||
private RenderTexture2D shadowMap;
|
||||
private Camera3D lightCamera;
|
||||
private int frameCounter;
|
||||
private int textureActiveSlot;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Shadows are a HUGE topic, and this example shows an extremely simple implementation of the shadowmapping algorithm,
|
||||
// which is the industry standard for shadows. This algorithm can be extended in a ridiculous number of ways to improve
|
||||
// realism and also adapt it for different scenes. This is pretty much the simplest possible implementation
|
||||
|
||||
camera = new();
|
||||
camera.Position = new Vector3(10.0f, 10.0f, 10.0f);
|
||||
camera.Target = Vector3.Zero;
|
||||
camera.Projection = CameraProjection.Perspective;
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
camera.FovY = 45.0f;
|
||||
|
||||
shadowShader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/shadowmap.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/shadowmap.fs"
|
||||
);
|
||||
shadowShader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(shadowShader, "viewPos");
|
||||
|
||||
lightDir = Vector3Normalize(new Vector3(0.35f, -1.0f, -0.35f));
|
||||
var lightColor = Color.White;
|
||||
var lightColorNormalized = ColorNormalize(lightColor);
|
||||
lightDirLoc = GetShaderLocation(shadowShader, "lightDir");
|
||||
var lightColLoc = GetShaderLocation(shadowShader, "lightColor");
|
||||
Raylib.SetShaderValue(shadowShader, lightDirLoc, lightDir, ShaderUniformDataType.Vec3);
|
||||
Raylib.SetShaderValue(shadowShader, lightColLoc, lightColorNormalized, ShaderUniformDataType.Vec4);
|
||||
var ambientLoc = GetShaderLocation(shadowShader, "ambient");
|
||||
var ambient = new[] { 0.1f, 0.1f, 0.1f, 1.0f };
|
||||
Raylib.SetShaderValue(shadowShader, ambientLoc, ambient, ShaderUniformDataType.Vec4);
|
||||
lightVPLoc = GetShaderLocation(shadowShader, "lightVP");
|
||||
shadowMapLoc = GetShaderLocation(shadowShader, "shadowMap");
|
||||
var shadowMapResolution = ShadowmapResolution;
|
||||
Raylib.SetShaderValue(shadowShader, GetShaderLocation(shadowShader, "shadowMapResolution"), shadowMapResolution, ShaderUniformDataType.Int);
|
||||
|
||||
cube = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f));
|
||||
cube.Materials[0].Shader = shadowShader;
|
||||
robot = LoadModel("resources/models/robot.glb");
|
||||
for (var i = 0; i < robot.MaterialCount; i++)
|
||||
{
|
||||
robot.Materials[i].Shader = shadowShader;
|
||||
}
|
||||
|
||||
animCount = 0;
|
||||
anims = LoadModelAnimations("resources/models/robot.glb", ref animCount);
|
||||
|
||||
shadowMap = LoadShadowmapRenderTexture(ShadowmapResolution, ShadowmapResolution);
|
||||
|
||||
// For the shadowmapping algorithm, we will be rendering everything from the light's point of view
|
||||
lightCamera = new();
|
||||
lightCamera.Position = Vector3Scale(lightDir, -15.0f);
|
||||
lightCamera.Target = Vector3.Zero;
|
||||
lightCamera.Projection = CameraProjection.Orthographic; // Use an orthographic projection for directional lights
|
||||
lightCamera.Up = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
lightCamera.FovY = 20.0f;
|
||||
|
||||
frameCounter = 0;
|
||||
textureActiveSlot = 10; // Can be anything 0 to 15, but 0 will probably be taken up
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
var deltaTime = GetFrameTime();
|
||||
|
||||
var cameraPos = camera.Position;
|
||||
Raylib.SetShaderValue(shadowShader, shadowShader.Locs[(int)ShaderLocationIndex.VectorView], cameraPos, ShaderUniformDataType.Vec3);
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
frameCounter++;
|
||||
frameCounter %= anims[0].KeyFrameCount;
|
||||
UpdateModelAnimation(robot, anims[0], (float)frameCounter);
|
||||
|
||||
// Move light with arrow keys
|
||||
const float cameraSpeed = 0.05f;
|
||||
if (IsKeyDown(KeyboardKey.Left))
|
||||
{
|
||||
if (lightDir.X < 0.6f)
|
||||
{
|
||||
lightDir.X += cameraSpeed * 60.0f * deltaTime;
|
||||
}
|
||||
}
|
||||
if (IsKeyDown(KeyboardKey.Right))
|
||||
{
|
||||
if (lightDir.X > -0.6f)
|
||||
{
|
||||
lightDir.X -= cameraSpeed * 60.0f * deltaTime;
|
||||
}
|
||||
}
|
||||
if (IsKeyDown(KeyboardKey.Up))
|
||||
{
|
||||
if (lightDir.Z < 0.6f)
|
||||
{
|
||||
lightDir.Z += cameraSpeed * 60.0f * deltaTime;
|
||||
}
|
||||
}
|
||||
if (IsKeyDown(KeyboardKey.Down))
|
||||
{
|
||||
if (lightDir.Z > -0.6f)
|
||||
{
|
||||
lightDir.Z -= cameraSpeed * 60.0f * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
lightDir = Vector3Normalize(lightDir);
|
||||
lightCamera.Position = Vector3Scale(lightDir, -15.0f);
|
||||
Raylib.SetShaderValue(shadowShader, lightDirLoc, lightDir, ShaderUniformDataType.Vec3);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
// PASS 01: Render all objects into the shadowmap render texture
|
||||
// We record all the objects' depths (as rendered from the light source's point of view) in a buffer
|
||||
// Anything that is "visible" to the light is in light, anything that isn't is in shadow
|
||||
// We can later use the depth buffer when rendering everything from the player's point of view
|
||||
// to determine whether a given point is "visible" to the light
|
||||
Matrix4x4 lightView;
|
||||
Matrix4x4 lightProj;
|
||||
BeginTextureMode(shadowMap);
|
||||
ClearBackground(Color.White);
|
||||
|
||||
BeginMode3D(lightCamera);
|
||||
lightView = GetMatrixModelview();
|
||||
lightProj = GetMatrixProjection();
|
||||
DrawScene(cube, robot);
|
||||
EndMode3D();
|
||||
|
||||
EndTextureMode();
|
||||
var lightViewProj = MatrixMultiply(lightView, lightProj);
|
||||
|
||||
// PASS 02: Draw the scene into main framebuffer, using the generated shadowmap
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
SetShaderValueMatrix(shadowShader, lightVPLoc, lightViewProj);
|
||||
EnableShader(shadowShader.Id);
|
||||
|
||||
ActiveTextureSlot(textureActiveSlot);
|
||||
EnableTexture(shadowMap.Depth.Id);
|
||||
var slot = textureActiveSlot;
|
||||
SetUniform(shadowMapLoc, &slot, (int)ShaderUniformDataType.Int, 1);
|
||||
|
||||
BeginMode3D(camera);
|
||||
DrawScene(cube, robot); // Draw the same exact things as we drew in the shadowmap!
|
||||
EndMode3D();
|
||||
|
||||
DrawText("Use the arrow keys to rotate the light!", 10, 10, 30, Color.Red);
|
||||
DrawText("Shadows in raylib using the shadowmapping algorithm!", screenWidth - 280, screenHeight - 20, 10, Color.Gray);
|
||||
|
||||
EndDrawing();
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.F))
|
||||
{
|
||||
TakeScreenshot("shaders_shadowmap.png");
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public unsafe void Unload()
|
||||
{
|
||||
UnloadShader(shadowShader);
|
||||
UnloadModel(cube);
|
||||
UnloadModel(robot);
|
||||
UnloadModelAnimations(anims, animCount);
|
||||
UnloadShadowmapRenderTexture(shadowMap);
|
||||
}
|
||||
|
||||
// Load render texture for shadowmap projection
|
||||
// NOTE: Load framebuffer with only a texture depth attachment,
|
||||
// no color attachment required for shadowmap
|
||||
private static unsafe RenderTexture2D LoadShadowmapRenderTexture(int width, int height)
|
||||
{
|
||||
RenderTexture2D target = new();
|
||||
|
||||
target.Id = LoadFramebuffer(); // Load an empty framebuffer
|
||||
target.Texture.Width = width;
|
||||
target.Texture.Height = height;
|
||||
|
||||
if (target.Id > 0)
|
||||
{
|
||||
EnableFramebuffer(target.Id);
|
||||
|
||||
// Create depth texture
|
||||
// NOTE: No need a color texture attachment for the shadowmap
|
||||
target.Depth.Id = LoadTextureDepth(width, height, false);
|
||||
target.Depth.Width = width;
|
||||
target.Depth.Height = height;
|
||||
target.Depth.Format = (PixelFormat)19; // DEPTH_COMPONENT_24BIT?
|
||||
target.Depth.Mipmaps = 1;
|
||||
|
||||
// Attach depth texture to FBO
|
||||
FramebufferAttach(target.Id, target.Depth.Id, FramebufferAttachType.Depth, FramebufferAttachTextureType.Texture2D, 0);
|
||||
|
||||
// Check if fbo is complete with attachments (valid)
|
||||
if (FramebufferComplete(target.Id) != 0)
|
||||
{
|
||||
TraceLog(TraceLogLevel.Info, $"FBO: [ID {target.Id}] Framebuffer object created successfully");
|
||||
}
|
||||
|
||||
DisableFramebuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLog(TraceLogLevel.Warning, "FBO: Framebuffer object can not be created");
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
// Unload shadowmap render texture from GPU memory (VRAM)
|
||||
private static void UnloadShadowmapRenderTexture(RenderTexture2D target)
|
||||
{
|
||||
if (target.Id > 0)
|
||||
{
|
||||
// NOTE: Depth texture/renderbuffer is automatically
|
||||
// queried and deleted before deleting framebuffer
|
||||
UnloadFramebuffer(target.Id);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw full scene projecting shadows
|
||||
// NOTE: Required to be called several time to generate shadowmap
|
||||
private static void DrawScene(Model cube, Model robot)
|
||||
{
|
||||
DrawModelEx(cube, Vector3.Zero, new Vector3(0.0f, 1.0f, 0.0f), 0.0f, new Vector3(10.0f, 1.0f, 10.0f), Color.Blue);
|
||||
DrawModelEx(cube, new Vector3(1.5f, 1.0f, -1.5f), new Vector3(0.0f, 1.0f, 0.0f), 0.0f, Vector3.One, Color.White);
|
||||
DrawModelEx(robot, new Vector3(0.0f, 0.5f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f), 0.0f, new Vector3(1.0f, 1.0f, 1.0f), Color.Red);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - shadowmap rendering");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new ShadowmapRendering();
|
||||
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;
|
||||
}
|
||||
}
|
||||
143
Examples/Shaders/TextureTiling.cs
Normal file
143
Examples/Shaders/TextureTiling.cs
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - texture tiling
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example demonstrates how to tile a texture on a 3D model using raylib
|
||||
*
|
||||
* Example originally created with raylib 4.5, last time updated with raylib 4.5
|
||||
*
|
||||
* Example contributed by Luis Almeida (@luis605) 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) 2023-2025 Luis Almeida (@luis605)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public class TextureTiling : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Shaders / Texture Tiling";
|
||||
|
||||
public string Title => "raylib [shaders] example - texture tiling";
|
||||
|
||||
public bool CursorDisabled => true;
|
||||
|
||||
private Camera3D camera;
|
||||
private Model model;
|
||||
private Texture2D texture;
|
||||
private Shader shader;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(4.0f, 4.0f, 4.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 0.5f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
// Load a cube model
|
||||
var cube = GenMeshCube(1.0f, 1.0f, 1.0f);
|
||||
model = LoadModelFromMesh(cube);
|
||||
|
||||
// Load a texture and assign to cube model
|
||||
texture = LoadTexture("resources/cubicmap_atlas.png");
|
||||
model.Materials[0].Maps[(int)MaterialMapIndex.Diffuse].Texture = texture;
|
||||
|
||||
// Set the texture tiling using a shader
|
||||
var tiling = new[] { 3.0f, 3.0f };
|
||||
shader = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/tiling.fs");
|
||||
SetTextureWrap(texture, TextureWrap.Repeat);
|
||||
Raylib.SetShaderValue(shader, GetShaderLocation(shader, "tiling"), tiling, ShaderUniformDataType.Vec2);
|
||||
model.Materials[0].Shader = shader;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Free);
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Z))
|
||||
{
|
||||
camera.Target = new Vector3(0.0f, 0.5f, 0.0f);
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
BeginShaderMode(shader);
|
||||
DrawModel(model, new Vector3(0.0f, 0.0f, 0.0f), 2.0f, Color.White);
|
||||
EndShaderMode();
|
||||
|
||||
DrawGrid(10, 1.0f);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
DrawText("Use mouse to rotate the camera", 10, 10, 20, Color.DarkGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadModel(model); // Unload model
|
||||
UnloadShader(shader); // Unload shader
|
||||
UnloadTexture(texture); // Unload texture
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - texture tiling");
|
||||
|
||||
DisableCursor(); // Limit cursor to relative movement inside the window
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new TextureTiling();
|
||||
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;
|
||||
}
|
||||
}
|
||||
148
Examples/Shaders/VertexDisplacement.cs
Normal file
148
Examples/Shaders/VertexDisplacement.cs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - vertex displacement
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 5.0, last time updated with raylib 4.5
|
||||
*
|
||||
* Example contributed by Alex ZH (@ZzzhHe) 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) 2023-2025 Alex ZH (@ZzzhHe)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Rlgl;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public partial class VertexDisplacement : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
public string Name => "Shaders / Vertex Displacement";
|
||||
|
||||
public string Title => "raylib [shaders] example - vertex displacement";
|
||||
|
||||
private Camera3D camera;
|
||||
private Shader shader;
|
||||
private Texture2D perlinNoiseMap;
|
||||
private Model planeModel;
|
||||
private float time;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// set up camera
|
||||
camera = new();
|
||||
camera.Position = new Vector3(20.0f, 5.0f, -20.0f);
|
||||
camera.Target = new Vector3(0.0f, 0.0f, 0.0f);
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
camera.FovY = 60.0f;
|
||||
camera.Projection = CameraProjection.Perspective;
|
||||
|
||||
// Load vertex and fragment shaders
|
||||
shader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/vertex_displacement.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/vertex_displacement.fs"
|
||||
);
|
||||
|
||||
// Load perlin noise texture
|
||||
var perlinNoiseImage = GenImagePerlinNoise(512, 512, 0, 0, 1.0f);
|
||||
perlinNoiseMap = LoadTextureFromImage(perlinNoiseImage);
|
||||
UnloadImage(perlinNoiseImage);
|
||||
|
||||
// Set shader uniform location
|
||||
var perlinNoiseMapLoc = GetShaderLocation(shader, "perlinNoiseMap");
|
||||
EnableShader(shader.Id);
|
||||
ActiveTextureSlot(1);
|
||||
EnableTexture(perlinNoiseMap.Id);
|
||||
SetUniformSampler(perlinNoiseMapLoc, 1);
|
||||
|
||||
// Create a plane mesh and model
|
||||
var planeMesh = GenMeshPlane(50, 50, 50, 50);
|
||||
planeModel = LoadModelFromMesh(planeMesh);
|
||||
// Set plane model material
|
||||
var materials = planeModel.Materials;
|
||||
materials[0].Shader = shader;
|
||||
|
||||
time = 0.0f;
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, CameraMode.Free); // Update camera
|
||||
|
||||
time += GetFrameTime(); // Update time variable
|
||||
Raylib.SetShaderValue(shader, GetShaderLocation(shader, "time"), time, ShaderUniformDataType.Float); // Send time value to shader
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
|
||||
BeginShaderMode(shader);
|
||||
// Draw plane model
|
||||
DrawModel(planeModel, new Vector3(0.0f, 0.0f, 0.0f), 1.0f, new Color(255, 255, 255, 255));
|
||||
EndShaderMode();
|
||||
|
||||
EndMode3D();
|
||||
|
||||
DrawText("Vertex displacement", 10, 10, 20, Color.DarkGray);
|
||||
DrawFPS(10, 40);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadShader(shader);
|
||||
UnloadModel(planeModel);
|
||||
UnloadTexture(perlinNoiseMap);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - vertex displacement");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new VertexDisplacement();
|
||||
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;
|
||||
}
|
||||
}
|
||||
275
Examples/Shapes/BallPhysics.cs
Normal file
275
Examples/Shapes/BallPhysics.cs
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - ball physics
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by David Buzatto (@davidbuzatto) 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 David Buzatto (@davidbuzatto)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class BallPhysics : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MAX_BALLS = 5000; // Maximum quantity of balls
|
||||
|
||||
public string Name => "Shapes / Ball Physics";
|
||||
|
||||
public string Title => "raylib [shapes] example - ball physics";
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// Ball data type
|
||||
private struct Ball
|
||||
{
|
||||
public Vector2 position;
|
||||
public Vector2 speed;
|
||||
public Vector2 prevPosition;
|
||||
public float radius;
|
||||
public float friction;
|
||||
public float elasticity;
|
||||
public Color color;
|
||||
public bool grabbed;
|
||||
}
|
||||
|
||||
private Ball[] balls;
|
||||
private int ballCount;
|
||||
private int grabbedBallIndex; // Index of the current ball that is grabbed (-1 if none)
|
||||
private Vector2 pressOffset; // Mouse press offset relative to the ball that grabbedd
|
||||
|
||||
private float gravity; // World gravity
|
||||
|
||||
private Vector2 windowPosition;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
balls = new Ball[MAX_BALLS];
|
||||
|
||||
// Init first ball in the array
|
||||
balls[0] = new Ball
|
||||
{
|
||||
position = new Vector2(GetScreenWidth()/2.0f, GetScreenHeight()/2.0f),
|
||||
speed = new Vector2(200, 200),
|
||||
prevPosition = new Vector2(0, 0),
|
||||
radius = 40,
|
||||
friction = 0.99f,
|
||||
elasticity = 0.9f,
|
||||
color = Color.Blue,
|
||||
grabbed = false
|
||||
};
|
||||
|
||||
ballCount = 1;
|
||||
grabbedBallIndex = -1; // A reference to the current ball that is grabbed
|
||||
pressOffset = new Vector2(0, 0); // Mouse press offset relative to the ball that grabbedd
|
||||
|
||||
gravity = 100; // World gravity
|
||||
|
||||
windowPosition = GetWindowPosition();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
float delta = GetFrameTime();
|
||||
Vector2 mousePos = GetMousePosition();
|
||||
|
||||
// Checks if a ball was grabbed
|
||||
if (IsMouseButtonPressed(MouseButton.Left))
|
||||
{
|
||||
for (int i = ballCount - 1; i >= 0; i--)
|
||||
{
|
||||
pressOffset.X = mousePos.X - balls[i].position.X;
|
||||
pressOffset.Y = mousePos.Y - balls[i].position.Y;
|
||||
|
||||
// If the distance between the ball position and the mouse press position
|
||||
// is less than or equal to the ball radius, the event occurred inside the ball
|
||||
if (MathF.Sqrt(pressOffset.X*pressOffset.X + pressOffset.Y*pressOffset.Y) <= balls[i].radius)
|
||||
{
|
||||
balls[i].grabbed = true;
|
||||
grabbedBallIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Releases any ball the was grabbed
|
||||
if (IsMouseButtonReleased(MouseButton.Left))
|
||||
{
|
||||
if (grabbedBallIndex != -1)
|
||||
{
|
||||
balls[grabbedBallIndex].grabbed = false;
|
||||
grabbedBallIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Creates a new ball
|
||||
if (IsMouseButtonPressed(MouseButton.Right) || (IsKeyDown(KeyboardKey.LeftControl) && IsMouseButtonDown(MouseButton.Right)))
|
||||
{
|
||||
if (ballCount < MAX_BALLS)
|
||||
{
|
||||
balls[ballCount++] = new Ball
|
||||
{
|
||||
position = mousePos,
|
||||
speed = new Vector2(GetRandomValue(-300, 300), GetRandomValue(-300, 300)),
|
||||
prevPosition = new Vector2(0, 0),
|
||||
radius = 20.0f + GetRandomValue(0, 30),
|
||||
friction = 0.99f,
|
||||
elasticity = 0.9f,
|
||||
color = new Color(GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255),
|
||||
grabbed = false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Get window position change for shaking
|
||||
Vector2 windowPositionDelta = Vector2Subtract(windowPosition, GetWindowPosition());
|
||||
|
||||
if (Vector2Length(windowPositionDelta) > 5.0f)
|
||||
{
|
||||
for (int i = 0; i < ballCount; i++)
|
||||
{
|
||||
if (!balls[i].grabbed) balls[i].speed = Vector2Add(balls[i].speed, Vector2Scale(windowPositionDelta, 10.0f));
|
||||
}
|
||||
}
|
||||
|
||||
// Shake balls
|
||||
if (IsMouseButtonPressed(MouseButton.Middle))
|
||||
{
|
||||
for (int i = 0; i < ballCount; i++)
|
||||
{
|
||||
if (!balls[i].grabbed) balls[i].speed = new Vector2(GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000));
|
||||
}
|
||||
}
|
||||
|
||||
// Changes gravity
|
||||
gravity += GetMouseWheelMove()*5;
|
||||
|
||||
// Updates each ball state
|
||||
for (int i = 0; i < ballCount; i++)
|
||||
{
|
||||
// The ball is not grabbed
|
||||
if (!balls[i].grabbed)
|
||||
{
|
||||
// Ball repositioning using the velocity
|
||||
balls[i].position.X += balls[i].speed.X * delta;
|
||||
balls[i].position.Y += balls[i].speed.Y * delta;
|
||||
|
||||
// Does the ball hit the screen right boundary?
|
||||
if ((balls[i].position.X + balls[i].radius) >= screenWidth)
|
||||
{
|
||||
balls[i].position.X = screenWidth - balls[i].radius; // Ball repositioning
|
||||
balls[i].speed.X = -balls[i].speed.X*balls[i].elasticity; // Elasticity makes the ball lose 10% of its velocity on hit
|
||||
}
|
||||
// Does the ball hit the screen left boundary?
|
||||
else if ((balls[i].position.X - balls[i].radius) <= 0)
|
||||
{
|
||||
balls[i].position.X = balls[i].radius;
|
||||
balls[i].speed.X = -balls[i].speed.X*balls[i].elasticity;
|
||||
}
|
||||
|
||||
// The same for y axis
|
||||
if ((balls[i].position.Y + balls[i].radius) >= screenHeight)
|
||||
{
|
||||
balls[i].position.Y = screenHeight - balls[i].radius;
|
||||
balls[i].speed.Y = -balls[i].speed.Y*balls[i].elasticity;
|
||||
}
|
||||
else if ((balls[i].position.Y - balls[i].radius) <= 0)
|
||||
{
|
||||
balls[i].position.Y = balls[i].radius;
|
||||
balls[i].speed.Y = -balls[i].speed.Y*balls[i].elasticity;
|
||||
}
|
||||
|
||||
// Friction makes the ball lose 1% of its velocity each frame
|
||||
balls[i].speed.X = balls[i].speed.X*balls[i].friction;
|
||||
// Gravity affects only the y axis
|
||||
balls[i].speed.Y = balls[i].speed.Y*balls[i].friction + gravity;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ball repositioning using the mouse position
|
||||
balls[i].position.X = mousePos.X - pressOffset.X;
|
||||
balls[i].position.Y = mousePos.Y - pressOffset.Y;
|
||||
|
||||
// While the ball is grabbed, recalculates its velocity
|
||||
balls[i].speed.X = (balls[i].position.X - balls[i].prevPosition.X)/delta;
|
||||
balls[i].speed.Y = (balls[i].position.Y - balls[i].prevPosition.Y)/delta;
|
||||
balls[i].prevPosition = balls[i].position;
|
||||
}
|
||||
}
|
||||
|
||||
windowPosition = GetWindowPosition();
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
for (int i = 0; i < ballCount; i++)
|
||||
{
|
||||
DrawCircleV(balls[i].position, balls[i].radius, balls[i].color);
|
||||
DrawCircleLinesV(balls[i].position, balls[i].radius, Color.Black);
|
||||
}
|
||||
|
||||
DrawText("grab a ball by pressing with the mouse and throw it by releasing", 10, 10, 10, Color.DarkGray);
|
||||
DrawText("right click to create new balls (keep left control pressed to create a lot)", 10, 30, 10, Color.DarkGray);
|
||||
DrawText("use mouse wheel to change gravity", 10, 50, 10, Color.DarkGray);
|
||||
DrawText("middle click to shake", 10, 70, 10, Color.DarkGray);
|
||||
DrawText($"BALL COUNT: {ballCount}", 10, GetScreenHeight() - 70, 20, Color.Black);
|
||||
DrawText($"GRAVITY: {gravity:F2}", 10, GetScreenHeight() - 40, 20, Color.Black);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - ball physics");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new BallPhysics();
|
||||
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;
|
||||
}
|
||||
}
|
||||
290
Examples/Shapes/BulletHell.cs
Normal file
290
Examples/Shapes/BulletHell.cs
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - bullet hell
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 5.6, last time updated with raylib 5.6
|
||||
*
|
||||
* Example contributed by Zero (@zerohorsepower) 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 Zero (@zerohorsepower)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class BulletHell : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MAX_BULLETS = 500000; // Max bullets to be processed
|
||||
|
||||
public string Name => "Shapes / Bullet Hell";
|
||||
|
||||
public string Title => "raylib [shapes] example - bullet hell";
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
private struct Bullet
|
||||
{
|
||||
public Vector2 position; // Bullet position on screen
|
||||
public Vector2 acceleration; // Amount of pixels to be incremented to position every frame
|
||||
public bool disabled; // Skip processing and draw case out of screen
|
||||
public Color color; // Bullet color
|
||||
}
|
||||
|
||||
// Bullets definition
|
||||
private Bullet[] bullets;
|
||||
private int bulletCount;
|
||||
private int bulletDisabledCount; // Used to calculate how many bullets are on screen
|
||||
private int bulletRadius;
|
||||
private float bulletSpeed;
|
||||
private int bulletRows;
|
||||
private Color[] bulletColor;
|
||||
|
||||
// Spawner variables
|
||||
private float baseDirection;
|
||||
private int angleIncrement; // After spawn all bullet rows, increment this value on the baseDirection for next the frame
|
||||
private float spawnCooldown;
|
||||
private float spawnCooldownTimer;
|
||||
|
||||
// Magic circle
|
||||
private float magicCircleRotation;
|
||||
|
||||
// Used on performance drawing
|
||||
private RenderTexture2D bulletTexture;
|
||||
|
||||
private bool drawInPerformanceMode; // Switch between DrawCircle() and DrawTexture()
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Bullets definition
|
||||
bullets = new Bullet[MAX_BULLETS]; // Bullets array
|
||||
bulletCount = 0;
|
||||
bulletDisabledCount = 0;
|
||||
bulletRadius = 10;
|
||||
bulletSpeed = 3.0f;
|
||||
bulletRows = 6;
|
||||
bulletColor = new[] { Color.Red, Color.Blue };
|
||||
|
||||
// Spawner variables
|
||||
baseDirection = 0;
|
||||
angleIncrement = 5;
|
||||
spawnCooldown = 2;
|
||||
spawnCooldownTimer = spawnCooldown;
|
||||
|
||||
// Magic circle
|
||||
magicCircleRotation = 0;
|
||||
|
||||
// Used on performance drawing
|
||||
bulletTexture = LoadRenderTexture(24, 24);
|
||||
|
||||
// Draw circle to bullet texture, then draw bullet using DrawTexture()
|
||||
// NOTE: This is done to improve the performance, since DrawCircle() is very slow
|
||||
BeginTextureMode(bulletTexture);
|
||||
DrawCircle(12, 12, (float)bulletRadius, Color.White);
|
||||
DrawCircleLines(12, 12, (float)bulletRadius, Color.Black);
|
||||
EndTextureMode();
|
||||
|
||||
drawInPerformanceMode = true;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Reset the bullet index
|
||||
// New bullets will replace the old ones that are already disabled due to out-of-screen
|
||||
if (bulletCount >= MAX_BULLETS)
|
||||
{
|
||||
bulletCount = 0;
|
||||
bulletDisabledCount = 0;
|
||||
}
|
||||
|
||||
spawnCooldownTimer--;
|
||||
if (spawnCooldownTimer < 0)
|
||||
{
|
||||
spawnCooldownTimer = spawnCooldown;
|
||||
|
||||
// Spawn bullets
|
||||
float degreesPerRow = 360.0f/bulletRows;
|
||||
for (int row = 0; row < bulletRows; row++)
|
||||
{
|
||||
if (bulletCount < MAX_BULLETS)
|
||||
{
|
||||
bullets[bulletCount].position = new Vector2((float)screenWidth/2, (float)screenHeight/2);
|
||||
bullets[bulletCount].disabled = false;
|
||||
bullets[bulletCount].color = bulletColor[row%2];
|
||||
|
||||
float bulletDirection = baseDirection + (degreesPerRow*row);
|
||||
|
||||
// Bullet speed*bullet direction, this will determine how much pixels will be incremented/decremented
|
||||
// from the bullet position every frame. Since the bullets doesn't change its direction and speed,
|
||||
// only need to calculate it at the spawning time
|
||||
// 0 degrees = right, 90 degrees = down, 180 degrees = left and 270 degrees = up, basically clockwise
|
||||
// Case you want it to be anti-clockwise, add "* -1" at the y acceleration
|
||||
bullets[bulletCount].acceleration = new Vector2(
|
||||
bulletSpeed*MathF.Cos(bulletDirection*DEG2RAD),
|
||||
bulletSpeed*MathF.Sin(bulletDirection*DEG2RAD)
|
||||
);
|
||||
|
||||
bulletCount++;
|
||||
}
|
||||
}
|
||||
|
||||
baseDirection += angleIncrement;
|
||||
}
|
||||
|
||||
// Update bullets position based on its acceleration
|
||||
for (int i = 0; i < bulletCount; i++)
|
||||
{
|
||||
// Only update bullet if inside the screen
|
||||
if (!bullets[i].disabled)
|
||||
{
|
||||
bullets[i].position.X += bullets[i].acceleration.X;
|
||||
bullets[i].position.Y += bullets[i].acceleration.Y;
|
||||
|
||||
// Disable bullet if out of screen
|
||||
if ((bullets[i].position.X < -bulletRadius*2) ||
|
||||
(bullets[i].position.X > screenWidth + bulletRadius*2) ||
|
||||
(bullets[i].position.Y < -bulletRadius*2) ||
|
||||
(bullets[i].position.Y > screenHeight + bulletRadius*2))
|
||||
{
|
||||
bullets[i].disabled = true;
|
||||
bulletDisabledCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Input logic
|
||||
if ((IsKeyPressed(KeyboardKey.Right) || IsKeyPressed(KeyboardKey.D)) && (bulletRows < 359)) bulletRows++;
|
||||
if ((IsKeyPressed(KeyboardKey.Left) || IsKeyPressed(KeyboardKey.A)) && (bulletRows > 1)) bulletRows--;
|
||||
if (IsKeyPressed(KeyboardKey.Up) || IsKeyPressed(KeyboardKey.W)) bulletSpeed += 0.25f;
|
||||
if ((IsKeyPressed(KeyboardKey.Down) || IsKeyPressed(KeyboardKey.S)) && (bulletSpeed > 0.50f)) bulletSpeed -= 0.25f;
|
||||
if (IsKeyPressed(KeyboardKey.Z) && (spawnCooldown > 1)) spawnCooldown--;
|
||||
if (IsKeyPressed(KeyboardKey.X)) spawnCooldown++;
|
||||
if (IsKeyPressed(KeyboardKey.Enter)) drawInPerformanceMode = !drawInPerformanceMode;
|
||||
|
||||
if (IsKeyDown(KeyboardKey.Space))
|
||||
{
|
||||
angleIncrement += 1;
|
||||
angleIncrement %= 360;
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.C))
|
||||
{
|
||||
bulletCount = 0;
|
||||
bulletDisabledCount = 0;
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw magic circle
|
||||
magicCircleRotation++;
|
||||
DrawRectanglePro(new Rectangle((float)screenWidth/2, (float)screenHeight/2, 120, 120),
|
||||
new Vector2(60.0f, 60.0f), magicCircleRotation, Color.Purple);
|
||||
DrawRectanglePro(new Rectangle((float)screenWidth/2, (float)screenHeight/2, 120, 120),
|
||||
new Vector2(60.0f, 60.0f), magicCircleRotation + 45, Color.Purple);
|
||||
DrawCircleLines(screenWidth/2, screenHeight/2, 70, Color.Black);
|
||||
DrawCircleLines(screenWidth/2, screenHeight/2, 50, Color.Black);
|
||||
DrawCircleLines(screenWidth/2, screenHeight/2, 30, Color.Black);
|
||||
|
||||
// Draw bullets
|
||||
if (drawInPerformanceMode)
|
||||
{
|
||||
// Draw bullets using pre-rendered texture containing circle
|
||||
for (int i = 0; i < bulletCount; i++)
|
||||
{
|
||||
// Do not draw disabled bullets (out of screen)
|
||||
if (!bullets[i].disabled)
|
||||
{
|
||||
DrawTexture(bulletTexture.Texture,
|
||||
(int)(bullets[i].position.X - bulletTexture.Texture.Width*0.5f),
|
||||
(int)(bullets[i].position.Y - bulletTexture.Texture.Height*0.5f),
|
||||
bullets[i].color);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw bullets using DrawCircle(), less performant
|
||||
for (int i = 0; i < bulletCount; i++)
|
||||
{
|
||||
// Do not draw disabled bullets (out of screen)
|
||||
if (!bullets[i].disabled)
|
||||
{
|
||||
DrawCircleV(bullets[i].position, (float)bulletRadius, bullets[i].color);
|
||||
DrawCircleLinesV(bullets[i].position, (float)bulletRadius, Color.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw UI
|
||||
DrawRectangle(10, 10, 280, 150, new Color(0, 0, 0, 200));
|
||||
DrawText("Controls:", 20, 20, 10, Color.LightGray);
|
||||
DrawText("- Right/Left or A/D: Change rows number", 40, 40, 10, Color.LightGray);
|
||||
DrawText("- Up/Down or W/S: Change bullet speed", 40, 60, 10, Color.LightGray);
|
||||
DrawText("- Z or X: Change spawn cooldown", 40, 80, 10, Color.LightGray);
|
||||
DrawText("- Space (Hold): Change the angle increment", 40, 100, 10, Color.LightGray);
|
||||
DrawText("- Enter: Switch draw method (Performance)", 40, 120, 10, Color.LightGray);
|
||||
DrawText("- C: Clear bullets", 40, 140, 10, Color.LightGray);
|
||||
|
||||
DrawRectangle(610, 10, 170, 30, new Color(0, 0, 0, 200));
|
||||
if (drawInPerformanceMode) DrawText("Draw method: DrawTexture(*)", 620, 20, 10, Color.Green);
|
||||
else DrawText("Draw method: DrawCircle(*)", 620, 20, 10, Color.Red);
|
||||
|
||||
DrawRectangle(135, 410, 530, 30, new Color(0, 0, 0, 200));
|
||||
DrawText($"[ FPS: {GetFPS()}, Bullets: {bulletCount - bulletDisabledCount}, Rows: {bulletRows}, Bullet speed: {bulletSpeed:F2}, Angle increment per frame: {angleIncrement}, Cooldown: {spawnCooldown:F0} ]",
|
||||
155, 420, 10, Color.Green);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadRenderTexture(bulletTexture); // Unload bullet texture
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - bullet hell");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new BulletHell();
|
||||
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;
|
||||
}
|
||||
}
|
||||
240
Examples/Shapes/ClockOfClocks.cs
Normal file
240
Examples/Shapes/ClockOfClocks.cs
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - clock of clocks
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by JP Mortiboys (@themushroompirates) 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 JP Mortiboys (@themushroompirates)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath; // Required for: Lerp(), Clamp()
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class ClockOfClocks : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Shapes / Clock of Clocks";
|
||||
|
||||
public string Title => "raylib [shapes] example - clock of clocks";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
private Color bgColor;
|
||||
private Color handsColor;
|
||||
|
||||
private const float clockFaceSize = 24;
|
||||
private const float clockFaceSpacing = 8.0f;
|
||||
private const float sectionSpacing = 16.0f;
|
||||
|
||||
private static readonly Vector2 TL = new(0.0f, 90.0f); // Top-left corner
|
||||
private static readonly Vector2 TR = new(90.0f, 180.0f); // Top-right corner
|
||||
private static readonly Vector2 BR = new(180.0f, 270.0f); // Bottom-right corner
|
||||
private static readonly Vector2 BL = new(0.0f, 270.0f); // Bottom-left corner
|
||||
private static readonly Vector2 HH = new(0.0f, 180.0f); // Horizontal line
|
||||
private static readonly Vector2 VV = new(90.0f, 270.0f); // Vertical line
|
||||
private static readonly Vector2 ZZ = new(135.0f, 135.0f); // Not relevant
|
||||
|
||||
private Vector2[,] digitAngles;
|
||||
|
||||
// Time for the hands to move to the new position (in seconds); this must be <1s
|
||||
private const float handsMoveDuration = 0.5f;
|
||||
|
||||
private int prevSeconds;
|
||||
private Vector2[,] currentAngles;
|
||||
private Vector2[,] srcAngles;
|
||||
private Vector2[,] dstAngles;
|
||||
|
||||
private float handsMoveTimer;
|
||||
private int hourMode;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
bgColor = ColorLerp(Color.DarkBlue, Color.Black, 0.75f);
|
||||
handsColor = ColorLerp(Color.Yellow, Color.RayWhite, .25f);
|
||||
|
||||
digitAngles = new Vector2[10, 24]
|
||||
{
|
||||
/* 0 */ { TL, HH, HH, TR, /* */ VV, TL, TR, VV, /* */ VV, VV, VV, VV, /* */ VV, VV, VV, VV, /* */ VV, BL, BR, VV, /* */ BL, HH, HH, BR },
|
||||
/* 1 */ { TL, HH, TR, ZZ, /* */ BL, TR, VV, ZZ, /* */ ZZ, VV, VV, ZZ, /* */ ZZ, VV, VV, ZZ, /* */ TL, BR, BL, TR, /* */ BL, HH, HH, BR },
|
||||
/* 2 */ { TL, HH, HH, TR, /* */ BL, HH, TR, VV, /* */ TL, HH, BR, VV, /* */ VV, TL, HH, BR, /* */ VV, BL, HH, TR, /* */ BL, HH, HH, BR },
|
||||
/* 3 */ { TL, HH, HH, TR, /* */ BL, HH, TR, VV, /* */ TL, HH, BR, VV, /* */ BL, HH, TR, VV, /* */ TL, HH, BR, VV, /* */ BL, HH, HH, BR },
|
||||
/* 4 */ { TL, TR, TL, TR, /* */ VV, VV, VV, VV, /* */ VV, BL, BR, VV, /* */ BL, HH, TR, VV, /* */ ZZ, ZZ, VV, VV, /* */ ZZ, ZZ, BL, BR },
|
||||
/* 5 */ { TL, HH, HH, TR, /* */ VV, TL, HH, BR, /* */ VV, BL, HH, TR, /* */ BL, HH, TR, VV, /* */ TL, HH, BR, VV, /* */ BL, HH, HH, BR },
|
||||
/* 6 */ { TL, HH, HH, TR, /* */ VV, TL, HH, BR, /* */ VV, BL, HH, TR, /* */ VV, TL, TR, VV, /* */ VV, BL, BR, VV, /* */ BL, HH, HH, BR },
|
||||
/* 7 */ { TL, HH, HH, TR, /* */ BL, HH, TR, VV, /* */ ZZ, ZZ, VV, VV, /* */ ZZ, ZZ, VV, VV, /* */ ZZ, ZZ, VV, VV, /* */ ZZ, ZZ, BL, BR },
|
||||
/* 8 */ { TL, HH, HH, TR, /* */ VV, TL, TR, VV, /* */ VV, BL, BR, VV, /* */ VV, TL, TR, VV, /* */ VV, BL, BR, VV, /* */ BL, HH, HH, BR },
|
||||
/* 9 */ { TL, HH, HH, TR, /* */ VV, TL, TR, VV, /* */ VV, BL, BR, VV, /* */ BL, HH, TR, VV, /* */ TL, HH, BR, VV, /* */ BL, HH, HH, BR },
|
||||
};
|
||||
|
||||
prevSeconds = -1;
|
||||
currentAngles = new Vector2[6, 24];
|
||||
srcAngles = new Vector2[6, 24];
|
||||
dstAngles = new Vector2[6, 24];
|
||||
|
||||
handsMoveTimer = 0.0f;
|
||||
hourMode = 24;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Get the current time
|
||||
DateTime timeinfo = DateTime.Now;
|
||||
|
||||
if (timeinfo.Second != prevSeconds)
|
||||
{
|
||||
// The time has changed, so we need to move the hands to the new positions
|
||||
prevSeconds = timeinfo.Second;
|
||||
|
||||
// Format the current time so we can access the individual digits
|
||||
string clockDigits = $"{timeinfo.Hour % hourMode:D2}{timeinfo.Minute:D2}{timeinfo.Second:D2}";
|
||||
|
||||
// Fetch where we want all the hands to be
|
||||
for (int digit = 0; digit < 6; digit++)
|
||||
{
|
||||
for (int cell = 0; cell < 24; cell++)
|
||||
{
|
||||
srcAngles[digit, cell] = currentAngles[digit, cell];
|
||||
dstAngles[digit, cell] = digitAngles[clockDigits[digit] - '0', cell];
|
||||
|
||||
// Quick exception for 12h mode
|
||||
if ((digit == 0) && (hourMode == 12) && (clockDigits[0] == '0')) dstAngles[digit, cell] = ZZ;
|
||||
if (srcAngles[digit, cell].X > dstAngles[digit, cell].X) srcAngles[digit, cell].X -= 360.0f;
|
||||
if (srcAngles[digit, cell].Y > dstAngles[digit, cell].Y) srcAngles[digit, cell].Y -= 360.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the timer
|
||||
handsMoveTimer = -GetFrameTime();
|
||||
}
|
||||
|
||||
// Now let's animate all the hands if we need to
|
||||
if (handsMoveTimer < handsMoveDuration)
|
||||
{
|
||||
// Increase the timer but don't go above the maximum
|
||||
handsMoveTimer = Clamp(handsMoveTimer + GetFrameTime(), 0, handsMoveDuration);
|
||||
|
||||
// Calculate the % completion of the animation
|
||||
float t = handsMoveTimer / handsMoveDuration;
|
||||
|
||||
// A little cheeky smoothstep
|
||||
t = t * t * (3.0f - 2.0f * t);
|
||||
|
||||
for (int digit = 0; digit < 6; digit++)
|
||||
{
|
||||
for (int cell = 0; cell < 24; cell++)
|
||||
{
|
||||
currentAngles[digit, cell].X = Lerp(srcAngles[digit, cell].X, dstAngles[digit, cell].X, t);
|
||||
currentAngles[digit, cell].Y = Lerp(srcAngles[digit, cell].Y, dstAngles[digit, cell].Y, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle input
|
||||
if (IsKeyPressed(KeyboardKey.Space)) hourMode = 36 - hourMode; // Toggle between 12 and 24 hour mode with space
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(bgColor);
|
||||
|
||||
DrawText($"{hourMode}-h mode, space to change", 10, 30, 20, Color.RayWhite);
|
||||
|
||||
float xOffset = 4.0f;
|
||||
|
||||
for (int digit = 0; digit < 6; digit++)
|
||||
{
|
||||
for (int row = 0; row < 6; row++)
|
||||
{
|
||||
for (int col = 0; col < 4; col++)
|
||||
{
|
||||
Vector2 centre = new(
|
||||
xOffset + col * (clockFaceSize + clockFaceSpacing) + clockFaceSize * 0.5f,
|
||||
100 + row * (clockFaceSize + clockFaceSpacing) + clockFaceSize * 0.5f
|
||||
);
|
||||
|
||||
DrawRing(centre, clockFaceSize * 0.5f - 2.0f, clockFaceSize * 0.5f, 0, 360, 24, Color.DarkGray);
|
||||
|
||||
// Big hand
|
||||
DrawRectanglePro(
|
||||
new Rectangle(centre.X, centre.Y, clockFaceSize * 0.5f + 4.0f, 4.0f),
|
||||
new Vector2(2.0f, 2.0f),
|
||||
currentAngles[digit, row * 4 + col].X,
|
||||
handsColor
|
||||
);
|
||||
|
||||
// Little hand
|
||||
DrawRectanglePro(
|
||||
new Rectangle(centre.X, centre.Y, clockFaceSize * 0.5f + 2.0f, 4.0f),
|
||||
new Vector2(2.0f, 2.0f),
|
||||
currentAngles[digit, row * 4 + col].Y,
|
||||
handsColor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
xOffset += (clockFaceSize + clockFaceSpacing) * 4;
|
||||
if (digit % 2 == 1)
|
||||
{
|
||||
DrawRing(new Vector2(xOffset + 4.0f, 160.0f), 6.0f, 8.0f, 0.0f, 360.0f, 24, handsColor);
|
||||
DrawRing(new Vector2(xOffset + 4.0f, 225.0f), 6.0f, 8.0f, 0.0f, 360.0f, 24, handsColor);
|
||||
xOffset += sectionSpacing;
|
||||
}
|
||||
}
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - clock of clocks");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new ClockOfClocks();
|
||||
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;
|
||||
}
|
||||
}
|
||||
130
Examples/Shapes/DashedLine.cs
Normal file
130
Examples/Shapes/DashedLine.cs
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - dashed line
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.5
|
||||
*
|
||||
* Example contributed by Luís Almeida (@luis605)
|
||||
*
|
||||
* 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 Luís Almeida (@luis605)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class DashedLine : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Shapes / Dashed Line";
|
||||
|
||||
public string Title => "raylib [shapes] example - dashed line";
|
||||
|
||||
// Line Properties
|
||||
private Vector2 lineStartPosition;
|
||||
private Vector2 lineEndPosition;
|
||||
private float dashLength;
|
||||
private float blankLength;
|
||||
|
||||
// Color selection
|
||||
private Color[] lineColors;
|
||||
private int colorIndex;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Line Properties
|
||||
lineStartPosition = new Vector2(20.0f, 50.0f);
|
||||
lineEndPosition = new Vector2(780.0f, 400.0f);
|
||||
dashLength = 25.0f;
|
||||
blankLength = 15.0f;
|
||||
|
||||
// Color selection
|
||||
lineColors = new[] { Color.Red, Color.Orange, Color.Gold, Color.Green, Color.Blue, Color.Violet, Color.Pink, Color.Black };
|
||||
colorIndex = 0;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
lineEndPosition = GetMousePosition(); // Line endpoint follows the mouse
|
||||
|
||||
// Change Dash Length (UP/DOWN arrows)
|
||||
if (IsKeyDown(KeyboardKey.Up)) dashLength += 1.0f;
|
||||
if (IsKeyDown(KeyboardKey.Down) && dashLength > 1.0f) dashLength -= 1.0f;
|
||||
|
||||
// Change Space Length (LEFT/RIGHT arrows)
|
||||
if (IsKeyDown(KeyboardKey.Right)) blankLength += 1.0f;
|
||||
if (IsKeyDown(KeyboardKey.Left) && blankLength > 1.0f) blankLength -= 1.0f;
|
||||
|
||||
// Cycle through colors ('C' key)
|
||||
if (IsKeyPressed(KeyboardKey.C)) colorIndex = (colorIndex + 1)%lineColors.Length;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw the dashed line with the current properties
|
||||
DrawLineDashed(lineStartPosition, lineEndPosition, (int)dashLength, (int)blankLength, lineColors[colorIndex]);
|
||||
|
||||
// Draw UI and Instructions
|
||||
DrawRectangle(5, 5, 265, 95, Fade(Color.SkyBlue, 0.5f));
|
||||
DrawRectangleLines(5, 5, 265, 95, Color.Blue);
|
||||
|
||||
DrawText("CONTROLS:", 15, 15, 10, Color.Black);
|
||||
DrawText("UP/DOWN: Change Dash Length", 15, 35, 10, Color.Black);
|
||||
DrawText("LEFT/RIGHT: Change Space Length", 15, 55, 10, Color.Black);
|
||||
DrawText("C: Cycle Color", 15, 75, 10, Color.Black);
|
||||
|
||||
DrawText($"Dash: {dashLength:F0} | Space: {blankLength:F0}", 15, 115, 10, Color.DarkGray);
|
||||
|
||||
DrawFPS(screenWidth - 80, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - dashed line");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new DashedLine();
|
||||
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;
|
||||
}
|
||||
}
|
||||
327
Examples/Shapes/DigitalClock.cs
Normal file
327
Examples/Shapes/DigitalClock.cs
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - digital clock
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, 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) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class DigitalClock : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int CLOCK_ANALOG = 0;
|
||||
private const int CLOCK_DIGITAL = 1;
|
||||
|
||||
public string Name => "Shapes / Digital Clock";
|
||||
|
||||
public string Title => "raylib [shapes] example - digital clock";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// Clock hand type
|
||||
private struct ClockHand
|
||||
{
|
||||
public int value; // Time value
|
||||
|
||||
// Visual elements
|
||||
public float angle; // Hand angle
|
||||
public int length; // Hand length
|
||||
public int thickness; // Hand thickness
|
||||
public Color color; // Hand color
|
||||
}
|
||||
|
||||
// Clock hands
|
||||
private struct Clock
|
||||
{
|
||||
public ClockHand second; // Clock hand for seconds
|
||||
public ClockHand minute; // Clock hand for minutes
|
||||
public ClockHand hour; // Clock hand for hours
|
||||
}
|
||||
|
||||
private int clockMode;
|
||||
private Clock clock;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
clockMode = CLOCK_DIGITAL;
|
||||
|
||||
// Initialize clock
|
||||
// NOTE: Includes visual info for analog clock
|
||||
clock = new Clock();
|
||||
|
||||
clock.second.angle = 45;
|
||||
clock.second.length = 140;
|
||||
clock.second.thickness = 3;
|
||||
clock.second.color = Color.Maroon;
|
||||
|
||||
clock.minute.angle = 10;
|
||||
clock.minute.length = 130;
|
||||
clock.minute.thickness = 7;
|
||||
clock.minute.color = Color.DarkGray;
|
||||
|
||||
clock.hour.angle = 0;
|
||||
clock.hour.length = 100;
|
||||
clock.hour.thickness = 7;
|
||||
clock.hour.color = Color.Black;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyPressed(KeyboardKey.Space))
|
||||
{
|
||||
// Toggle clock mode
|
||||
if (clockMode == CLOCK_DIGITAL) clockMode = CLOCK_ANALOG;
|
||||
else if (clockMode == CLOCK_ANALOG) clockMode = CLOCK_DIGITAL;
|
||||
}
|
||||
|
||||
UpdateClock(); // Update clock required data: value and angle
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw clock in selected mode
|
||||
if (clockMode == CLOCK_ANALOG) DrawClockAnalog(clock, new Vector2(400, 240));
|
||||
else if (clockMode == CLOCK_DIGITAL)
|
||||
{
|
||||
DrawClockDigital(clock, new Vector2(30, 60));
|
||||
|
||||
// Draw clock using default raylib font
|
||||
string clockTime = $"{clock.hour.value:D2}:{clock.minute.value:D2}:{clock.second.value:D2}";
|
||||
DrawText(clockTime, GetScreenWidth() / 2 - MeasureText(clockTime, 150) / 2, 300, 150, Color.Black);
|
||||
}
|
||||
|
||||
DrawText($"Press [SPACE] to switch clock mode: {((clockMode == CLOCK_DIGITAL) ? "DIGITAL CLOCK" : "ANALOGUE CLOCK")}",
|
||||
10, 10, 20, Color.DarkGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// Update clock time
|
||||
private void UpdateClock()
|
||||
{
|
||||
DateTime timeinfo = DateTime.Now;
|
||||
|
||||
// Updating time data
|
||||
clock.second.value = timeinfo.Second;
|
||||
clock.minute.value = timeinfo.Minute;
|
||||
clock.hour.value = timeinfo.Hour;
|
||||
|
||||
clock.hour.angle = (timeinfo.Hour % 12) * 180.0f / 6.0f;
|
||||
clock.hour.angle += (timeinfo.Minute % 60) * 30 / 60.0f;
|
||||
clock.hour.angle -= 90;
|
||||
|
||||
clock.minute.angle = (timeinfo.Minute % 60) * 6.0f;
|
||||
clock.minute.angle += (timeinfo.Second % 60) * 6 / 60.0f;
|
||||
clock.minute.angle -= 90;
|
||||
|
||||
clock.second.angle = (timeinfo.Second % 60) * 6.0f;
|
||||
clock.second.angle -= 90;
|
||||
}
|
||||
|
||||
// Draw analog clock
|
||||
// Parameter: position, refers to center position
|
||||
private static void DrawClockAnalog(Clock clock, Vector2 position)
|
||||
{
|
||||
// Draw clock base
|
||||
DrawCircleV(position, clock.second.length + 40.0f, Color.LightGray);
|
||||
DrawCircleV(position, 12.0f, Color.Gray);
|
||||
|
||||
// Draw clock minutes/seconds lines
|
||||
for (int i = 0; i < 60; i++)
|
||||
{
|
||||
DrawLineEx(new Vector2(position.X + (clock.second.length + ((i % 5) != 0 ? 10 : 6)) * MathF.Cos((6.0f * i - 90.0f) * DEG2RAD),
|
||||
position.Y + (clock.second.length + ((i % 5) != 0 ? 10 : 6)) * MathF.Sin((6.0f * i - 90.0f) * DEG2RAD)),
|
||||
new Vector2(position.X + (clock.second.length + 20) * MathF.Cos((6.0f * i - 90.0f) * DEG2RAD),
|
||||
position.Y + (clock.second.length + 20) * MathF.Sin((6.0f * i - 90.0f) * DEG2RAD)), ((i % 5) != 0 ? 1.0f : 3.0f), Color.DarkGray);
|
||||
}
|
||||
|
||||
// Draw hand seconds
|
||||
DrawRectanglePro(new Rectangle(position.X, position.Y, (float)clock.second.length, (float)clock.second.thickness),
|
||||
new Vector2(0.0f, clock.second.thickness / 2.0f), clock.second.angle, clock.second.color);
|
||||
|
||||
// Draw hand minutes
|
||||
DrawRectanglePro(new Rectangle(position.X, position.Y, (float)clock.minute.length, (float)clock.minute.thickness),
|
||||
new Vector2(0.0f, clock.minute.thickness / 2.0f), clock.minute.angle, clock.minute.color);
|
||||
|
||||
// Draw hand hours
|
||||
DrawRectanglePro(new Rectangle(position.X, position.Y, (float)clock.hour.length, (float)clock.hour.thickness),
|
||||
new Vector2(0.0f, clock.hour.thickness / 2.0f), clock.hour.angle, clock.hour.color);
|
||||
}
|
||||
|
||||
// Draw digital clock
|
||||
// PARAM: position, refers to top-left corner
|
||||
private static void DrawClockDigital(Clock clock, Vector2 position)
|
||||
{
|
||||
// Draw clock using custom 7-segments display (made of shapes)
|
||||
DrawDisplayValue(new Vector2(position.X, position.Y), clock.hour.value / 10, Color.Red, Fade(Color.LightGray, 0.3f));
|
||||
DrawDisplayValue(new Vector2(position.X + 120, position.Y), clock.hour.value % 10, Color.Red, Fade(Color.LightGray, 0.3f));
|
||||
|
||||
DrawCircle((int)position.X + 240, (int)position.Y + 70, 12, (clock.second.value % 2) != 0 ? Color.Red : Fade(Color.LightGray, 0.3f));
|
||||
DrawCircle((int)position.X + 240, (int)position.Y + 150, 12, (clock.second.value % 2) != 0 ? Color.Red : Fade(Color.LightGray, 0.3f));
|
||||
|
||||
DrawDisplayValue(new Vector2(position.X + 260, position.Y), clock.minute.value / 10, Color.Red, Fade(Color.LightGray, 0.3f));
|
||||
DrawDisplayValue(new Vector2(position.X + 380, position.Y), clock.minute.value % 10, Color.Red, Fade(Color.LightGray, 0.3f));
|
||||
|
||||
DrawCircle((int)position.X + 500, (int)position.Y + 70, 12, (clock.second.value % 2) != 0 ? Color.Red : Fade(Color.LightGray, 0.3f));
|
||||
DrawCircle((int)position.X + 500, (int)position.Y + 150, 12, (clock.second.value % 2) != 0 ? Color.Red : Fade(Color.LightGray, 0.3f));
|
||||
|
||||
DrawDisplayValue(new Vector2(position.X + 520, position.Y), clock.second.value / 10, Color.Red, Fade(Color.LightGray, 0.3f));
|
||||
DrawDisplayValue(new Vector2(position.X + 640, position.Y), clock.second.value % 10, Color.Red, Fade(Color.LightGray, 0.3f));
|
||||
}
|
||||
|
||||
// Draw 7-segment display with value
|
||||
private static void DrawDisplayValue(Vector2 position, int value, Color colorOn, Color colorOff)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0: Draw7SDisplay(position, 0b00111111, colorOn, colorOff); break;
|
||||
case 1: Draw7SDisplay(position, 0b00000110, colorOn, colorOff); break;
|
||||
case 2: Draw7SDisplay(position, 0b01011011, colorOn, colorOff); break;
|
||||
case 3: Draw7SDisplay(position, 0b01001111, colorOn, colorOff); break;
|
||||
case 4: Draw7SDisplay(position, 0b01100110, colorOn, colorOff); break;
|
||||
case 5: Draw7SDisplay(position, 0b01101101, colorOn, colorOff); break;
|
||||
case 6: Draw7SDisplay(position, 0b01111101, colorOn, colorOff); break;
|
||||
case 7: Draw7SDisplay(position, 0b00000111, colorOn, colorOff); break;
|
||||
case 8: Draw7SDisplay(position, 0b01111111, colorOn, colorOff); break;
|
||||
case 9: Draw7SDisplay(position, 0b01101111, colorOn, colorOff); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw seven segments display
|
||||
// Parameter: position, refers to top-left corner of display
|
||||
// Parameter: segments, defines in binary the segments to be activated
|
||||
private static void Draw7SDisplay(Vector2 position, int segments, Color colorOn, Color colorOff)
|
||||
{
|
||||
int segmentLen = 60;
|
||||
int segmentThick = 20;
|
||||
float offsetYAdjust = segmentThick * 0.3f; // HACK: Adjust gap space between segment limits
|
||||
|
||||
// Segment A
|
||||
DrawDisplaySegment(new Vector2(position.X + segmentThick + segmentLen / 2.0f, position.Y + segmentThick),
|
||||
segmentLen, segmentThick, false, (segments & 0b00000001) != 0 ? colorOn : colorOff);
|
||||
// Segment B
|
||||
DrawDisplaySegment(new Vector2(position.X + segmentThick + segmentLen + segmentThick / 2.0f, position.Y + 2 * segmentThick + segmentLen / 2.0f - offsetYAdjust),
|
||||
segmentLen, segmentThick, true, (segments & 0b00000010) != 0 ? colorOn : colorOff);
|
||||
// Segment C
|
||||
DrawDisplaySegment(new Vector2(position.X + segmentThick + segmentLen + segmentThick / 2.0f, position.Y + 4 * segmentThick + segmentLen + segmentLen / 2.0f - 3 * offsetYAdjust),
|
||||
segmentLen, segmentThick, true, (segments & 0b00000100) != 0 ? colorOn : colorOff);
|
||||
// Segment D
|
||||
DrawDisplaySegment(new Vector2(position.X + segmentThick + segmentLen / 2.0f, position.Y + 5 * segmentThick + 2 * segmentLen - 4 * offsetYAdjust),
|
||||
segmentLen, segmentThick, false, (segments & 0b00001000) != 0 ? colorOn : colorOff);
|
||||
// Segment E
|
||||
DrawDisplaySegment(new Vector2(position.X + segmentThick / 2.0f, position.Y + 4 * segmentThick + segmentLen + segmentLen / 2.0f - 3 * offsetYAdjust),
|
||||
segmentLen, segmentThick, true, (segments & 0b00010000) != 0 ? colorOn : colorOff);
|
||||
// Segment F
|
||||
DrawDisplaySegment(new Vector2(position.X + segmentThick / 2.0f, position.Y + 2 * segmentThick + segmentLen / 2.0f - offsetYAdjust),
|
||||
segmentLen, segmentThick, true, (segments & 0b00100000) != 0 ? colorOn : colorOff);
|
||||
// Segment G
|
||||
DrawDisplaySegment(new Vector2(position.X + segmentThick + segmentLen / 2.0f, position.Y + 3 * segmentThick + segmentLen - 2 * offsetYAdjust),
|
||||
segmentLen, segmentThick, false, (segments & 0b01000000) != 0 ? colorOn : colorOff);
|
||||
}
|
||||
|
||||
// Draw one 7-segment display segment, horizontal or vertical
|
||||
private static void DrawDisplaySegment(Vector2 center, int length, int thick, bool vertical, Color color)
|
||||
{
|
||||
if (!vertical)
|
||||
{
|
||||
// Horizontal segment points
|
||||
/*
|
||||
3___________________________5
|
||||
/ \
|
||||
/1 x 6\
|
||||
\ /
|
||||
\2___________________________4/
|
||||
*/
|
||||
Vector2[] segmentPointsH = new Vector2[6]
|
||||
{
|
||||
new Vector2(center.X - length / 2.0f - thick / 2.0f, center.Y), // Point 1
|
||||
new Vector2(center.X - length / 2.0f, center.Y + thick / 2.0f), // Point 2
|
||||
new Vector2(center.X - length / 2.0f, center.Y - thick / 2.0f), // Point 3
|
||||
new Vector2(center.X + length / 2.0f, center.Y + thick / 2.0f), // Point 4
|
||||
new Vector2(center.X + length / 2.0f, center.Y - thick / 2.0f), // Point 5
|
||||
new Vector2(center.X + length / 2.0f + thick / 2.0f, center.Y), // Point 6
|
||||
};
|
||||
|
||||
DrawTriangleStrip(segmentPointsH, 6, color);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Vertical segment points
|
||||
Vector2[] segmentPointsV = new Vector2[6]
|
||||
{
|
||||
new Vector2(center.X, center.Y - length / 2.0f - thick / 2.0f), // Point 1
|
||||
new Vector2(center.X - thick / 2.0f, center.Y - length / 2.0f), // Point 2
|
||||
new Vector2(center.X + thick / 2.0f, center.Y - length / 2.0f), // Point 3
|
||||
new Vector2(center.X - thick / 2.0f, center.Y + length / 2.0f), // Point 4
|
||||
new Vector2(center.X + thick / 2.0f, center.Y + length / 2.0f), // Point 5
|
||||
new Vector2(center.X, center.Y + (float)length / 2 + thick / 2.0f), // Point 6
|
||||
};
|
||||
|
||||
DrawTriangleStrip(segmentPointsV, 6, color);
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - digital clock");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new DigitalClock();
|
||||
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;
|
||||
}
|
||||
}
|
||||
205
Examples/Shapes/DoublePendulum.cs
Normal file
205
Examples/Shapes/DoublePendulum.cs
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - double pendulum
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.5
|
||||
*
|
||||
* Example contributed by JoeCheong (@Joecheong2006) 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 JoeCheong (@Joecheong2006)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class DoublePendulum : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
// Constant for Simulation
|
||||
private const int SIMULATION_STEPS = 30;
|
||||
private const float G = 9.81f;
|
||||
|
||||
public string Name => "Shapes / Double Pendulum";
|
||||
|
||||
public string Title => "raylib [shapes] example - double pendulum";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.HighDpiWindow;
|
||||
|
||||
// Simulation Parameters
|
||||
private float l1, m1, theta1, w1;
|
||||
private float l2, m2, theta2, w2;
|
||||
private float lengthScaler;
|
||||
private float totalM;
|
||||
|
||||
private Vector2 previousPosition;
|
||||
|
||||
// Scale length
|
||||
private float L1;
|
||||
private float L2;
|
||||
|
||||
// Draw parameters
|
||||
private float lineThick, trailThick;
|
||||
private float fateAlpha;
|
||||
|
||||
// Create framebuffer
|
||||
private RenderTexture2D target;
|
||||
|
||||
// Calculate pendulum end point
|
||||
private static Vector2 CalculatePendulumEndPoint(float l, float theta)
|
||||
{
|
||||
return new(10 * l * MathF.Sin(theta), 10 * l * MathF.Cos(theta));
|
||||
}
|
||||
|
||||
// Calculate double pendulum end point
|
||||
private static Vector2 CalculateDoublePendulumEndPoint(float l1, float theta1, float l2, float theta2)
|
||||
{
|
||||
Vector2 endpoint1 = CalculatePendulumEndPoint(l1, theta1);
|
||||
Vector2 endpoint2 = CalculatePendulumEndPoint(l2, theta2);
|
||||
return new(endpoint1.X + endpoint2.X, endpoint1.Y + endpoint2.Y);
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Simulation Parameters
|
||||
l1 = 15.0f; m1 = 0.2f; theta1 = DEG2RAD * 170; w1 = 0;
|
||||
l2 = 15.0f; m2 = 0.1f; theta2 = DEG2RAD * 0; w2 = 0;
|
||||
lengthScaler = 0.1f;
|
||||
totalM = m1 + m2;
|
||||
|
||||
previousPosition = CalculateDoublePendulumEndPoint(l1, theta1, l2, theta2);
|
||||
previousPosition.X += ((float)screenWidth / 2);
|
||||
previousPosition.Y += ((float)screenHeight / 2 - 100);
|
||||
|
||||
// Scale length
|
||||
L1 = l1 * lengthScaler;
|
||||
L2 = l2 * lengthScaler;
|
||||
|
||||
// Draw parameters
|
||||
lineThick = 20; trailThick = 2;
|
||||
fateAlpha = 0.01f;
|
||||
|
||||
// Create framebuffer
|
||||
target = LoadRenderTexture(screenWidth, screenHeight);
|
||||
SetTextureFilter(target.Texture, TextureFilter.Bilinear);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
float dt = GetFrameTime();
|
||||
float step = dt / SIMULATION_STEPS, step2 = step * step;
|
||||
|
||||
// Update Physics - larger steps = better approximation
|
||||
for (int i = 0; i < SIMULATION_STEPS; i++)
|
||||
{
|
||||
float delta = theta1 - theta2;
|
||||
float sinD = MathF.Sin(delta), cosD = MathF.Cos(delta), cos2D = MathF.Cos(2 * delta);
|
||||
float ww1 = w1 * w1, ww2 = w2 * w2;
|
||||
|
||||
// Calculate a1
|
||||
float a1 = (-G * (2 * m1 + m2) * MathF.Sin(theta1)
|
||||
- m2 * G * MathF.Sin(theta1 - 2 * theta2)
|
||||
- 2 * sinD * m2 * (ww2 * L2 + ww1 * L1 * cosD))
|
||||
/ (L1 * (2 * m1 + m2 - m2 * cos2D));
|
||||
|
||||
// Calculate a2
|
||||
float a2 = (2 * sinD * (ww1 * L1 * totalM
|
||||
+ G * totalM * MathF.Cos(theta1)
|
||||
+ ww2 * L2 * m2 * cosD))
|
||||
/ (L2 * (2 * m1 + m2 - m2 * cos2D));
|
||||
|
||||
// Update thetas
|
||||
theta1 += w1 * step + 0.5f * a1 * step2;
|
||||
theta2 += w2 * step + 0.5f * a2 * step2;
|
||||
|
||||
// Update omegas
|
||||
w1 += a1 * step;
|
||||
w2 += a2 * step;
|
||||
}
|
||||
|
||||
// Calculate position
|
||||
Vector2 currentPosition = CalculateDoublePendulumEndPoint(l1, theta1, l2, theta2);
|
||||
currentPosition.X += (float)screenWidth / 2;
|
||||
currentPosition.Y += (float)screenHeight / 2 - 100;
|
||||
|
||||
// Draw to render texture
|
||||
BeginTextureMode(target);
|
||||
// Draw a transparent rectangle - smaller alpha = longer trails
|
||||
DrawRectangle(0, 0, screenWidth, screenHeight, Fade(Color.Black, fateAlpha));
|
||||
|
||||
// Draw trail
|
||||
DrawCircleV(previousPosition, trailThick, Color.Red);
|
||||
DrawLineEx(previousPosition, currentPosition, trailThick * 2, Color.Red);
|
||||
EndTextureMode();
|
||||
|
||||
// Update previous position
|
||||
previousPosition = currentPosition;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.Black);
|
||||
|
||||
// Draw trails texture
|
||||
DrawTextureRec(target.Texture, new Rectangle(0, 0, (float)target.Texture.Width, (float)-target.Texture.Height), new Vector2(0, 0), Color.White);
|
||||
|
||||
// Draw double pendulum
|
||||
DrawRectanglePro(new Rectangle(screenWidth / 2.0f, screenHeight / 2.0f - 100, 10 * l1, lineThick),
|
||||
new Vector2(0, lineThick * 0.5f), 90 - RAD2DEG * theta1, Color.RayWhite);
|
||||
|
||||
Vector2 endpoint1 = CalculatePendulumEndPoint(l1, theta1);
|
||||
DrawRectanglePro(new Rectangle(screenWidth / 2.0f + endpoint1.X, screenHeight / 2.0f - 100 + endpoint1.Y, 10 * l2, lineThick),
|
||||
new Vector2(0, lineThick * 0.5f), 90 - RAD2DEG * theta2, Color.RayWhite);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadRenderTexture(target);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.HighDpiWindow);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - double pendulum");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new DoublePendulum();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -75,11 +75,11 @@ public partial class DrawCircleSector : IExample
|
|||
|
||||
// Draw GUI controls
|
||||
//------------------------------------------------------------------------------
|
||||
/*GuiSliderBar(new Rectangle( 600, 40, 120, 20), "StartAngle", TextFormat("%.2f", startAngle), ref startAngle, 0, 720);
|
||||
GuiSliderBar(new Rectangle( 600, 70, 120, 20), "EndAngle", TextFormat("%.2f", endAngle), ref endAngle, 0, 720);
|
||||
GuiSliderBar(new Rectangle(600, 40, 120, 20), "StartAngle", $"{startAngle:F2}", ref startAngle, 0, 720);
|
||||
GuiSliderBar(new Rectangle(600, 70, 120, 20), "EndAngle", $"{endAngle:F2}", ref endAngle, 0, 720);
|
||||
|
||||
GuiSliderBar(new Rectangle( 600, 140, 120, 20), "Radius", TextFormat("%.2f", outerRadius), ref outerRadius, 0, 200);
|
||||
GuiSliderBar(new Rectangle( 600, 170, 120, 20), "Segments", TextFormat("%.2f", segments), ref segments, 0, 100);*/
|
||||
GuiSliderBar(new Rectangle(600, 140, 120, 20), "Radius", $"{outerRadius:F2}", ref outerRadius, 0, 200);
|
||||
GuiSliderBar(new Rectangle(600, 170, 120, 20), "Segments", $"{segments:F2}", ref segments, 0, 100);
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
minSegments = MathF.Truncate(MathF.Ceiling((endAngle - startAngle) / 90));
|
||||
|
|
@ -96,6 +96,28 @@ public partial class DrawCircleSector : IExample
|
|||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Minimal raygui-like widgets (plain raylib re-implementation)
|
||||
//----------------------------------------------------------------------------------
|
||||
private static void GuiSliderBar(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);
|
||||
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
|
|
|
|||
|
|
@ -86,15 +86,15 @@ public partial class DrawRectangleRounded : IExample
|
|||
|
||||
// Draw GUI controls
|
||||
//------------------------------------------------------------------------------
|
||||
/*GuiSliderBar(new Rectangle( 640, 40, 105, 20 ), "Width", TextFormat("%.2f", width), ref width, 0, (float)GetScreenWidth() - 300);
|
||||
GuiSliderBar(new Rectangle( 640, 70, 105, 20 ), "Height", TextFormat("%.2f", height), ref height, 0, (float)GetScreenHeight() - 50);
|
||||
GuiSliderBar(new Rectangle( 640, 140, 105, 20 ), "Roundness", TextFormat("%.2f", roundness), ref roundness, 0.0f, 1.0f);
|
||||
GuiSliderBar(new Rectangle( 640, 170, 105, 20 ), "Thickness", TextFormat("%.2f", lineThick), ref lineThick, 0, 20);
|
||||
GuiSliderBar(new Rectangle( 640, 240, 105, 20), "Segments", TextFormat("%.2f", segments), ref segments, 0, 60);
|
||||
GuiSliderBar(new Rectangle(640, 40, 105, 20), "Width", $"{width:F2}", ref width, 0, (float)GetScreenWidth() - 300);
|
||||
GuiSliderBar(new Rectangle(640, 70, 105, 20), "Height", $"{height:F2}", ref height, 0, (float)GetScreenHeight() - 50);
|
||||
GuiSliderBar(new Rectangle(640, 140, 105, 20), "Roundness", $"{roundness:F2}", ref roundness, 0.0f, 1.0f);
|
||||
GuiSliderBar(new Rectangle(640, 170, 105, 20), "Thickness", $"{lineThick:F2}", ref lineThick, 0, 20);
|
||||
GuiSliderBar(new Rectangle(640, 240, 105, 20), "Segments", $"{segments:F2}", ref segments, 0, 60);
|
||||
|
||||
GuiCheckBox(new Rectangle( 640, 320, 20, 20 ), "DrawRoundedRect", ref drawRoundedRect);
|
||||
GuiCheckBox(new Rectangle( 640, 350, 20, 20 ), "DrawRoundedLines", ref drawRoundedLines);
|
||||
GuiCheckBox(new Rectangle( 640, 380, 20, 20), "DrawRect", ref drawRect);*/
|
||||
GuiCheckBox(new Rectangle(640, 320, 20, 20), "DrawRoundedRect", ref drawRoundedRect);
|
||||
GuiCheckBox(new Rectangle(640, 350, 20, 20), "DrawRoundedLines", ref drawRoundedLines);
|
||||
GuiCheckBox(new Rectangle(640, 380, 20, 20), "DrawRect", ref drawRect);
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
var text = $"MODE: {((segments >= 4) ? "MANUAL" : "AUTO")}";
|
||||
|
|
@ -109,6 +109,39 @@ public partial class DrawRectangleRounded : IExample
|
|||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Minimal raygui-like widgets (plain raylib re-implementation)
|
||||
//----------------------------------------------------------------------------------
|
||||
private static void GuiSliderBar(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);
|
||||
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
|
|
|
|||
|
|
@ -112,17 +112,17 @@ public partial class DrawRing : IExample
|
|||
|
||||
// Draw GUI controls
|
||||
//------------------------------------------------------------------------------
|
||||
/*GuiSliderBar(new Rectangle( 600, 40, 120, 20 ), "StartAngle", TextFormat("%.2f", startAngle), ref startAngle, -450, 450);
|
||||
GuiSliderBar(new Rectangle( 600, 70, 120, 20 ), "EndAngle", TextFormat("%.2f", endAngle), ref endAngle, -450, 450);
|
||||
GuiSliderBar(new Rectangle(600, 40, 120, 20), "StartAngle", $"{startAngle:F2}", ref startAngle, -450, 450);
|
||||
GuiSliderBar(new Rectangle(600, 70, 120, 20), "EndAngle", $"{endAngle:F2}", ref endAngle, -450, 450);
|
||||
|
||||
GuiSliderBar(new Rectangle( 600, 140, 120, 20 ), "InnerRadius", TextFormat("%.2f", innerRadius), ref innerRadius, 0, 100);
|
||||
GuiSliderBar(new Rectangle( 600, 170, 120, 20 ), "OuterRadius", TextFormat("%.2f", outerRadius), ref outerRadius, 0, 200);
|
||||
GuiSliderBar(new Rectangle(600, 140, 120, 20), "InnerRadius", $"{innerRadius:F2}", ref innerRadius, 0, 100);
|
||||
GuiSliderBar(new Rectangle(600, 170, 120, 20), "OuterRadius", $"{outerRadius:F2}", ref outerRadius, 0, 200);
|
||||
|
||||
GuiSliderBar(new Rectangle( 600, 240, 120, 20 ), "Segments", TextFormat("%.2f", segments), ref segments, 0, 100);
|
||||
GuiSliderBar(new Rectangle(600, 240, 120, 20), "Segments", $"{segments:F2}", ref segments, 0, 100);
|
||||
|
||||
GuiCheckBox(new Rectangle( 600, 320, 20, 20 ), "Draw Ring", ref drawRing);
|
||||
GuiCheckBox(new Rectangle( 600, 350, 20, 20 ), "Draw RingLines", ref drawRingLines);
|
||||
GuiCheckBox(new Rectangle( 600, 380, 20, 20 ), "Draw CircleLines", ref drawCircleLines);*/
|
||||
GuiCheckBox(new Rectangle(600, 320, 20, 20), "Draw Ring", ref drawRing);
|
||||
GuiCheckBox(new Rectangle(600, 350, 20, 20), "Draw RingLines", ref drawRingLines);
|
||||
GuiCheckBox(new Rectangle(600, 380, 20, 20), "Draw CircleLines", ref drawCircleLines);
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
var minSegments = (int)MathF.Ceiling((endAngle - startAngle) / 90);
|
||||
|
|
@ -139,6 +139,39 @@ public partial class DrawRing : IExample
|
|||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Minimal raygui-like widgets (plain raylib re-implementation)
|
||||
//----------------------------------------------------------------------------------
|
||||
private static void GuiSliderBar(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);
|
||||
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
|
|
|
|||
230
Examples/Shapes/EasingsTestbed.cs
Normal file
230
Examples/Shapes/EasingsTestbed.cs
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - easings testbed
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 2.5, last time updated with raylib 2.5
|
||||
*
|
||||
* Example contributed by Juan Miguel López (@flashback-fx) 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) 2019-2025 Juan Miguel López (@flashback-fx) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Examples.Shared;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class EasingsTestbed : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int FONT_SIZE = 20;
|
||||
|
||||
private const float D_STEP = 20.0f;
|
||||
private const float D_STEP_FINE = 2.0f;
|
||||
private const float D_MIN = 1.0f;
|
||||
private const float D_MAX = 10000.0f;
|
||||
|
||||
public string Name => "Shapes / Easings Testbed";
|
||||
|
||||
public string Title => "raylib [shapes] example - easings testbed";
|
||||
|
||||
// Easing types
|
||||
private const int EASE_LINEAR_NONE = 0;
|
||||
private const int NUM_EASING_TYPES = 27;
|
||||
private const int EASING_NONE = NUM_EASING_TYPES;
|
||||
|
||||
// NoEase function, used when "no easing" is selected for any axis
|
||||
// It just ignores all parameters besides b
|
||||
private static float NoEase(float t, float b, float c, float d)
|
||||
{
|
||||
// Hack to avoid compiler warning (about unused variables)
|
||||
float burn = t + b + c + d;
|
||||
d += burn;
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
// Easing functions reference data
|
||||
private string[] easingNames;
|
||||
private Func<float, float, float, float, float>[] easingFuncs;
|
||||
|
||||
private Vector2 ballPosition;
|
||||
private float t; // Current time (in any unit measure, but same unit as duration)
|
||||
private float d; // Total time it should take to complete (duration)
|
||||
private bool paused;
|
||||
private bool boundedT; // If true, t will stop when d >= td, otherwise t will keep adding td to its value every loop
|
||||
|
||||
private int easingX; // Easing selected for x axis
|
||||
private int easingY; // Easing selected for y axis
|
||||
|
||||
public void Init()
|
||||
{
|
||||
easingNames = new string[]
|
||||
{
|
||||
"EaseLinearNone", "EaseLinearIn", "EaseLinearOut", "EaseLinearInOut",
|
||||
"EaseSineIn", "EaseSineOut", "EaseSineInOut",
|
||||
"EaseCircIn", "EaseCircOut", "EaseCircInOut",
|
||||
"EaseCubicIn", "EaseCubicOut", "EaseCubicInOut",
|
||||
"EaseQuadIn", "EaseQuadOut", "EaseQuadInOut",
|
||||
"EaseExpoIn", "EaseExpoOut", "EaseExpoInOut",
|
||||
"EaseBackIn", "EaseBackOut", "EaseBackInOut",
|
||||
"EaseBounceOut", "EaseBounceIn", "EaseBounceInOut",
|
||||
"EaseElasticIn", "EaseElasticOut", "EaseElasticInOut",
|
||||
"None",
|
||||
};
|
||||
|
||||
easingFuncs = new Func<float, float, float, float, float>[]
|
||||
{
|
||||
Easings.EaseLinearNone, Easings.EaseLinearIn, Easings.EaseLinearOut, Easings.EaseLinearInOut,
|
||||
Easings.EaseSineIn, Easings.EaseSineOut, Easings.EaseSineInOut,
|
||||
Easings.EaseCircIn, Easings.EaseCircOut, Easings.EaseCircInOut,
|
||||
Easings.EaseCubicIn, Easings.EaseCubicOut, Easings.EaseCubicInOut,
|
||||
Easings.EaseQuadIn, Easings.EaseQuadOut, Easings.EaseQuadInOut,
|
||||
Easings.EaseExpoIn, Easings.EaseExpoOut, Easings.EaseExpoInOut,
|
||||
Easings.EaseBackIn, Easings.EaseBackOut, Easings.EaseBackInOut,
|
||||
Easings.EaseBounceOut, Easings.EaseBounceIn, Easings.EaseBounceInOut,
|
||||
Easings.EaseElasticIn, Easings.EaseElasticOut, Easings.EaseElasticInOut,
|
||||
NoEase,
|
||||
};
|
||||
|
||||
ballPosition = new Vector2(100.0f, 100.0f);
|
||||
|
||||
t = 0.0f;
|
||||
d = 300.0f;
|
||||
paused = true;
|
||||
boundedT = true;
|
||||
|
||||
easingX = EASING_NONE;
|
||||
easingY = EASING_NONE;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyPressed(KeyboardKey.T)) boundedT = !boundedT;
|
||||
|
||||
// Choose easing for the X axis
|
||||
if (IsKeyPressed(KeyboardKey.Right))
|
||||
{
|
||||
easingX++;
|
||||
|
||||
if (easingX > EASING_NONE) easingX = 0;
|
||||
}
|
||||
else if (IsKeyPressed(KeyboardKey.Left))
|
||||
{
|
||||
if (easingX == 0) easingX = EASING_NONE;
|
||||
else easingX--;
|
||||
}
|
||||
|
||||
// Choose easing for the Y axis
|
||||
if (IsKeyPressed(KeyboardKey.Down))
|
||||
{
|
||||
easingY++;
|
||||
|
||||
if (easingY > EASING_NONE) easingY = 0;
|
||||
}
|
||||
else if (IsKeyPressed(KeyboardKey.Up))
|
||||
{
|
||||
if (easingY == 0) easingY = EASING_NONE;
|
||||
else easingY--;
|
||||
}
|
||||
|
||||
// Change d (duration) value
|
||||
if (IsKeyPressed(KeyboardKey.W) && (d < D_MAX - D_STEP)) d += D_STEP;
|
||||
else if (IsKeyPressed(KeyboardKey.Q) && (d > D_MIN + D_STEP)) d -= D_STEP;
|
||||
|
||||
if (IsKeyDown(KeyboardKey.S) && (d < D_MAX - D_STEP_FINE)) d += D_STEP_FINE;
|
||||
else if (IsKeyDown(KeyboardKey.A) && (d > D_MIN + D_STEP_FINE)) d -= D_STEP_FINE;
|
||||
|
||||
// Play, pause and restart controls
|
||||
if (IsKeyPressed(KeyboardKey.Space) || IsKeyPressed(KeyboardKey.T) ||
|
||||
IsKeyPressed(KeyboardKey.Right) || IsKeyPressed(KeyboardKey.Left) ||
|
||||
IsKeyPressed(KeyboardKey.Down) || IsKeyPressed(KeyboardKey.Up) ||
|
||||
IsKeyPressed(KeyboardKey.W) || IsKeyPressed(KeyboardKey.Q) ||
|
||||
IsKeyDown(KeyboardKey.S) || IsKeyDown(KeyboardKey.A) ||
|
||||
(IsKeyPressed(KeyboardKey.Enter) && (boundedT == true) && (t >= d)))
|
||||
{
|
||||
t = 0.0f;
|
||||
ballPosition.X = 100.0f;
|
||||
ballPosition.Y = 100.0f;
|
||||
paused = true;
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Enter)) paused = !paused;
|
||||
|
||||
// Movement computation
|
||||
if (!paused && ((boundedT && t < d) || !boundedT))
|
||||
{
|
||||
ballPosition.X = easingFuncs[easingX](t, 100.0f, 700.0f - 170.0f, d);
|
||||
ballPosition.Y = easingFuncs[easingY](t, 100.0f, 400.0f - 170.0f, d);
|
||||
t += 1.0f;
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw information text
|
||||
DrawText($"Easing x: {easingNames[easingX]}", 20, FONT_SIZE, FONT_SIZE, Color.LightGray);
|
||||
DrawText($"Easing y: {easingNames[easingY]}", 20, FONT_SIZE * 2, FONT_SIZE, Color.LightGray);
|
||||
DrawText($"t ({(boundedT == true ? 'b' : 'u')}) = {t:F2} d = {d:F2}", 20, FONT_SIZE * 3, FONT_SIZE, Color.LightGray);
|
||||
|
||||
// Draw instructions text
|
||||
DrawText("Use ENTER to play or pause movement, use SPACE to restart", 20, GetScreenHeight() - FONT_SIZE * 2, FONT_SIZE, Color.LightGray);
|
||||
DrawText("Use Q and W or A and S keys to change duration", 20, GetScreenHeight() - FONT_SIZE * 3, FONT_SIZE, Color.LightGray);
|
||||
DrawText("Use LEFT or RIGHT keys to choose easing for the x axis", 20, GetScreenHeight() - FONT_SIZE * 4, FONT_SIZE, Color.LightGray);
|
||||
DrawText("Use UP or DOWN keys to choose easing for the y axis", 20, GetScreenHeight() - FONT_SIZE * 5, FONT_SIZE, Color.LightGray);
|
||||
|
||||
// Draw ball
|
||||
DrawCircleV(ballPosition, 16.0f, Color.Maroon);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - easings testbed");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new EasingsTestbed();
|
||||
game.Init();
|
||||
|
||||
// Main game loop
|
||||
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||
{
|
||||
game.Update();
|
||||
}
|
||||
|
||||
game.Unload();
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
CloseWindow();
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
195
Examples/Shapes/EllipseCollision.cs
Normal file
195
Examples/Shapes/EllipseCollision.cs
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - ellipse collision
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.5
|
||||
*
|
||||
* Example contributed by Ziya (@Monjaris)
|
||||
*
|
||||
* 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 Ziya (@Monjaris)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class EllipseCollision : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Shapes / Ellipse Collision";
|
||||
|
||||
public string Title => "raylib [shapes] example - collision ellipses";
|
||||
|
||||
private Vector2 ellipseACenter;
|
||||
private float ellipseARx;
|
||||
private float ellipseARy;
|
||||
|
||||
private Vector2 ellipseBCenter;
|
||||
private float ellipseBRx;
|
||||
private float ellipseBRy;
|
||||
|
||||
// 0 = controlling A, 1 = controlling B
|
||||
private int controlled;
|
||||
|
||||
// Check if point is inside ellipse
|
||||
private static bool CheckCollisionPointEllipse(Vector2 point, Vector2 center, float rx, float ry)
|
||||
{
|
||||
float dx = (point.X - center.X) / rx;
|
||||
float dy = (point.Y - center.Y) / ry;
|
||||
return (dx * dx + dy * dy) <= 1.0f;
|
||||
}
|
||||
|
||||
// Check if two ellipses collide
|
||||
// Uses radial boundary distance in the direction between centers — scales correctly with radii
|
||||
private static bool CheckCollisionEllipses(Vector2 c1, float rx1, float ry1, Vector2 c2, float rx2, float ry2)
|
||||
{
|
||||
float dx = c2.X - c1.X;
|
||||
float dy = c2.Y - c1.Y;
|
||||
float dist = MathF.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Ellipses are on top of each other
|
||||
if (dist == 0.0f)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
float theta = MathF.Atan2(dy, dx);
|
||||
float cosT = MathF.Cos(theta);
|
||||
float sinT = MathF.Sin(theta);
|
||||
|
||||
// Radial distance from center to ellipse boundary in direction theta
|
||||
// r(theta) = (rx * ry) / sqrt((ry*cos)^2 + (rx*sin)^2)
|
||||
float r1 = (rx1 * ry1) / MathF.Sqrt((ry1 * cosT) * (ry1 * cosT) + (rx1 * sinT) * (rx1 * sinT));
|
||||
float r2 = (rx2 * ry2) / MathF.Sqrt((ry2 * cosT) * (ry2 * cosT) + (rx2 * sinT) * (rx2 * sinT));
|
||||
|
||||
return dist <= (r1 + r2);
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
ellipseACenter = new((float)screenWidth / 4, (float)screenHeight / 2);
|
||||
ellipseARx = 120.0f;
|
||||
ellipseARy = 70.0f;
|
||||
|
||||
ellipseBCenter = new((float)screenWidth * 3 / 4, (float)screenHeight / 2);
|
||||
ellipseBRx = 90.0f;
|
||||
ellipseBRy = 140.0f;
|
||||
|
||||
// 0 = controlling A, 1 = controlling B
|
||||
controlled = 0;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyPressed(KeyboardKey.A))
|
||||
{
|
||||
controlled = 0;
|
||||
}
|
||||
if (IsKeyPressed(KeyboardKey.B))
|
||||
{
|
||||
controlled = 1;
|
||||
}
|
||||
|
||||
if (controlled == 0)
|
||||
{
|
||||
ellipseACenter = GetMousePosition();
|
||||
}
|
||||
else
|
||||
{
|
||||
ellipseBCenter = GetMousePosition();
|
||||
}
|
||||
|
||||
bool ellipsesCollide = CheckCollisionEllipses(
|
||||
ellipseACenter, ellipseARx, ellipseARy,
|
||||
ellipseBCenter, ellipseBRx, ellipseBRy
|
||||
);
|
||||
|
||||
bool mouseInA = CheckCollisionPointEllipse(GetMousePosition(), ellipseACenter, ellipseARx, ellipseARy);
|
||||
bool mouseInB = CheckCollisionPointEllipse(GetMousePosition(), ellipseBCenter, ellipseBRx, ellipseBRy);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
DrawEllipse((int)ellipseACenter.X, (int)ellipseACenter.Y, ellipseARx, ellipseARy, ellipsesCollide ? Color.Red : Color.Blue);
|
||||
|
||||
DrawEllipse((int)ellipseBCenter.X, (int)ellipseBCenter.Y, ellipseBRx, ellipseBRy, ellipsesCollide ? Color.Red : Color.Green);
|
||||
|
||||
DrawEllipseLines((int)ellipseACenter.X, (int)ellipseACenter.Y, ellipseARx, ellipseARy, Color.White);
|
||||
|
||||
DrawEllipseLines((int)ellipseBCenter.X, (int)ellipseBCenter.Y, ellipseBRx, ellipseBRy, Color.White);
|
||||
|
||||
DrawCircleV(ellipseACenter, 4, Color.White);
|
||||
DrawCircleV(ellipseBCenter, 4, Color.White);
|
||||
|
||||
if (ellipsesCollide)
|
||||
{
|
||||
DrawText("ELLIPSES COLLIDE", screenWidth / 2 - 120, 40, 28, Color.Red);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawText("NO COLLISION", screenWidth / 2 - 80, 40, 28, Color.DarkGray);
|
||||
}
|
||||
|
||||
DrawText(controlled == 0 ? "Controlling: A" : "Controlling: B", 20, screenHeight - 40, 20, Color.Yellow);
|
||||
|
||||
if (mouseInA && controlled != 0)
|
||||
{
|
||||
DrawText("Mouse inside ellipse A", 20, screenHeight - 70, 20, Color.Blue);
|
||||
}
|
||||
if (mouseInB && controlled != 1)
|
||||
{
|
||||
DrawText("Mouse inside ellipse B", 20, screenHeight - 70, 20, Color.Green);
|
||||
}
|
||||
|
||||
DrawText("Press [A] or [B] to switch control", 20, 20, 20, Color.Gray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - collision ellipses");
|
||||
SetTargetFPS(60);
|
||||
|
||||
var game = new EllipseCollision();
|
||||
game.Init();
|
||||
|
||||
// Main game loop
|
||||
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||
{
|
||||
game.Update();
|
||||
}
|
||||
|
||||
game.Unload();
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
CloseWindow();
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
270
Examples/Shapes/HilbertCurve.cs
Normal file
270
Examples/Shapes/HilbertCurve.cs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* 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)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
224
Examples/Shapes/Kaleidoscope.cs
Normal file
224
Examples/Shapes/Kaleidoscope.cs
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - kaleidoscope
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.6
|
||||
*
|
||||
* Example contributed by Hugo ARNAL (@hugoarnal) 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 Hugo ARNAL (@hugoarnal) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class Kaleidoscope : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MAX_DRAW_LINES = 8192;
|
||||
|
||||
// Line data type
|
||||
private struct Line
|
||||
{
|
||||
public Vector2 Start;
|
||||
public Vector2 End;
|
||||
}
|
||||
|
||||
public string Name => "Shapes / Kaleidoscope";
|
||||
|
||||
public string Title => "raylib [shapes] example - kaleidoscope";
|
||||
|
||||
public int TargetFps => 20;
|
||||
|
||||
// Lines array as a global static variable to be stored
|
||||
// in heap and avoid potential stack overflow (on Web platform)
|
||||
private Line[] lines;
|
||||
|
||||
// Line drawing properties
|
||||
private int symmetry;
|
||||
private float angle;
|
||||
private float thickness;
|
||||
private Rectangle resetButtonRec;
|
||||
private Rectangle backButtonRec;
|
||||
private Rectangle nextButtonRec;
|
||||
private Vector2 mousePos;
|
||||
private Vector2 prevMousePos;
|
||||
private Vector2 scaleVector;
|
||||
private Vector2 offset;
|
||||
|
||||
private Camera2D camera;
|
||||
|
||||
private int currentLineCounter;
|
||||
private int totalLineCounter;
|
||||
private bool resetButtonClicked;
|
||||
private bool backButtonClicked;
|
||||
private bool nextButtonClicked;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
lines = new Line[MAX_DRAW_LINES];
|
||||
|
||||
// Line drawing properties
|
||||
symmetry = 6;
|
||||
angle = 360.0f / (float)symmetry;
|
||||
thickness = 3.0f;
|
||||
resetButtonRec = new(screenWidth - 55.0f, 5.0f, 50, 25);
|
||||
backButtonRec = new(screenWidth - 55.0f, screenHeight - 30.0f, 25, 25);
|
||||
nextButtonRec = new(screenWidth - 30.0f, screenHeight - 30.0f, 25, 25);
|
||||
mousePos = new(0, 0);
|
||||
prevMousePos = new(0, 0);
|
||||
scaleVector = new(1.0f, -1.0f);
|
||||
offset = new((float)screenWidth / 2.0f, (float)screenHeight / 2.0f);
|
||||
|
||||
camera = new();
|
||||
camera.Target = new(0, 0);
|
||||
camera.Offset = offset;
|
||||
camera.Rotation = 0.0f;
|
||||
camera.Zoom = 1.0f;
|
||||
|
||||
currentLineCounter = 0;
|
||||
totalLineCounter = 0;
|
||||
resetButtonClicked = false;
|
||||
backButtonClicked = false;
|
||||
nextButtonClicked = false;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
prevMousePos = mousePos;
|
||||
mousePos = GetMousePosition();
|
||||
|
||||
Vector2 lineStart = Vector2Subtract(mousePos, offset);
|
||||
Vector2 lineEnd = Vector2Subtract(prevMousePos, offset);
|
||||
|
||||
if (
|
||||
IsMouseButtonDown(MouseButton.Left)
|
||||
&& !CheckCollisionPointRec(mousePos, resetButtonRec)
|
||||
&& !CheckCollisionPointRec(mousePos, backButtonRec)
|
||||
&& !CheckCollisionPointRec(mousePos, nextButtonRec)
|
||||
)
|
||||
{
|
||||
for (int s = 0; (s < symmetry) && (totalLineCounter < (MAX_DRAW_LINES - 1)); s++)
|
||||
{
|
||||
lineStart = Vector2Rotate(lineStart, angle * DEG2RAD);
|
||||
lineEnd = Vector2Rotate(lineEnd, angle * DEG2RAD);
|
||||
|
||||
// Store mouse line
|
||||
lines[totalLineCounter].Start = lineStart;
|
||||
lines[totalLineCounter].End = lineEnd;
|
||||
|
||||
// Store reflective line
|
||||
lines[totalLineCounter + 1].Start = Vector2Multiply(lineStart, scaleVector);
|
||||
lines[totalLineCounter + 1].End = Vector2Multiply(lineEnd, scaleVector);
|
||||
|
||||
totalLineCounter += 2;
|
||||
currentLineCounter = totalLineCounter;
|
||||
}
|
||||
}
|
||||
|
||||
if (resetButtonClicked)
|
||||
{
|
||||
Array.Clear(lines, 0, MAX_DRAW_LINES);
|
||||
currentLineCounter = 0;
|
||||
totalLineCounter = 0;
|
||||
}
|
||||
|
||||
if (backButtonClicked && (currentLineCounter > 0))
|
||||
{
|
||||
currentLineCounter -= 1;
|
||||
}
|
||||
|
||||
if (nextButtonClicked && (currentLineCounter < MAX_DRAW_LINES) && ((currentLineCounter + 1) <= totalLineCounter))
|
||||
{
|
||||
currentLineCounter += 1;
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
BeginMode2D(camera);
|
||||
|
||||
for (int s = 0; s < symmetry; s++)
|
||||
{
|
||||
for (int i = 0; i < currentLineCounter; i += 2)
|
||||
{
|
||||
DrawLineEx(lines[i].Start, lines[i].End, thickness, Color.Black);
|
||||
DrawLineEx(lines[i + 1].Start, lines[i + 1].End, thickness, Color.Black);
|
||||
}
|
||||
}
|
||||
|
||||
EndMode2D();
|
||||
|
||||
// NOTE: raygui is not bound in raylib-cs, so the on-screen back/next/reset
|
||||
// controls are unavailable; drawing with the mouse still works.
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
if ((currentLineCounter - 1) < 0) GuiDisable();
|
||||
|
||||
backButtonClicked = GuiButton(backButtonRec, "<");
|
||||
GuiEnable();
|
||||
|
||||
if ((currentLineCounter + 1) > totalLineCounter) GuiDisable();
|
||||
|
||||
nextButtonClicked = GuiButton(nextButtonRec, ">");
|
||||
GuiEnable();
|
||||
resetButtonClicked = GuiButton(resetButtonRec, "Reset");
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
DrawText($"LINES: {currentLineCounter}/{MAX_DRAW_LINES}", 10, screenHeight - 30, 20, Color.Maroon);
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - kaleidoscope");
|
||||
|
||||
SetTargetFPS(20);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new Kaleidoscope();
|
||||
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;
|
||||
}
|
||||
}
|
||||
173
Examples/Shapes/LinesDrawing.cs
Normal file
173
Examples/Shapes/LinesDrawing.cs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - lines drawing
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 5.6
|
||||
*
|
||||
* Example contributed by Robin (@RobinsAviary) 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 Robin (@RobinsAviary)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class LinesDrawing : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Shapes / Lines Drawing";
|
||||
|
||||
public string Title => "raylib [shapes] example - lines drawing";
|
||||
|
||||
// Hint text that shows before you click the screen
|
||||
private bool startText;
|
||||
|
||||
// The mouse's position on the previous frame
|
||||
private Vector2 mousePositionPrevious;
|
||||
|
||||
// The canvas to draw lines on
|
||||
private RenderTexture2D canvas;
|
||||
|
||||
// The line's thickness
|
||||
private float lineThickness;
|
||||
// The lines hue (in HSV, from 0-360)
|
||||
private float lineHue;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Hint text that shows before you click the screen
|
||||
startText = true;
|
||||
|
||||
// The mouse's position on the previous frame
|
||||
mousePositionPrevious = GetMousePosition();
|
||||
|
||||
// The canvas to draw lines on
|
||||
canvas = LoadRenderTexture(screenWidth, screenHeight);
|
||||
|
||||
// The line's thickness
|
||||
lineThickness = 8.0f;
|
||||
// The lines hue (in HSV, from 0-360)
|
||||
lineHue = 0.0f;
|
||||
|
||||
// Clear the canvas to the background color
|
||||
BeginTextureMode(canvas);
|
||||
ClearBackground(Color.RayWhite);
|
||||
EndTextureMode();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Disable the hint text once the user clicks
|
||||
if (IsMouseButtonPressed(MouseButton.Left) && startText) startText = false;
|
||||
|
||||
// Clear the canvas when the user middle-clicks
|
||||
if (IsMouseButtonPressed(MouseButton.Middle))
|
||||
{
|
||||
BeginTextureMode(canvas);
|
||||
ClearBackground(Color.RayWhite);
|
||||
EndTextureMode();
|
||||
}
|
||||
|
||||
// Store whether the left and right buttons are down
|
||||
bool leftButtonDown = IsMouseButtonDown(MouseButton.Left);
|
||||
bool rightButtonDown = IsMouseButtonDown(MouseButton.Right);
|
||||
|
||||
if (leftButtonDown || rightButtonDown)
|
||||
{
|
||||
// The color for the line
|
||||
Color drawColor = Color.White;
|
||||
|
||||
if (leftButtonDown)
|
||||
{
|
||||
// Increase the hue value by the distance our cursor has moved since the last frame (divided by 3)
|
||||
lineHue += Vector2Distance(mousePositionPrevious, GetMousePosition())/3.0f;
|
||||
|
||||
// While the hue is >=360, subtract it to bring it down into the range 0-360
|
||||
// This is more visually accurate than resetting to zero
|
||||
while (lineHue >= 360.0f) lineHue -= 360.0f;
|
||||
|
||||
// Create the final color
|
||||
drawColor = ColorFromHSV(lineHue, 1.0f, 1.0f);
|
||||
}
|
||||
else if (rightButtonDown) drawColor = Color.RayWhite; // Use the background color as an "eraser"
|
||||
|
||||
// Draw the line onto the canvas
|
||||
BeginTextureMode(canvas);
|
||||
// Circles act as "caps", smoothing corners
|
||||
DrawCircleV(mousePositionPrevious, lineThickness/2.0f, drawColor);
|
||||
DrawCircleV(GetMousePosition(), lineThickness/2.0f, drawColor);
|
||||
DrawLineEx(mousePositionPrevious, GetMousePosition(), lineThickness, drawColor);
|
||||
EndTextureMode();
|
||||
}
|
||||
|
||||
// Update line thickness based on mousewheel
|
||||
lineThickness += GetMouseWheelMove();
|
||||
lineThickness = Clamp(lineThickness, 1.0f, 500.0f);
|
||||
|
||||
// Update mouse's previous position
|
||||
mousePositionPrevious = GetMousePosition();
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
// Draw the render texture to the screen, flipped vertically to make it appear top-side up
|
||||
DrawTextureRec(canvas.Texture, new Rectangle(0.0f, 0.0f, (float)canvas.Texture.Width, (float)-canvas.Texture.Height), Vector2Zero(), Color.White);
|
||||
|
||||
// Draw the preview circle
|
||||
if (!leftButtonDown) DrawCircleLinesV(GetMousePosition(), lineThickness/2.0f, new Color(127, 127, 127, 127));
|
||||
|
||||
// Draw the hint text
|
||||
if (startText) DrawText("try clicking and dragging!", 275, 215, 20, Color.LightGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadRenderTexture(canvas); // Unload the canvas render texture
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - lines drawing");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new LinesDrawing();
|
||||
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;
|
||||
}
|
||||
}
|
||||
141
Examples/Shapes/MathAngleRotation.cs
Normal file
141
Examples/Shapes/MathAngleRotation.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - math angle rotation
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 5.6
|
||||
*
|
||||
* Example contributed by Kris (@krispy-snacc) 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 Kris (@krispy-snacc)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class MathAngleRotation : IExample
|
||||
{
|
||||
private const int screenWidth = 720;
|
||||
private const int screenHeight = 400;
|
||||
|
||||
public string Name => "Shapes / Math Angle Rotation";
|
||||
|
||||
public string Title => "raylib [shapes] example - math angle rotation";
|
||||
|
||||
public int Width => screenWidth;
|
||||
|
||||
public int Height => screenHeight;
|
||||
|
||||
private Vector2 center;
|
||||
private const float lineLength = 150.0f;
|
||||
|
||||
// Predefined angles for fixed lines
|
||||
private int[] angles;
|
||||
private int numAngles;
|
||||
|
||||
private float totalAngle; // Animated rotation angle
|
||||
|
||||
public void Init()
|
||||
{
|
||||
center = new Vector2(screenWidth/2.0f, screenHeight/2.0f);
|
||||
|
||||
// Predefined angles for fixed lines
|
||||
angles = new[] { 0, 30, 60, 90 };
|
||||
numAngles = angles.Length;
|
||||
|
||||
totalAngle = 0.0f; // Animated rotation angle
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
totalAngle += 1.0f; // degrees per frame
|
||||
if (totalAngle >= 360.0f) totalAngle -= 360.0f;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.White);
|
||||
|
||||
DrawText("Fixed angles + rotating line", 10, 10, 20, Color.LightGray);
|
||||
|
||||
// Draw fixed-angle lines with colorful gradient
|
||||
for (int i = 0; i < numAngles; i++)
|
||||
{
|
||||
float rad = angles[i]*DEG2RAD;
|
||||
Vector2 end = new Vector2(center.X + MathF.Cos(rad)*lineLength,
|
||||
center.Y + MathF.Sin(rad)*lineLength);
|
||||
|
||||
// Gradient color from green → cyan → blue → magenta
|
||||
Color col;
|
||||
switch(i)
|
||||
{
|
||||
case 0: col = Color.Green; break;
|
||||
case 1: col = Color.Orange; break;
|
||||
case 2: col = Color.Blue; break;
|
||||
case 3: col = Color.Magenta; break;
|
||||
default: col = Color.White; break;
|
||||
}
|
||||
|
||||
DrawLineEx(center, end, 5.0f, col);
|
||||
|
||||
// Draw angle label slightly offset along the line
|
||||
Vector2 textPos = new Vector2(center.X + MathF.Cos(rad)*(lineLength + 20),
|
||||
center.Y + MathF.Sin(rad)*(lineLength + 20));
|
||||
DrawText($"{angles[i]}°", (int)textPos.X, (int)textPos.Y, 20, col);
|
||||
}
|
||||
|
||||
// Draw animated rotating line with changing color
|
||||
float animRad = totalAngle*DEG2RAD;
|
||||
Vector2 animEnd = new Vector2(center.X + MathF.Cos(animRad)*lineLength,
|
||||
center.Y + MathF.Sin(animRad)*lineLength);
|
||||
|
||||
// Cycle through HSV colors for animated line
|
||||
Color animCol = ColorFromHSV(totalAngle % 360.0f, 0.8f, 0.9f);
|
||||
DrawLineEx(center, animEnd, 5.0f, animCol);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - math angle rotation");
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new MathAngleRotation();
|
||||
game.Init();
|
||||
|
||||
// Main game loop
|
||||
while (!WindowShouldClose())
|
||||
{
|
||||
game.Update();
|
||||
}
|
||||
|
||||
game.Unload();
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
CloseWindow();
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
212
Examples/Shapes/MathSineCosine.cs
Normal file
212
Examples/Shapes/MathSineCosine.cs
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - math sine cosine
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Jopestpe (@jopestpe) 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 Jopestpe (@jopestpe)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class MathSineCosine : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
// Wave points for sine/cosine visualization
|
||||
private const int WAVE_POINTS = 36;
|
||||
|
||||
public string Name => "Shapes / Math Sine Cosine";
|
||||
|
||||
public string Title => "raylib [shapes] example - math sine cosine";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
private Vector2[] sinePoints;
|
||||
private Vector2[] cosPoints;
|
||||
private Vector2 center;
|
||||
private Rectangle start;
|
||||
private float radius;
|
||||
private float angle;
|
||||
private bool pause;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
sinePoints = new Vector2[WAVE_POINTS];
|
||||
cosPoints = new Vector2[WAVE_POINTS];
|
||||
center = new((screenWidth / 2.0f) - 30.0f, screenHeight / 2.0f);
|
||||
start = new(20.0f, screenHeight - 120.0f, 200.0f, 100.0f);
|
||||
radius = 130.0f;
|
||||
angle = 0.0f;
|
||||
pause = false;
|
||||
|
||||
for (int i = 0; i < WAVE_POINTS; i++)
|
||||
{
|
||||
float t = i / (float)(WAVE_POINTS - 1);
|
||||
float currentAngle = t * 360.0f * DEG2RAD;
|
||||
sinePoints[i] = new(start.X + t * start.Width, start.Y + start.Height / 2.0f - MathF.Sin(currentAngle) * (start.Height / 2.0f));
|
||||
cosPoints[i] = new(start.X + t * start.Width, start.Y + start.Height / 2.0f - MathF.Cos(currentAngle) * (start.Height / 2.0f));
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
float angleRad = angle * DEG2RAD;
|
||||
float cosRad = MathF.Cos(angleRad);
|
||||
float sinRad = MathF.Sin(angleRad);
|
||||
|
||||
Vector2 point = new(center.X + cosRad * radius, center.Y - sinRad * radius);
|
||||
Vector2 limitMin = new(center.X - radius, center.Y - radius);
|
||||
Vector2 limitMax = new(center.X + radius, center.Y + radius);
|
||||
|
||||
float complementary = 90.0f - angle;
|
||||
float supplementary = 180.0f - angle;
|
||||
float explementary = 360.0f - angle;
|
||||
|
||||
float tangent = Clamp(MathF.Tan(angleRad), -10.0f, 10.0f);
|
||||
float cotangent = (MathF.Abs(tangent) > 0.001f) ? Clamp(1.0f / tangent, -radius, radius) : 0.0f;
|
||||
Vector2 tangentPoint = new(center.X + radius, center.Y - tangent * radius);
|
||||
Vector2 cotangentPoint = new(center.X + cotangent * radius, center.Y - radius);
|
||||
|
||||
angle = Wrap(angle + (!pause ? 1.0f : 0.0f), 0.0f, 360.0f);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Cotangent (orange)
|
||||
DrawLineEx(new Vector2(center.X, limitMin.Y), new Vector2(cotangentPoint.X, limitMin.Y), 2.0f, Color.Orange);
|
||||
DrawLineDashed(center, cotangentPoint, 10, 4, Color.Orange);
|
||||
|
||||
// Side background
|
||||
DrawLine(580, 0, 580, GetScreenHeight(), new Color(218, 218, 218, 255));
|
||||
DrawRectangle(580, 0, GetScreenWidth(), GetScreenHeight(), new Color(232, 232, 232, 255));
|
||||
|
||||
// Base circle and axes
|
||||
DrawCircleLinesV(center, radius, Color.Gray);
|
||||
DrawLineEx(new Vector2(center.X, limitMin.Y), new Vector2(center.X, limitMax.Y), 1.0f, Color.Gray);
|
||||
DrawLineEx(new Vector2(limitMin.X, center.Y), new Vector2(limitMax.X, center.Y), 1.0f, Color.Gray);
|
||||
|
||||
// Wave graph axes
|
||||
DrawLineEx(new Vector2(start.X, start.Y), new Vector2(start.X, start.Y + start.Height), 2.0f, Color.Gray);
|
||||
DrawLineEx(new Vector2(start.X + start.Width, start.Y), new Vector2(start.X + start.Width, start.Y + start.Height), 2.0f, Color.Gray);
|
||||
DrawLineEx(new Vector2(start.X, start.Y + start.Height / 2), new Vector2(start.X + start.Width, start.Y + start.Height / 2), 2.0f, Color.Gray);
|
||||
|
||||
// Wave graph axis labels
|
||||
DrawText("1", (int)start.X - 8, (int)start.Y, 6, Color.Gray);
|
||||
DrawText("0", (int)start.X - 8, (int)start.Y + (int)start.Height / 2 - 6, 6, Color.Gray);
|
||||
DrawText("-1", (int)start.X - 12, (int)start.Y + (int)start.Height - 8, 6, Color.Gray);
|
||||
DrawText("0", (int)start.X - 2, (int)start.Y + (int)start.Height + 4, 6, Color.Gray);
|
||||
DrawText("360", (int)start.X + (int)start.Width - 8, (int)start.Y + (int)start.Height + 4, 6, Color.Gray);
|
||||
|
||||
// Sine (red - vertical)
|
||||
DrawLineEx(new Vector2(center.X, center.Y), new Vector2(center.X, point.Y), 2.0f, Color.Red);
|
||||
DrawLineDashed(new Vector2(point.X, center.Y), new Vector2(point.X, point.Y), 10, 4, Color.Red);
|
||||
DrawText($"Sine {sinRad:0.00}", 640, 190, 6, Color.Red);
|
||||
DrawCircleV(new Vector2(start.X + (angle / 360.0f) * start.Width, start.Y + ((-sinRad + 1) * start.Height / 2.0f)), 4.0f, Color.Red);
|
||||
fixed (Vector2* p = sinePoints) DrawSplineLinear(p, WAVE_POINTS, 1.0f, Color.Red);
|
||||
|
||||
// Cosine (blue - horizontal)
|
||||
DrawLineEx(new Vector2(center.X, center.Y), new Vector2(point.X, center.Y), 2.0f, Color.Blue);
|
||||
DrawLineDashed(new Vector2(center.X, point.Y), new Vector2(point.X, point.Y), 10, 4, Color.Blue);
|
||||
DrawText($"Cosine {cosRad:0.00}", 640, 210, 6, Color.Blue);
|
||||
DrawCircleV(new Vector2(start.X + (angle / 360.0f) * start.Width, start.Y + ((-cosRad + 1) * start.Height / 2.0f)), 4.0f, Color.Blue);
|
||||
fixed (Vector2* p = cosPoints) DrawSplineLinear(p, WAVE_POINTS, 1.0f, Color.Blue);
|
||||
|
||||
// Tangent (purple)
|
||||
DrawLineEx(new Vector2(limitMax.X, center.Y), new Vector2(limitMax.X, tangentPoint.Y), 2.0f, Color.Purple);
|
||||
DrawLineDashed(center, tangentPoint, 10, 4, Color.Purple);
|
||||
DrawText($"Tangent {tangent:0.00}", 640, 230, 6, Color.Purple);
|
||||
|
||||
// Cotangent (orange)
|
||||
DrawText($"Cotangent {cotangent:0.00}", 640, 250, 6, Color.Orange);
|
||||
|
||||
// Complementary angle (beige)
|
||||
DrawCircleSectorLines(center, radius * 0.6f, -angle, -90.0f, 36, Color.Beige);
|
||||
DrawText($"Complementary {complementary:0}°", 640, 150, 6, Color.Beige);
|
||||
|
||||
// Supplementary angle (darkblue)
|
||||
DrawCircleSectorLines(center, radius * 0.5f, -angle, -180.0f, 36, Color.DarkBlue);
|
||||
DrawText($"Supplementary {supplementary:0}°", 640, 130, 6, Color.DarkBlue);
|
||||
|
||||
// Explementary angle (pink)
|
||||
DrawCircleSectorLines(center, radius * 0.4f, -angle, -360.0f, 36, Color.Pink);
|
||||
DrawText($"Explementary {explementary:0}°", 640, 170, 6, Color.Pink);
|
||||
|
||||
// Current angle - arc (lime), radius (black), endpoint (black)
|
||||
DrawCircleSectorLines(center, radius * 0.7f, -angle, 0.0f, 36, Color.Lime);
|
||||
DrawLineEx(new Vector2(center.X, center.Y), point, 2.0f, Color.Black);
|
||||
DrawCircleV(point, 4.0f, Color.Black);
|
||||
|
||||
// Draw GUI controls
|
||||
// NOTE: raygui is not bound in raylib-cs, so the Pause toggle and Angle slider
|
||||
// are unavailable; the angle animates continuously.
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
GuiSetStyle(LABEL, TEXT_COLOR_NORMAL, ColorToInt(GRAY));
|
||||
GuiToggle((Rectangle){ 640, 70, 120, 20}, TextFormat("Pause"), &pause);
|
||||
GuiSetStyle(LABEL, TEXT_COLOR_NORMAL, ColorToInt(LIME));
|
||||
GuiSliderBar((Rectangle){ 640, 40, 120, 20}, "Angle", TextFormat("%.0f°", angle), &angle, 0.0f, 360.0f);
|
||||
|
||||
// Angle values panel
|
||||
GuiGroupBox((Rectangle){ 620, 110, 140, 170}, "Angle Values");
|
||||
*/
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - math sine cosine");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new MathSineCosine();
|
||||
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;
|
||||
}
|
||||
}
|
||||
127
Examples/Shapes/MouseTrail.cs
Normal file
127
Examples/Shapes/MouseTrail.cs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - Draw a mouse trail (position history)
|
||||
*
|
||||
* Example complexity rating: [★☆☆☆] 1/4
|
||||
*
|
||||
* Example originally created with raylib 5.6
|
||||
*
|
||||
* Example contributed by Balamurugan R (@Bala050814]) 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 Balamurugan R (@Bala050814)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class MouseTrail : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
// Define the maximum number of positions to store in the trail
|
||||
private const int MAX_TRAIL_LENGTH = 30;
|
||||
|
||||
public string Name => "Shapes / Mouse Trail";
|
||||
|
||||
public string Title => "raylib [shapes] example - mouse trail";
|
||||
|
||||
// Array to store the history of mouse positions (our fixed-size queue)
|
||||
private Vector2[] trailPositions;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Array to store the history of mouse positions (our fixed-size queue)
|
||||
trailPositions = new Vector2[MAX_TRAIL_LENGTH];
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
Vector2 mousePosition = GetMousePosition();
|
||||
|
||||
// Shift all existing positions backward by one slot in the array
|
||||
// The last element (the oldest position) is dropped
|
||||
for (int i = MAX_TRAIL_LENGTH - 1; i > 0; i--)
|
||||
{
|
||||
trailPositions[i] = trailPositions[i - 1];
|
||||
}
|
||||
|
||||
// Store the new, current mouse position at the start of the array (Index 0)
|
||||
trailPositions[0] = mousePosition;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.Black);
|
||||
|
||||
// Draw the trail by looping through the history array
|
||||
for (int i = 0; i < MAX_TRAIL_LENGTH; i++)
|
||||
{
|
||||
// Ensure we skip drawing if the array hasn't been fully filled on startup
|
||||
if ((trailPositions[i].X != 0.0f) || (trailPositions[i].Y != 0.0f))
|
||||
{
|
||||
// Calculate relative trail strength (ratio is near 1.0 for new, near 0.0 for old)
|
||||
float ratio = (float)(MAX_TRAIL_LENGTH - i)/MAX_TRAIL_LENGTH;
|
||||
|
||||
// Fade effect: oldest positions are more transparent
|
||||
// Fade (color, alpha) - alpha is 0.5 to 1.0 based on ratio
|
||||
Color trailColor = Fade(Color.SkyBlue, ratio*0.5f + 0.5f);
|
||||
|
||||
// Size effect: oldest positions are smaller
|
||||
float trailRadius = 15.0f*ratio;
|
||||
|
||||
DrawCircleV(trailPositions[i], trailRadius, trailColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a distinct white circle for the current mouse position (Index 0)
|
||||
DrawCircleV(mousePosition, 15.0f, Color.White);
|
||||
|
||||
DrawText("Move the mouse to see the trail effect!", 10, screenHeight - 30, 20, Color.LightGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - mouse trail");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new MouseTrail();
|
||||
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;
|
||||
}
|
||||
}
|
||||
283
Examples/Shapes/PenroseTile.cs
Normal file
283
Examples/Shapes/PenroseTile.cs
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - penrose tile
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 6.0
|
||||
* Based on: https://processing.org/examples/penrosetile.html
|
||||
*
|
||||
* Example contributed by David Buzatto (@davidbuzatto) 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 David Buzatto (@davidbuzatto)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class PenroseTile : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Shapes / Penrose Tile";
|
||||
|
||||
public string Title => "raylib [shapes] example - penrose tile";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
public int TargetFps => 120;
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
private struct TurtleState
|
||||
{
|
||||
public Vector2 origin;
|
||||
public float angle;
|
||||
}
|
||||
|
||||
private class PenroseLSystem
|
||||
{
|
||||
public int steps;
|
||||
public StringBuilder production;
|
||||
public string ruleW;
|
||||
public string ruleX;
|
||||
public string ruleY;
|
||||
public string ruleZ;
|
||||
public float drawLength;
|
||||
public float theta;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Global Variables Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
private Stack<TurtleState> turtleStack;
|
||||
|
||||
private const float drawLength = 460.0f;
|
||||
private int minGenerations;
|
||||
private int maxGenerations;
|
||||
private int generations;
|
||||
private PenroseLSystem ls;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
turtleStack = new Stack<TurtleState>();
|
||||
|
||||
minGenerations = 0;
|
||||
maxGenerations = 4;
|
||||
generations = 0;
|
||||
|
||||
// Initialize new penrose tile
|
||||
ls = CreatePenroseLSystem(drawLength * (generations / (float)maxGenerations));
|
||||
for (int i = 0; i < generations; i++) BuildProductionStep(ls);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
bool rebuild = false;
|
||||
if (IsKeyPressed(KeyboardKey.Up))
|
||||
{
|
||||
if (generations < maxGenerations)
|
||||
{
|
||||
generations++;
|
||||
rebuild = true;
|
||||
}
|
||||
}
|
||||
else if (IsKeyPressed(KeyboardKey.Down))
|
||||
{
|
||||
if (generations > minGenerations)
|
||||
{
|
||||
generations--;
|
||||
if (generations > 0) rebuild = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (rebuild)
|
||||
{
|
||||
ls = CreatePenroseLSystem(drawLength * (generations / (float)maxGenerations));
|
||||
for (int i = 0; i < generations; i++) BuildProductionStep(ls);
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
if (generations > 0) DrawPenroseLSystem(ls);
|
||||
|
||||
DrawText("penrose l-system", 10, 10, 20, Color.DarkGray);
|
||||
DrawText("press up or down to change generations", 10, 30, 20, Color.DarkGray);
|
||||
DrawText($"generations: {generations}", 10, 50, 20, Color.DarkGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// Push turtle state for next step
|
||||
private void PushTurtleState(TurtleState state)
|
||||
{
|
||||
turtleStack.Push(state);
|
||||
}
|
||||
|
||||
// Pop turtle state step
|
||||
private TurtleState PopTurtleState()
|
||||
{
|
||||
if (turtleStack.Count > 0) return turtleStack.Pop();
|
||||
else TraceLog(TraceLogLevel.Warning, "TURTLE STACK UNDERFLOW!");
|
||||
|
||||
return new TurtleState();
|
||||
}
|
||||
|
||||
// Create a new penrose tile structure
|
||||
private static PenroseLSystem CreatePenroseLSystem(float drawLength)
|
||||
{
|
||||
PenroseLSystem ls = new PenroseLSystem
|
||||
{
|
||||
steps = 0,
|
||||
ruleW = "YF++ZF4-XF[-YF4-WF]++",
|
||||
ruleX = "+YF--ZF[3-WF--XF]+",
|
||||
ruleY = "-WF++XF[+++YF++ZF]-",
|
||||
ruleZ = "--YF++++WF[+ZF++++XF]--XF",
|
||||
drawLength = drawLength,
|
||||
theta = 36.0f // Degrees
|
||||
};
|
||||
|
||||
ls.production = new StringBuilder("[X]++[X]++[X]++[X]++[X]");
|
||||
|
||||
return ls;
|
||||
}
|
||||
|
||||
// Build next penrose step
|
||||
private static void BuildProductionStep(PenroseLSystem ls)
|
||||
{
|
||||
StringBuilder newProduction = new StringBuilder();
|
||||
|
||||
string production = ls.production.ToString();
|
||||
|
||||
for (int i = 0; i < production.Length; i++)
|
||||
{
|
||||
char step = production[i];
|
||||
switch (step)
|
||||
{
|
||||
case 'W': newProduction.Append(ls.ruleW); break;
|
||||
case 'X': newProduction.Append(ls.ruleX); break;
|
||||
case 'Y': newProduction.Append(ls.ruleY); break;
|
||||
case 'Z': newProduction.Append(ls.ruleZ); break;
|
||||
default:
|
||||
{
|
||||
if (step != 'F') newProduction.Append(step);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
ls.drawLength *= 0.5f;
|
||||
ls.production = newProduction;
|
||||
}
|
||||
|
||||
// Draw penrose tile lines
|
||||
private void DrawPenroseLSystem(PenroseLSystem ls)
|
||||
{
|
||||
Vector2 screenCenter = new Vector2(GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f);
|
||||
|
||||
TurtleState turtle = new TurtleState
|
||||
{
|
||||
origin = new Vector2(0, 0),
|
||||
angle = -90.0f
|
||||
};
|
||||
|
||||
int repeats = 1;
|
||||
string production = ls.production.ToString();
|
||||
int productionLength = production.Length;
|
||||
ls.steps += 12;
|
||||
|
||||
if (ls.steps > productionLength) ls.steps = productionLength;
|
||||
|
||||
for (int i = 0; i < ls.steps; i++)
|
||||
{
|
||||
char step = production[i];
|
||||
if (step == 'F')
|
||||
{
|
||||
for (int j = 0; j < repeats; j++)
|
||||
{
|
||||
Vector2 startPosWorld = turtle.origin;
|
||||
float radAngle = DEG2RAD * turtle.angle;
|
||||
turtle.origin.X += ls.drawLength * MathF.Cos(radAngle);
|
||||
turtle.origin.Y += ls.drawLength * MathF.Sin(radAngle);
|
||||
Vector2 startPosScreen = new Vector2(startPosWorld.X + screenCenter.X, startPosWorld.Y + screenCenter.Y);
|
||||
Vector2 endPosScreen = new Vector2(turtle.origin.X + screenCenter.X, turtle.origin.Y + screenCenter.Y);
|
||||
|
||||
DrawLineEx(startPosScreen, endPosScreen, 2, Fade(Color.Black, 0.2f));
|
||||
}
|
||||
|
||||
repeats = 1;
|
||||
}
|
||||
else if (step == '+')
|
||||
{
|
||||
for (int j = 0; j < repeats; j++) turtle.angle += ls.theta;
|
||||
|
||||
repeats = 1;
|
||||
}
|
||||
else if (step == '-')
|
||||
{
|
||||
for (int j = 0; j < repeats; j++) turtle.angle += -ls.theta;
|
||||
|
||||
repeats = 1;
|
||||
}
|
||||
else if (step == '[') PushTurtleState(turtle);
|
||||
else if (step == ']') turtle = PopTurtleState();
|
||||
else if ((step >= 48) && (step <= 57)) repeats = (int)step - 48;
|
||||
}
|
||||
|
||||
turtleStack.Clear();
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - penrose tile");
|
||||
|
||||
SetTargetFPS(120); // Set our game to run at 120 frames-per-second
|
||||
//---------------------------------------------------------------------------------------
|
||||
|
||||
var game = new PenroseTile();
|
||||
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;
|
||||
}
|
||||
}
|
||||
384
Examples/Shapes/PieChart.cs
Normal file
384
Examples/Shapes/PieChart.cs
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - pie chart
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.6
|
||||
*
|
||||
* Example contributed by Gideon Serfontein (@GideonSerf) 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 Gideon Serfontein (@GideonSerf)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class PieChart : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MAX_PIE_SLICES = 10; // Max pie slices
|
||||
|
||||
public string Name => "Shapes / Pie Chart";
|
||||
|
||||
public string Title => "raylib [shapes] example - pie chart";
|
||||
|
||||
private int sliceCount;
|
||||
private float donutInnerRadius;
|
||||
private float[] values;
|
||||
private StringBuilder[] labels;
|
||||
private bool[] editingLabel;
|
||||
|
||||
private bool showValues;
|
||||
private bool showPercentages;
|
||||
private bool showDonut;
|
||||
private int hoveredSlice;
|
||||
private Vector2 scrollContentOffset;
|
||||
|
||||
// UI layout parameters
|
||||
private const int panelWidth = 270;
|
||||
private const int panelMargin = 5;
|
||||
|
||||
private Vector2 panelPos;
|
||||
private Rectangle panelRect;
|
||||
private Rectangle canvas;
|
||||
private Vector2 center;
|
||||
private const float radius = 205.0f;
|
||||
|
||||
// Total value for percentage calculations
|
||||
private float totalValue;
|
||||
|
||||
private int framesCounter;
|
||||
|
||||
// Minimal raygui-like global state
|
||||
private static bool guiDisabled;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
sliceCount = 7;
|
||||
donutInnerRadius = 25.0f;
|
||||
values = new float[MAX_PIE_SLICES] { 300.0f, 100.0f, 450.0f, 350.0f, 600.0f, 380.0f, 750.0f, 0.0f, 0.0f, 0.0f };
|
||||
labels = new StringBuilder[MAX_PIE_SLICES];
|
||||
editingLabel = new bool[MAX_PIE_SLICES];
|
||||
|
||||
for (int i = 0; i < MAX_PIE_SLICES; i++) labels[i] = new StringBuilder($"Slice {i + 1:D2}");
|
||||
|
||||
showValues = true;
|
||||
showPercentages = false;
|
||||
showDonut = false;
|
||||
hoveredSlice = -1;
|
||||
scrollContentOffset = new Vector2(0, 0);
|
||||
|
||||
// UI Panel top-left anchor
|
||||
panelPos = new Vector2(
|
||||
(float)screenWidth - panelMargin - panelWidth,
|
||||
(float)panelMargin
|
||||
);
|
||||
|
||||
// UI Panel rectangle
|
||||
panelRect = new Rectangle(
|
||||
panelPos.X, panelPos.Y,
|
||||
(float)panelWidth,
|
||||
(float)screenHeight - 2.0f * panelMargin
|
||||
);
|
||||
|
||||
// Pie chart geometry
|
||||
canvas = new Rectangle(0, 0, panelPos.X, (float)screenHeight);
|
||||
center = new Vector2(canvas.Width / 2.0f, canvas.Height / 2.0f);
|
||||
|
||||
totalValue = 0.0f;
|
||||
|
||||
framesCounter = 0;
|
||||
guiDisabled = false;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
framesCounter++;
|
||||
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Calculate total value for percentage calculations
|
||||
totalValue = 0.0f;
|
||||
for (int i = 0; i < sliceCount; i++) totalValue += values[i];
|
||||
|
||||
// Check for mouse hover over slices
|
||||
hoveredSlice = -1; // Reset hovered slice
|
||||
Vector2 mousePos = GetMousePosition();
|
||||
if (CheckCollisionPointRec(mousePos, canvas)) // Only check if mouse is inside the canvas
|
||||
{
|
||||
float dx = mousePos.X - center.X;
|
||||
float dy = mousePos.Y - center.Y;
|
||||
float distance = MathF.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance <= radius) // Inside the pie radius
|
||||
{
|
||||
float angle = MathF.Atan2(dy, dx) * RAD2DEG;
|
||||
if (angle < 0) angle += 360;
|
||||
|
||||
float currentAngle = 0.0f;
|
||||
for (int i = 0; i < sliceCount; i++)
|
||||
{
|
||||
float sweep = (totalValue > 0) ? (values[i] / totalValue) * 360.0f : 0.0f;
|
||||
|
||||
if ((angle >= currentAngle) && (angle < (currentAngle + sweep)))
|
||||
{
|
||||
hoveredSlice = i;
|
||||
break;
|
||||
}
|
||||
|
||||
currentAngle += sweep;
|
||||
}
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw the pie chart on the canvas
|
||||
float startAngle = 0.0f;
|
||||
for (int i = 0; i < sliceCount; i++)
|
||||
{
|
||||
float sweepAngle = (totalValue > 0) ? (values[i] / totalValue) * 360.0f : 0.0f;
|
||||
float midAngle = startAngle + sweepAngle / 2.0f; // Middle angle for label positioning
|
||||
|
||||
Color color = ColorFromHSV((float)i / sliceCount * 360.0f, 0.75f, 0.9f);
|
||||
float currentRadius = radius;
|
||||
|
||||
// Make the hovered slice pop out by adding pixels to its radius
|
||||
if (i == hoveredSlice) currentRadius += 20.0f;
|
||||
|
||||
// Draw the pie slice using raylib's DrawCircleSector function
|
||||
DrawCircleSector(center, currentRadius, startAngle, startAngle + sweepAngle, 120, color);
|
||||
|
||||
// Draw the label for the current slice
|
||||
if (values[i] > 0)
|
||||
{
|
||||
string labelText;
|
||||
if (showValues && showPercentages) labelText = $"{values[i]:F1} ({(values[i] / totalValue) * 100.0f:F0}%)";
|
||||
else if (showValues) labelText = $"{values[i]:F1}";
|
||||
else if (showPercentages) labelText = $"{(values[i] / totalValue) * 100.0f:F0}%";
|
||||
else labelText = "";
|
||||
|
||||
Vector2 textSize = MeasureTextEx(GetFontDefault(), labelText, 20, 1);
|
||||
float labelRadius = radius * 0.7f;
|
||||
Vector2 labelPos = new Vector2(center.X + MathF.Cos(midAngle * DEG2RAD) * labelRadius - textSize.X / 2.0f,
|
||||
center.Y + MathF.Sin(midAngle * DEG2RAD) * labelRadius - textSize.Y / 2.0f);
|
||||
DrawText(labelText, (int)labelPos.X, (int)labelPos.Y, 20, Color.White);
|
||||
}
|
||||
|
||||
// Draw inner circle to create donut effect
|
||||
// TODO: This is a hacky solution, better use DrawRing()
|
||||
if (showDonut) DrawCircleV(center, donutInnerRadius, Color.RayWhite);
|
||||
|
||||
startAngle += sweepAngle;
|
||||
}
|
||||
|
||||
// UI control panel (minimal raygui-like controls, raygui is not bound in raylib-cs)
|
||||
DrawRectangleRec(panelRect, Fade(Color.LightGray, 0.5f));
|
||||
DrawRectangleLinesEx(panelRect, 1.0f, Color.Gray);
|
||||
|
||||
GuiSpinner(new Rectangle(panelPos.X + 95, (float)panelPos.Y + 12, 125, 25), "Slices ", ref sliceCount, 1, MAX_PIE_SLICES);
|
||||
GuiCheckBox(new Rectangle(panelPos.X + 20, (float)panelPos.Y + 12 + 40, 20, 20), "Show Values", ref showValues);
|
||||
GuiCheckBox(new Rectangle(panelPos.X + 20, (float)panelPos.Y + 12 + 70, 20, 20), "Show Percentages", ref showPercentages);
|
||||
GuiCheckBox(new Rectangle(panelPos.X + 20, (float)panelPos.Y + 12 + 100, 20, 20), "Make Donut", ref showDonut);
|
||||
|
||||
if (showDonut) GuiDisable();
|
||||
GuiSliderBar(new Rectangle(panelPos.X + 80, (float)panelPos.Y + 12 + 130, panelRect.Width - 100, 30),
|
||||
"Inner Radius", null, ref donutInnerRadius, 5.0f, radius - 10.0f);
|
||||
GuiEnable();
|
||||
|
||||
GuiLine(new Rectangle(panelPos.X + 10, (float)panelPos.Y + 12 + 170, panelRect.Width - 20, 1));
|
||||
|
||||
// Scrollable area for slice editors
|
||||
float scrollTop = (float)panelPos.Y + 12 + 190;
|
||||
Rectangle scrollPanelBounds = new Rectangle(
|
||||
panelPos.X + panelMargin,
|
||||
scrollTop,
|
||||
panelRect.Width - panelMargin * 2,
|
||||
(panelRect.Y + panelRect.Height) - scrollTop - panelMargin);
|
||||
int contentHeight = sliceCount * 35;
|
||||
|
||||
// Simple vertical scroll via mouse wheel while hovering the panel
|
||||
if (CheckCollisionPointRec(GetMousePosition(), scrollPanelBounds))
|
||||
{
|
||||
scrollContentOffset.Y += GetMouseWheelMove() * 20.0f;
|
||||
float minOffset = MathF.Min(0.0f, scrollPanelBounds.Height - contentHeight);
|
||||
if (scrollContentOffset.Y < minOffset) scrollContentOffset.Y = minOffset;
|
||||
if (scrollContentOffset.Y > 0.0f) scrollContentOffset.Y = 0.0f;
|
||||
}
|
||||
|
||||
Rectangle view = scrollPanelBounds;
|
||||
float contentX = view.X + scrollContentOffset.X; // Left of content
|
||||
float contentY = view.Y + scrollContentOffset.Y; // Top of content
|
||||
|
||||
BeginScissorMode((int)view.X, (int)view.Y, (int)view.Width, (int)view.Height);
|
||||
|
||||
for (int i = 0; i < sliceCount; i++)
|
||||
{
|
||||
int rowY = (int)(contentY + 5 + i * 35);
|
||||
|
||||
// Color indicator
|
||||
Color color = ColorFromHSV((float)i / sliceCount * 360.0f, 0.75f, 0.9f);
|
||||
DrawRectangle((int)(contentX + 15), rowY + 5, 20, 20, color);
|
||||
|
||||
// Label textbox
|
||||
if (GuiTextBox(new Rectangle(contentX + 45, (float)rowY, 75, 30), labels[i], 32, editingLabel[i])) editingLabel[i] = !editingLabel[i];
|
||||
|
||||
GuiSliderBar(new Rectangle(contentX + 130, (float)rowY, 110, 30), null, null, ref values[i], 0.0f, 1000.0f);
|
||||
}
|
||||
|
||||
EndScissorMode();
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Minimal raygui-like widgets (plain raylib re-implementation)
|
||||
//----------------------------------------------------------------------------------
|
||||
private static void GuiDisable() => guiDisabled = true;
|
||||
private static void GuiEnable() => guiDisabled = false;
|
||||
|
||||
private static void GuiLine(Rectangle bounds)
|
||||
{
|
||||
int y = (int)(bounds.Y + bounds.Height / 2);
|
||||
DrawLine((int)bounds.X, y, (int)(bounds.X + bounds.Width), y, Color.Gray);
|
||||
}
|
||||
|
||||
private static void GuiCheckBox(Rectangle bounds, string text, ref bool active)
|
||||
{
|
||||
Vector2 mouse = GetMousePosition();
|
||||
bool hover = CheckCollisionPointRec(mouse, bounds);
|
||||
if (!guiDisabled && 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 (!guiDisabled && CheckCollisionPointRec(mouse, left) && IsMouseButtonPressed(MouseButton.Left) && value > minValue) value--;
|
||||
if (!guiDisabled && 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 GuiSliderBar(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
|
||||
{
|
||||
Vector2 mouse = GetMousePosition();
|
||||
bool hover = CheckCollisionPointRec(mouse, bounds);
|
||||
if (!guiDisabled && 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, guiDisabled ? Color.Gray : Color.LightGray);
|
||||
float pct = (value - minValue) / (maxValue - minValue);
|
||||
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), guiDisabled ? Color.DarkGray : Color.SkyBlue);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
|
||||
private bool GuiTextBox(Rectangle bounds, StringBuilder text, int maxChars, bool editMode)
|
||||
{
|
||||
bool pressed = false;
|
||||
Vector2 mouse = GetMousePosition();
|
||||
bool hover = CheckCollisionPointRec(mouse, bounds);
|
||||
|
||||
if (editMode)
|
||||
{
|
||||
int key = GetCharPressed();
|
||||
while (key > 0)
|
||||
{
|
||||
if ((key >= 32) && (key <= 125) && (text.Length < maxChars - 1))
|
||||
{
|
||||
text.Append((char)key);
|
||||
}
|
||||
|
||||
key = GetCharPressed();
|
||||
}
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Backspace) && (text.Length > 0)) text.Remove(text.Length - 1, 1);
|
||||
}
|
||||
|
||||
DrawRectangleRec(bounds, Color.RayWhite);
|
||||
DrawRectangleLinesEx(bounds, editMode ? 2 : 1, editMode ? Color.Red : (hover ? Color.Blue : Color.Gray));
|
||||
|
||||
string content = text.ToString();
|
||||
DrawText(content, (int)bounds.X + 4, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
|
||||
if (editMode && ((framesCounter / 20) % 2 == 0))
|
||||
{
|
||||
DrawText("_", (int)bounds.X + 4 + MeasureText(content, 10), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
|
||||
if (hover && IsMouseButtonPressed(MouseButton.Left)) pressed = true;
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - pie chart");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new PieChart();
|
||||
game.Init();
|
||||
|
||||
// Main game loop
|
||||
while (!WindowShouldClose())
|
||||
{
|
||||
game.Update();
|
||||
}
|
||||
|
||||
game.Unload();
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
CloseWindow(); // Close window and OpenGL context
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
252
Examples/Shapes/RectangleAdvanced.cs
Normal file
252
Examples/Shapes/RectangleAdvanced.cs
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - rectangle advanced
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 5.5
|
||||
*
|
||||
* Example contributed by Everton Jr. (@evertonse) 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) 2024-2025 Everton Jr. (@evertonse) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Rlgl;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class RectangleAdvanced : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Shapes / Rectangle Advanced";
|
||||
|
||||
public string Title => "raylib [shapes] example - rectangle advanced";
|
||||
|
||||
public void Init()
|
||||
{
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update rectangle bounds
|
||||
//----------------------------------------------------------------------------------
|
||||
float width = GetScreenWidth() / 2.0f, height = GetScreenHeight() / 6.0f;
|
||||
Rectangle rec = new Rectangle(
|
||||
GetScreenWidth() / 2.0f - width / 2,
|
||||
GetScreenHeight() / 2.0f - 5 * (height / 2),
|
||||
width, height
|
||||
);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Draw All Rectangles with different roundess for each side and different gradients
|
||||
DrawRectangleRoundedGradientH(rec, 0.8f, 0.8f, 36, Color.Blue, Color.Red);
|
||||
|
||||
rec.Y += rec.Height + 1;
|
||||
DrawRectangleRoundedGradientH(rec, 0.5f, 1.0f, 36, Color.Red, Color.Pink);
|
||||
|
||||
rec.Y += rec.Height + 1;
|
||||
DrawRectangleRoundedGradientH(rec, 1.0f, 0.5f, 36, Color.Red, Color.Blue);
|
||||
|
||||
rec.Y += rec.Height + 1;
|
||||
DrawRectangleRoundedGradientH(rec, 0.0f, 1.0f, 36, Color.Blue, Color.Black);
|
||||
|
||||
rec.Y += rec.Height + 1;
|
||||
DrawRectangleRoundedGradientH(rec, 1.0f, 0.0f, 36, Color.Blue, Color.Pink);
|
||||
EndDrawing();
|
||||
//--------------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Draw rectangle with rounded edges and horizontal gradient, with options to choose side of roundness
|
||||
// NOTE: Adapted from both 'DrawRectangleRounded()' and 'DrawRectangleGradientH()' raylib [rshapes] implementations
|
||||
private static void DrawRectangleRoundedGradientH(Rectangle rec, float roundnessLeft, float roundnessRight, int segments, Color left, Color right)
|
||||
{
|
||||
// Neither side is rounded
|
||||
if ((roundnessLeft <= 0.0f && roundnessRight <= 0.0f) || (rec.Width < 1) || (rec.Height < 1))
|
||||
{
|
||||
DrawRectangleGradientEx(rec, left, left, right, right);
|
||||
return;
|
||||
}
|
||||
|
||||
if (roundnessLeft >= 1.0f) roundnessLeft = 1.0f;
|
||||
if (roundnessRight >= 1.0f) roundnessRight = 1.0f;
|
||||
|
||||
// Calculate corner radius both from right and left
|
||||
float recSize = rec.Width > rec.Height ? rec.Height : rec.Width;
|
||||
float radiusLeft = (recSize * roundnessLeft) / 2;
|
||||
float radiusRight = (recSize * roundnessRight) / 2;
|
||||
|
||||
if (radiusLeft <= 0.0f) radiusLeft = 0.0f;
|
||||
if (radiusRight <= 0.0f) radiusRight = 0.0f;
|
||||
|
||||
if (radiusRight <= 0.0f && radiusLeft <= 0.0f) return;
|
||||
|
||||
float stepLength = 90.0f / (float)segments;
|
||||
|
||||
/*
|
||||
Diagram Copied here for reference, original at 'DrawRectangleRounded()' source code
|
||||
|
||||
P0____________________P1
|
||||
/| |\
|
||||
/1| 2 |3\
|
||||
P7 /__|____________________|__\ P2
|
||||
| |P8 P9| |
|
||||
| 8 | 9 | 4 |
|
||||
| __|____________________|__ |
|
||||
P6 \ |P11 P10| / P3
|
||||
\7| 6 |5/
|
||||
\|____________________|/
|
||||
P5 P4
|
||||
*/
|
||||
|
||||
// Coordinates of the 12 points also adapted from `DrawRectangleRounded`
|
||||
Vector2[] point = new Vector2[12]
|
||||
{
|
||||
// PO, P1, P2
|
||||
new Vector2(rec.X + radiusLeft, rec.Y), new Vector2((rec.X + rec.Width) - radiusRight, rec.Y), new Vector2(rec.X + rec.Width, rec.Y + radiusRight),
|
||||
// P3, P4
|
||||
new Vector2(rec.X + rec.Width, (rec.Y + rec.Height) - radiusRight), new Vector2((rec.X + rec.Width) - radiusRight, rec.Y + rec.Height),
|
||||
// P5, P6, P7
|
||||
new Vector2(rec.X + radiusLeft, rec.Y + rec.Height), new Vector2(rec.X, (rec.Y + rec.Height) - radiusLeft), new Vector2(rec.X, rec.Y + radiusLeft),
|
||||
// P8, P9
|
||||
new Vector2(rec.X + radiusLeft, rec.Y + radiusLeft), new Vector2((rec.X + rec.Width) - radiusRight, rec.Y + radiusRight),
|
||||
// P10, P11
|
||||
new Vector2((rec.X + rec.Width) - radiusRight, (rec.Y + rec.Height) - radiusRight), new Vector2(rec.X + radiusLeft, (rec.Y + rec.Height) - radiusLeft)
|
||||
};
|
||||
|
||||
Vector2[] centers = new Vector2[4] { point[8], point[9], point[10], point[11] };
|
||||
float[] angles = new float[4] { 180.0f, 270.0f, 0.0f, 90.0f };
|
||||
|
||||
// Here we use the 'Diagram' to guide ourselves to which point receives what color
|
||||
// By choosing the color correctly associated with a point the gradient effect
|
||||
// will naturally come from OpenGL interpolation
|
||||
// But this time instead of Quad, we think in triangles
|
||||
|
||||
Begin(DrawMode.Triangles);
|
||||
// Draw all of the 4 corners: [1] Upper Left Corner, [3] Upper Right Corner, [5] Lower Right Corner, [7] Lower Left Corner
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
Color color = new Color(0, 0, 0, 0);
|
||||
float radius = 0.0f;
|
||||
if (k == 0) { color = left; radius = radiusLeft; } // [1] Upper Left Corner
|
||||
if (k == 1) { color = right; radius = radiusRight; } // [3] Upper Right Corner
|
||||
if (k == 2) { color = right; radius = radiusRight; } // [5] Lower Right Corner
|
||||
if (k == 3) { color = left; radius = radiusLeft; } // [7] Lower Left Corner
|
||||
|
||||
float angle = angles[k];
|
||||
Vector2 center = centers[k];
|
||||
|
||||
for (int i = 0; i < segments; i++)
|
||||
{
|
||||
Color4ub(color.R, color.G, color.B, color.A);
|
||||
Vertex2f(center.X, center.Y);
|
||||
Vertex2f(center.X + MathF.Cos(DEG2RAD * (angle + stepLength)) * radius, center.Y + MathF.Sin(DEG2RAD * (angle + stepLength)) * radius);
|
||||
Vertex2f(center.X + MathF.Cos(DEG2RAD * angle) * radius, center.Y + MathF.Sin(DEG2RAD * angle) * radius);
|
||||
angle += stepLength;
|
||||
}
|
||||
}
|
||||
|
||||
// [2] Upper Rectangle
|
||||
Color4ub(left.R, left.G, left.B, left.A);
|
||||
Vertex2f(point[0].X, point[0].Y);
|
||||
Vertex2f(point[8].X, point[8].Y);
|
||||
Color4ub(right.R, right.G, right.B, right.A);
|
||||
Vertex2f(point[9].X, point[9].Y);
|
||||
Vertex2f(point[1].X, point[1].Y);
|
||||
Color4ub(left.R, left.G, left.B, left.A);
|
||||
Vertex2f(point[0].X, point[0].Y);
|
||||
Color4ub(right.R, right.G, right.B, right.A);
|
||||
Vertex2f(point[9].X, point[9].Y);
|
||||
|
||||
// [4] Right Rectangle
|
||||
Color4ub(right.R, right.G, right.B, right.A);
|
||||
Vertex2f(point[9].X, point[9].Y);
|
||||
Vertex2f(point[10].X, point[10].Y);
|
||||
Vertex2f(point[3].X, point[3].Y);
|
||||
Vertex2f(point[2].X, point[2].Y);
|
||||
Vertex2f(point[9].X, point[9].Y);
|
||||
Vertex2f(point[3].X, point[3].Y);
|
||||
|
||||
// [6] Bottom Rectangle
|
||||
Color4ub(left.R, left.G, left.B, left.A);
|
||||
Vertex2f(point[11].X, point[11].Y);
|
||||
Vertex2f(point[5].X, point[5].Y);
|
||||
Color4ub(right.R, right.G, right.B, right.A);
|
||||
Vertex2f(point[4].X, point[4].Y);
|
||||
Vertex2f(point[10].X, point[10].Y);
|
||||
Color4ub(left.R, left.G, left.B, left.A);
|
||||
Vertex2f(point[11].X, point[11].Y);
|
||||
Color4ub(right.R, right.G, right.B, right.A);
|
||||
Vertex2f(point[4].X, point[4].Y);
|
||||
|
||||
// [8] Left Rectangle
|
||||
Color4ub(left.R, left.G, left.B, left.A);
|
||||
Vertex2f(point[7].X, point[7].Y);
|
||||
Vertex2f(point[6].X, point[6].Y);
|
||||
Vertex2f(point[11].X, point[11].Y);
|
||||
Vertex2f(point[8].X, point[8].Y);
|
||||
Vertex2f(point[7].X, point[7].Y);
|
||||
Vertex2f(point[11].X, point[11].Y);
|
||||
|
||||
// [9] Middle Rectangle
|
||||
Color4ub(left.R, left.G, left.B, left.A);
|
||||
Vertex2f(point[8].X, point[8].Y);
|
||||
Vertex2f(point[11].X, point[11].Y);
|
||||
Color4ub(right.R, right.G, right.B, right.A);
|
||||
Vertex2f(point[10].X, point[10].Y);
|
||||
Vertex2f(point[9].X, point[9].Y);
|
||||
Color4ub(left.R, left.G, left.B, left.A);
|
||||
Vertex2f(point[8].X, point[8].Y);
|
||||
Color4ub(right.R, right.G, right.B, right.A);
|
||||
Vertex2f(point[10].X, point[10].Y);
|
||||
End();
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - rectangle advanced");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new RectangleAdvanced();
|
||||
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;
|
||||
}
|
||||
}
|
||||
199
Examples/Shapes/RecursiveTree.cs
Normal file
199
Examples/Shapes/RecursiveTree.cs
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - recursive tree
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Jopestpe (@jopestpe)
|
||||
*
|
||||
* 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 Jopestpe (@jopestpe)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class RecursiveTree : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Shapes / Recursive Tree";
|
||||
|
||||
public string Title => "raylib [shapes] example - recursive tree";
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
private struct Branch
|
||||
{
|
||||
public Vector2 start;
|
||||
public Vector2 end;
|
||||
public float angle;
|
||||
public float length;
|
||||
}
|
||||
|
||||
private Vector2 start;
|
||||
private float angle;
|
||||
private float thick;
|
||||
private float treeDepth;
|
||||
private float branchDecay;
|
||||
private float length;
|
||||
private bool bezier;
|
||||
|
||||
private Branch[] branches;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
start = new Vector2((screenWidth / 2.0f) - 125.0f, (float)screenHeight);
|
||||
angle = 40.0f;
|
||||
thick = 1.0f;
|
||||
treeDepth = 10.0f;
|
||||
branchDecay = 0.66f;
|
||||
length = 120.0f;
|
||||
bezier = false;
|
||||
|
||||
branches = new Branch[1030];
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
float theta = angle * DEG2RAD;
|
||||
int maxBranches = (int)(MathF.Pow(2, MathF.Floor(treeDepth)));
|
||||
int count = 0;
|
||||
|
||||
Vector2 initialEnd = new Vector2(start.X + length * MathF.Sin(0.0f), start.Y - length * MathF.Cos(0.0f));
|
||||
branches[count++] = new Branch { start = start, end = initialEnd, angle = 0.0f, length = length };
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Branch branch = branches[i];
|
||||
if (branch.length < 2) continue;
|
||||
|
||||
float nextLength = branch.length * branchDecay;
|
||||
|
||||
if (count < maxBranches && nextLength >= 2)
|
||||
{
|
||||
Vector2 branchStart = branch.end;
|
||||
|
||||
float angle1 = branch.angle + theta;
|
||||
Vector2 branchEnd1 = new Vector2(branchStart.X + nextLength * MathF.Sin(angle1), branchStart.Y - nextLength * MathF.Cos(angle1));
|
||||
branches[count++] = new Branch { start = branchStart, end = branchEnd1, angle = angle1, length = nextLength };
|
||||
|
||||
float angle2 = branch.angle - theta;
|
||||
Vector2 branchEnd2 = new Vector2(branchStart.X + nextLength * MathF.Sin(angle2), branchStart.Y - nextLength * MathF.Cos(angle2));
|
||||
branches[count++] = new Branch { start = branchStart, end = branchEnd2, angle = angle2, length = nextLength };
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Branch branch = branches[i];
|
||||
if (branch.length >= 2)
|
||||
{
|
||||
if (bezier) DrawLineBezier(branch.start, branch.end, thick, Color.Red);
|
||||
else DrawLineEx(branch.start, branch.end, thick, Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
DrawLine(580, 0, 580, GetScreenHeight(), new Color(218, 218, 218, 255));
|
||||
DrawRectangle(580, 0, GetScreenWidth(), GetScreenHeight(), new Color(232, 232, 232, 255));
|
||||
|
||||
// Draw GUI controls (minimal raygui-like controls, raygui is not bound in raylib-cs)
|
||||
//------------------------------------------------------------------------------
|
||||
GuiSliderBar(new Rectangle(640, 40, 120, 20), "Angle", $"{angle:F0}", ref angle, 0, 180);
|
||||
GuiSliderBar(new Rectangle(640, 70, 120, 20), "Length", $"{length:F0}", ref length, 12.0f, 240.0f);
|
||||
GuiSliderBar(new Rectangle(640, 100, 120, 20), "Decay", $"{branchDecay:F2}", ref branchDecay, 0.1f, 0.78f);
|
||||
GuiSliderBar(new Rectangle(640, 130, 120, 20), "Depth", $"{treeDepth:F0}", ref treeDepth, 1.0f, 10.0f);
|
||||
GuiSliderBar(new Rectangle(640, 160, 120, 20), "Thick", $"{thick:F0}", ref thick, 1, 8);
|
||||
GuiCheckBox(new Rectangle(640, 190, 20, 20), "Bezier", ref bezier);
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Minimal raygui-like widgets (plain raylib re-implementation)
|
||||
//----------------------------------------------------------------------------------
|
||||
private static void GuiSliderBar(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);
|
||||
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - recursive tree");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new RecursiveTree();
|
||||
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;
|
||||
}
|
||||
}
|
||||
331
Examples/Shapes/RlglColorWheel.cs
Normal file
331
Examples/Shapes/RlglColorWheel.cs
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - rlgl color wheel
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Robin (@RobinsAviary) 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 Robin (@RobinsAviary)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
using static Raylib_cs.Rlgl;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class RlglColorWheel : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Shapes / RLGL Color Wheel";
|
||||
|
||||
public string Title => "raylib [shapes] example - rlgl color wheel";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
// The minimum/maximum points the circle can have
|
||||
private const int pointsMin = 3;
|
||||
private const int pointsMax = 256;
|
||||
|
||||
// The current number of points and the radius of the circle
|
||||
private int triangleCount;
|
||||
private float pointScale;
|
||||
|
||||
// Slider value, literally maps to value in HSV
|
||||
private float value;
|
||||
|
||||
// The center of the screen
|
||||
private Vector2 center;
|
||||
// The location of the color wheel
|
||||
private Vector2 circlePosition;
|
||||
|
||||
// The currently selected color
|
||||
private Color color;
|
||||
|
||||
// Indicates if the slider is being clicked
|
||||
private bool sliderClicked;
|
||||
|
||||
// Indicates if the current color going to be updated, as well as the handle position
|
||||
private bool settingColor;
|
||||
|
||||
// How the color wheel will be rendered
|
||||
private DrawMode renderType;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
triangleCount = 64;
|
||||
pointScale = 150.0f;
|
||||
value = 1.0f;
|
||||
|
||||
center = new Vector2((float)screenWidth / 2.0f, (float)screenHeight / 2.0f);
|
||||
circlePosition = center;
|
||||
|
||||
color = new Color(255, 255, 255, 255);
|
||||
|
||||
sliderClicked = false;
|
||||
settingColor = false;
|
||||
|
||||
renderType = DrawMode.Triangles;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
triangleCount += (int)GetMouseWheelMove();
|
||||
triangleCount = (int)Clamp((float)triangleCount, (float)pointsMin, (float)pointsMax);
|
||||
|
||||
Rectangle sliderRectangle = new Rectangle(42.0f, 16.0f + 64.0f + 45.0f, 64.0f, 16.0f);
|
||||
Vector2 mousePosition = GetMousePosition();
|
||||
|
||||
// Checks if the user is hovering over the value slider
|
||||
bool sliderHover = (mousePosition.X >= sliderRectangle.X && mousePosition.Y >= sliderRectangle.Y && mousePosition.X < sliderRectangle.X + sliderRectangle.Width && mousePosition.Y < sliderRectangle.Y + sliderRectangle.Height);
|
||||
|
||||
// Copy color as hex
|
||||
if (IsKeyDown(KeyboardKey.LeftControl) && IsKeyDown(KeyboardKey.C))
|
||||
{
|
||||
if (IsKeyPressed(KeyboardKey.C))
|
||||
{
|
||||
SetClipboardText($"#{color.R:X2}{color.G:X2}{color.B:X2}");
|
||||
}
|
||||
}
|
||||
|
||||
// Scale up the color wheel, adjusting the handle visually
|
||||
if (IsKeyDown(KeyboardKey.Up))
|
||||
{
|
||||
pointScale *= 1.025f;
|
||||
|
||||
if (pointScale > (float)screenHeight / 2.0f)
|
||||
{
|
||||
pointScale = (float)screenHeight / 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
circlePosition = Vector2Add(Vector2Multiply(Vector2Subtract(circlePosition, center), new Vector2(1.025f, 1.025f)), center);
|
||||
}
|
||||
}
|
||||
|
||||
// Scale down the wheel, adjusting the handle visually
|
||||
if (IsKeyDown(KeyboardKey.Down))
|
||||
{
|
||||
pointScale *= 0.975f;
|
||||
|
||||
if (pointScale < 32.0f)
|
||||
{
|
||||
pointScale = 32.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
circlePosition = Vector2Add(Vector2Multiply(Vector2Subtract(circlePosition, center), new Vector2(0.975f, 0.975f)), center);
|
||||
}
|
||||
|
||||
float distanceDown = Vector2Distance(center, circlePosition) / pointScale;
|
||||
float angleDown = ((Vector2Angle(new Vector2(0.0f, -pointScale), Vector2Subtract(center, circlePosition)) / MathF.PI + 1.0f) / 2.0f);
|
||||
|
||||
if (distanceDown > 1.0f)
|
||||
{
|
||||
circlePosition = Vector2Add(new Vector2(MathF.Sin(angleDown * (MathF.PI * 2.0f)) * pointScale, -MathF.Cos(angleDown * (MathF.PI * 2.0f)) * pointScale), center);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the user clicked on the color wheel
|
||||
if (IsMouseButtonPressed(MouseButton.Left) && Vector2Distance(GetMousePosition(), center) <= pointScale + 10.0f)
|
||||
{
|
||||
settingColor = true;
|
||||
}
|
||||
|
||||
// Update flag when mouse button is released
|
||||
if (IsMouseButtonReleased(MouseButton.Left)) settingColor = false;
|
||||
|
||||
// Check if the user clicked/released the slider for the color's value
|
||||
if (sliderHover && IsMouseButtonPressed(MouseButton.Left)) sliderClicked = true;
|
||||
|
||||
if (sliderClicked && IsMouseButtonReleased(MouseButton.Left)) sliderClicked = false;
|
||||
|
||||
// Update render mode accordingly
|
||||
if (IsKeyPressed(KeyboardKey.Space)) renderType = DrawMode.Lines;
|
||||
|
||||
if (IsKeyReleased(KeyboardKey.Space)) renderType = DrawMode.Triangles;
|
||||
|
||||
// If the slider or the wheel was clicked, update the current color
|
||||
if (settingColor || sliderClicked)
|
||||
{
|
||||
if (settingColor) circlePosition = GetMousePosition();
|
||||
|
||||
float distance = Vector2Distance(center, circlePosition) / pointScale;
|
||||
|
||||
float angle = ((Vector2Angle(new Vector2(0.0f, -pointScale), Vector2Subtract(center, circlePosition)) / MathF.PI + 1.0f) / 2.0f);
|
||||
if (settingColor && distance > 1.0f) circlePosition = Vector2Add(new Vector2(MathF.Sin(angle * (MathF.PI * 2.0f)) * pointScale, -MathF.Cos(angle * (MathF.PI * 2.0f)) * pointScale), center);
|
||||
|
||||
float angle360 = angle * 360.0f;
|
||||
float valueActual = Clamp(distance, 0.0f, 1.0f);
|
||||
color = ColorLerp(new Color((int)(value * 255.0f), (int)(value * 255.0f), (int)(value * 255.0f), 255), ColorFromHSV(angle360, Clamp(distance, 0.0f, 1.0f), 1.0f), valueActual);
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Begin rendering color wheel
|
||||
Begin(renderType);
|
||||
for (int i = 0; i < triangleCount; i++)
|
||||
{
|
||||
float angleOffset = ((MathF.PI * 2.0f) / (float)triangleCount);
|
||||
float angle = angleOffset * (float)i;
|
||||
float angleOffsetCalculated = ((float)i + 1) * angleOffset;
|
||||
Vector2 scale = new Vector2(pointScale, pointScale);
|
||||
|
||||
Vector2 offset = Vector2Multiply(new Vector2(MathF.Sin(angle), -MathF.Cos(angle)), scale);
|
||||
Vector2 offset2 = Vector2Multiply(new Vector2(MathF.Sin(angleOffsetCalculated), -MathF.Cos(angleOffsetCalculated)), scale);
|
||||
|
||||
Vector2 position = Vector2Add(center, offset);
|
||||
Vector2 position2 = Vector2Add(center, offset2);
|
||||
|
||||
float angleNonRadian = (angle / (2.0f * MathF.PI)) * 360.0f;
|
||||
float angleNonRadianOffset = (angleOffset / (2.0f * MathF.PI)) * 360.0f;
|
||||
|
||||
Color currentColor = ColorFromHSV(angleNonRadian, 1.0f, 1.0f);
|
||||
Color offsetColor = ColorFromHSV(angleNonRadian + angleNonRadianOffset, 1.0f, 1.0f);
|
||||
|
||||
// Input vertices differently depending on mode
|
||||
if (renderType == DrawMode.Triangles)
|
||||
{
|
||||
// RL_TRIANGLES expects three vertices per triangle
|
||||
Color4ub(currentColor.R, currentColor.G, currentColor.B, currentColor.A);
|
||||
Vertex2f(position.X, position.Y);
|
||||
Color4f(value, value, value, 1.0f);
|
||||
Vertex2f(center.X, center.Y);
|
||||
Color4ub(offsetColor.R, offsetColor.G, offsetColor.B, offsetColor.A);
|
||||
Vertex2f(position2.X, position2.Y);
|
||||
}
|
||||
else if (renderType == DrawMode.Lines)
|
||||
{
|
||||
// RL_LINES expects two vertices per line
|
||||
Color4ub(currentColor.R, currentColor.G, currentColor.B, currentColor.A);
|
||||
Vertex2f(position.X, position.Y);
|
||||
Color4ub(Color.White.R, Color.White.G, Color.White.B, Color.White.A);
|
||||
Vertex2f(center.X, center.Y);
|
||||
|
||||
Vertex2f(center.X, center.Y);
|
||||
Color4ub(offsetColor.R, offsetColor.G, offsetColor.B, offsetColor.A);
|
||||
Vertex2f(position2.X, position2.Y);
|
||||
|
||||
Vertex2f(position2.X, position2.Y);
|
||||
Color4ub(currentColor.R, currentColor.G, currentColor.B, currentColor.A);
|
||||
Vertex2f(position.X, position.Y);
|
||||
}
|
||||
}
|
||||
End();
|
||||
|
||||
// Make the handle slightly more visible overtop darker colors
|
||||
Color handleColor = Color.Black;
|
||||
|
||||
if (Vector2Distance(center, circlePosition) / pointScale <= 0.5f && value <= 0.5f)
|
||||
{
|
||||
handleColor = Color.DarkGray;
|
||||
}
|
||||
|
||||
// Draw the color handle
|
||||
DrawCircleLinesV(circlePosition, 4.0f, handleColor);
|
||||
|
||||
// Draw the color in a preview, with a darkened outline.
|
||||
DrawRectangleV(new Vector2(8.0f, 8.0f), new Vector2(64.0f, 64.0f), color);
|
||||
DrawRectangleLinesEx(new Rectangle(8.0f, 8.0f, 64.0f, 64.0f), 2.0f, ColorLerp(color, Color.Black, 0.5f));
|
||||
|
||||
// Draw current color as hex and decimal
|
||||
DrawText($"#{color.R:X2}{color.G:X2}{color.B:X2}\n({color.R}, {color.G}, {color.B})", 8, 8 + 64 + 8, 20, Color.DarkGray);
|
||||
|
||||
// Update the visuals for the copying text
|
||||
Color copyColor = Color.DarkGray;
|
||||
int textOffset = 0;
|
||||
if (IsKeyDown(KeyboardKey.LeftControl) && IsKeyDown(KeyboardKey.C))
|
||||
{
|
||||
copyColor = Color.DarkGreen;
|
||||
textOffset = 4;
|
||||
}
|
||||
|
||||
// Draw the copying text
|
||||
DrawText("press ctrl+c to copy!", 8, 425 - textOffset, 20, copyColor);
|
||||
|
||||
// Display the number of rendered triangles
|
||||
DrawText($"triangle count: {triangleCount}", 8, 395, 20, Color.DarkGray);
|
||||
|
||||
// Slider to change color's value (minimal raygui-like control, raygui is not bound in raylib-cs)
|
||||
GuiSliderBar(sliderRectangle, "value: ", "", ref value, 0.0f, 1.0f);
|
||||
|
||||
// Draw FPS next to outlined color preview
|
||||
DrawFPS(64 + 16, 8);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Minimal raygui-like widgets (plain raylib re-implementation)
|
||||
//----------------------------------------------------------------------------------
|
||||
private static void GuiSliderBar(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);
|
||||
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - rlgl color wheel");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new RlglColorWheel();
|
||||
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;
|
||||
}
|
||||
}
|
||||
201
Examples/Shapes/RlglTriangle.cs
Normal file
201
Examples/Shapes/RlglTriangle.cs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - rlgl triangle
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Robin (@RobinsAviary) 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 Robin (@RobinsAviary)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Rlgl;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class RlglTriangle : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Shapes / RLGL Triangle";
|
||||
|
||||
public string Title => "raylib [shapes] example - rlgl triangle";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
// Starting postions and rendered triangle positions
|
||||
private Vector2[] startingPositions;
|
||||
private Vector2[] trianglePositions;
|
||||
|
||||
// Currently selected vertex, -1 means none
|
||||
private int triangleIndex;
|
||||
private bool linesMode;
|
||||
private float handleRadius;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Starting postions and rendered triangle positions
|
||||
startingPositions = [new(400.0f, 150.0f), new(300.0f, 300.0f), new(500.0f, 300.0f)];
|
||||
trianglePositions = [startingPositions[0], startingPositions[1], startingPositions[2]];
|
||||
|
||||
// Currently selected vertex, -1 means none
|
||||
triangleIndex = -1;
|
||||
linesMode = false;
|
||||
handleRadius = 8.0f;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
if (IsKeyPressed(KeyboardKey.Space)) linesMode = !linesMode;
|
||||
|
||||
// Check selected vertex
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
// If the mouse is within the handle circle
|
||||
if (CheckCollisionPointCircle(GetMousePosition(), trianglePositions[i], handleRadius) &&
|
||||
IsMouseButtonDown(MouseButton.Left))
|
||||
{
|
||||
triangleIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If the user has selected a vertex, offset it by the mouse's delta this frame
|
||||
if (triangleIndex != -1)
|
||||
{
|
||||
Vector2 mouseDelta = GetMouseDelta();
|
||||
trianglePositions[triangleIndex].X += mouseDelta.X;
|
||||
trianglePositions[triangleIndex].Y += mouseDelta.Y;
|
||||
}
|
||||
|
||||
// Reset index on release
|
||||
if (IsMouseButtonReleased(MouseButton.Left)) triangleIndex = -1;
|
||||
|
||||
// Enable/disable backface culling (2-sided triangles, slower to render)
|
||||
if (IsKeyPressed(KeyboardKey.Left)) EnableBackfaceCulling();
|
||||
if (IsKeyPressed(KeyboardKey.Right)) DisableBackfaceCulling();
|
||||
|
||||
// Reset triangle vertices to starting positions and reset backface culling
|
||||
if (IsKeyPressed(KeyboardKey.R))
|
||||
{
|
||||
trianglePositions[0] = startingPositions[0];
|
||||
trianglePositions[1] = startingPositions[1];
|
||||
trianglePositions[2] = startingPositions[2];
|
||||
|
||||
EnableBackfaceCulling();
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
if (linesMode)
|
||||
{
|
||||
// Draw triangle with lines
|
||||
Begin(DrawMode.Lines);
|
||||
// Three lines, six points
|
||||
// Define color for next vertex
|
||||
Color4ub(255, 0, 0, 255);
|
||||
// Define vertex
|
||||
Vertex2f(trianglePositions[0].X, trianglePositions[0].Y);
|
||||
Color4ub(0, 255, 0, 255);
|
||||
Vertex2f(trianglePositions[1].X, trianglePositions[1].Y);
|
||||
|
||||
Color4ub(0, 255, 0, 255);
|
||||
Vertex2f(trianglePositions[1].X, trianglePositions[1].Y);
|
||||
Color4ub(0, 0, 255, 255);
|
||||
Vertex2f(trianglePositions[2].X, trianglePositions[2].Y);
|
||||
|
||||
Color4ub(0, 0, 255, 255);
|
||||
Vertex2f(trianglePositions[2].X, trianglePositions[2].Y);
|
||||
Color4ub(255, 0, 0, 255);
|
||||
Vertex2f(trianglePositions[0].X, trianglePositions[0].Y);
|
||||
End();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Draw triangle as a triangle
|
||||
Begin(DrawMode.Triangles);
|
||||
// One triangle, three points
|
||||
// Define color for next vertex
|
||||
Color4ub(255, 0, 0, 255);
|
||||
// Define vertex
|
||||
Vertex2f(trianglePositions[0].X, trianglePositions[0].Y);
|
||||
Color4ub(0, 255, 0, 255);
|
||||
Vertex2f(trianglePositions[1].X, trianglePositions[1].Y);
|
||||
Color4ub(0, 0, 255, 255);
|
||||
Vertex2f(trianglePositions[2].X, trianglePositions[2].Y);
|
||||
End();
|
||||
}
|
||||
|
||||
// Render the vertex handles, reacting to mouse movement/input
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
// Draw handle fill focused by mouse
|
||||
if (CheckCollisionPointCircle(GetMousePosition(), trianglePositions[i], handleRadius))
|
||||
DrawCircleV(trianglePositions[i], handleRadius, ColorAlpha(Color.DarkGray, 0.5f));
|
||||
|
||||
// Draw handle fill selected
|
||||
if (i == triangleIndex) DrawCircleV(trianglePositions[i], handleRadius, Color.DarkGray);
|
||||
|
||||
// Draw handle outline
|
||||
DrawCircleLinesV(trianglePositions[i], handleRadius, Color.Black);
|
||||
}
|
||||
|
||||
// Draw controls
|
||||
DrawText("SPACE: Toggle lines mode", 10, 10, 20, Color.DarkGray);
|
||||
DrawText("LEFT-RIGHT: Toggle backface culling", 10, 40, 20, Color.DarkGray);
|
||||
DrawText("MOUSE: Click and drag vertex points", 10, 70, 20, Color.DarkGray);
|
||||
DrawText("R: Reset triangle to start positions", 10, 100, 20, Color.DarkGray);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - rlgl triangle");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new RlglTriangle();
|
||||
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;
|
||||
}
|
||||
}
|
||||
308
Examples/Shapes/SimpleParticles.cs
Normal file
308
Examples/Shapes/SimpleParticles.cs
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - simple particles
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.6, last time updated with raylib 5.6
|
||||
*
|
||||
* Example contributed by Jordi Santonja (@JordSant)
|
||||
*
|
||||
* 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 Jordi Santonja (@JordSant)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class SimpleParticles : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MAX_PARTICLES = 3000; // Max number of particles
|
||||
|
||||
public string Name => "Shapes / Simple Particles";
|
||||
|
||||
public string Title => "raylib [shapes] example - simple particles";
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
private enum ParticleType
|
||||
{
|
||||
Water = 0,
|
||||
Smoke,
|
||||
Fire
|
||||
}
|
||||
|
||||
private static readonly string[] particleTypeNames = ["WATER", "SMOKE", "FIRE"];
|
||||
|
||||
private struct Particle
|
||||
{
|
||||
public ParticleType type; // Particle type (WATER, SMOKE, FIRE)
|
||||
public Vector2 position; // Particle position on screen
|
||||
public Vector2 velocity; // Particle current speed and direction
|
||||
public float radius; // Particle radius
|
||||
public Color color; // Particle color
|
||||
|
||||
public float lifeTime; // Particle life time
|
||||
public bool alive; // Particle alive: inside screen and life time
|
||||
}
|
||||
|
||||
// Circular buffer state
|
||||
private int head; // Index for the next write
|
||||
private int tail; // Index for the next read
|
||||
private Particle[] buffer; // Particle buffer array
|
||||
|
||||
// Particle emitter parameters
|
||||
private int emissionRate; // Negative: on average every -X frames. Positive: particles per frame
|
||||
private ParticleType currentType;
|
||||
private Vector2 emitterPosition;
|
||||
|
||||
private Random random;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
// Definition of particles
|
||||
buffer = new Particle[MAX_PARTICLES]; // Particle array
|
||||
head = 0;
|
||||
tail = 0;
|
||||
|
||||
// Particle emitter parameters
|
||||
emissionRate = -2; // Negative: on average every -X frames. Positive: particles per frame
|
||||
currentType = ParticleType.Water;
|
||||
emitterPosition = new(screenWidth / 2.0f, screenHeight / 2.0f);
|
||||
|
||||
random = new Random();
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Emit new particles: when emissionRate is 1, emit every frame
|
||||
if (emissionRate < 0)
|
||||
{
|
||||
if (random.Next(-emissionRate) == 0) EmitParticle(emitterPosition, currentType);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i <= emissionRate; i++) EmitParticle(emitterPosition, currentType);
|
||||
}
|
||||
|
||||
// Update the parameters of each particle
|
||||
UpdateParticles(screenWidth, screenHeight);
|
||||
|
||||
// Remove dead particles from the circular buffer
|
||||
UpdateCircularBuffer();
|
||||
|
||||
// Change Particle Emission Rate (UP/DOWN arrows)
|
||||
if (IsKeyPressed(KeyboardKey.Up)) emissionRate++;
|
||||
if (IsKeyPressed(KeyboardKey.Down)) emissionRate--;
|
||||
|
||||
// Change Particle Type (LEFT/RIGHT arrows)
|
||||
if (IsKeyPressed(KeyboardKey.Right)) currentType = (currentType == ParticleType.Fire) ? ParticleType.Water : (ParticleType)((int)currentType + 1);
|
||||
if (IsKeyPressed(KeyboardKey.Left)) currentType = (currentType == ParticleType.Water) ? ParticleType.Fire : (ParticleType)((int)currentType - 1);
|
||||
|
||||
if (IsMouseButtonDown(MouseButton.Left)) emitterPosition = GetMousePosition();
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Call the function with a loop to draw all particles
|
||||
DrawParticles();
|
||||
|
||||
// Draw UI and Instructions
|
||||
DrawRectangle(5, 5, 315, 75, Fade(Color.SkyBlue, 0.5f));
|
||||
DrawRectangleLines(5, 5, 315, 75, Color.Blue);
|
||||
|
||||
DrawText("CONTROLS:", 15, 15, 10, Color.Black);
|
||||
DrawText("UP/DOWN: Change Particle Emission Rate", 15, 35, 10, Color.Black);
|
||||
DrawText("LEFT/RIGHT: Change Particle Type (Water, Smoke, Fire)", 15, 55, 10, Color.Black);
|
||||
|
||||
if (emissionRate < 0) DrawText($"Particles every {-emissionRate} frames | Type: {particleTypeNames[(int)currentType]}", 15, 95, 10, Color.DarkGray);
|
||||
else DrawText($"{emissionRate + 1} Particles per frame | Type: {particleTypeNames[(int)currentType]}", 15, 95, 10, Color.DarkGray);
|
||||
|
||||
DrawFPS(screenWidth - 80, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
private void EmitParticle(Vector2 emitterPosition, ParticleType type)
|
||||
{
|
||||
int index = AddToCircularBuffer();
|
||||
|
||||
// If buffer is full, index is -1
|
||||
if (index != -1)
|
||||
{
|
||||
ref Particle newParticle = ref buffer[index];
|
||||
|
||||
// Fill particle properties
|
||||
newParticle.position = emitterPosition;
|
||||
newParticle.alive = true;
|
||||
newParticle.lifeTime = 0.0f;
|
||||
newParticle.type = type;
|
||||
float speed = (float)(random.Next(10)) / 5.0f;
|
||||
switch (type)
|
||||
{
|
||||
case ParticleType.Water:
|
||||
{
|
||||
newParticle.radius = 5.0f;
|
||||
newParticle.color = Color.Blue;
|
||||
} break;
|
||||
case ParticleType.Smoke:
|
||||
{
|
||||
newParticle.radius = 7.0f;
|
||||
newParticle.color = Color.Gray;
|
||||
} break;
|
||||
case ParticleType.Fire:
|
||||
{
|
||||
newParticle.radius = 10.0f;
|
||||
newParticle.color = Color.Yellow;
|
||||
speed /= 10.0f;
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
float direction = (float)(random.Next(360));
|
||||
newParticle.velocity = new(speed * MathF.Cos(direction * DEG2RAD), speed * MathF.Sin(direction * DEG2RAD));
|
||||
}
|
||||
}
|
||||
|
||||
private int AddToCircularBuffer()
|
||||
{
|
||||
int index = -1;
|
||||
|
||||
// Check if buffer full
|
||||
if (((head + 1) % MAX_PARTICLES) != tail)
|
||||
{
|
||||
// Add new particle to the head position and advance head
|
||||
index = head;
|
||||
head = (head + 1) % MAX_PARTICLES;
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private void UpdateParticles(int screenWidth, int screenHeight)
|
||||
{
|
||||
for (int i = tail; i != head; i = (i + 1) % MAX_PARTICLES)
|
||||
{
|
||||
// Update particle life and positions
|
||||
buffer[i].lifeTime += 1.0f / 60.0f; // 60 FPS -> 1/60 seconds per frame
|
||||
|
||||
switch (buffer[i].type)
|
||||
{
|
||||
case ParticleType.Water:
|
||||
{
|
||||
buffer[i].position.X += buffer[i].velocity.X;
|
||||
buffer[i].velocity.Y += 0.2f; // Gravity
|
||||
buffer[i].position.Y += buffer[i].velocity.Y;
|
||||
} break;
|
||||
case ParticleType.Smoke:
|
||||
{
|
||||
buffer[i].position.X += buffer[i].velocity.X;
|
||||
buffer[i].velocity.Y -= 0.05f; // Upwards
|
||||
buffer[i].position.Y += buffer[i].velocity.Y;
|
||||
buffer[i].radius += 0.5f; // Increment radius: smoke expands
|
||||
buffer[i].color.A -= 4; // Decrement alpha: smoke fades
|
||||
|
||||
// If alpha transparent, particle dies
|
||||
if (buffer[i].color.A < 4) buffer[i].alive = false;
|
||||
} break;
|
||||
case ParticleType.Fire:
|
||||
{
|
||||
// Add a little horizontal oscillation to fire particles
|
||||
buffer[i].position.X += buffer[i].velocity.X + MathF.Cos(buffer[i].lifeTime * 215.0f);
|
||||
buffer[i].velocity.Y -= 0.05f; // Upwards
|
||||
buffer[i].position.Y += buffer[i].velocity.Y;
|
||||
buffer[i].radius -= 0.15f; // Decrement radius: fire shrinks
|
||||
buffer[i].color.G -= 3; // Decrement green: fire turns reddish starting from yellow
|
||||
|
||||
// If radius too small, particle dies
|
||||
if (buffer[i].radius <= 0.02f) buffer[i].alive = false;
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Disable particle when out of screen
|
||||
Vector2 center = buffer[i].position;
|
||||
float radius = buffer[i].radius;
|
||||
|
||||
if ((center.X < -radius) || (center.X > (screenWidth + radius)) ||
|
||||
(center.Y < -radius) || (center.Y > (screenHeight + radius)))
|
||||
{
|
||||
buffer[i].alive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCircularBuffer()
|
||||
{
|
||||
// Update circular buffer: advance tail over dead particles
|
||||
while ((tail != head) && !buffer[tail].alive)
|
||||
{
|
||||
tail = (tail + 1) % MAX_PARTICLES;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawParticles()
|
||||
{
|
||||
for (int i = tail; i != head; i = (i + 1) % MAX_PARTICLES)
|
||||
{
|
||||
if (buffer[i].alive)
|
||||
{
|
||||
DrawCircleV(buffer[i].position,
|
||||
buffer[i].radius,
|
||||
buffer[i].color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - simple particles");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new SimpleParticles();
|
||||
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;
|
||||
}
|
||||
}
|
||||
387
Examples/Shapes/SplinesDrawing.cs
Normal file
387
Examples/Shapes/SplinesDrawing.cs
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - splines drawing
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 5.0, last time updated with raylib 5.0
|
||||
*
|
||||
* 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) 2023-2025 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class SplinesDrawing : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MAX_SPLINE_POINTS = 32;
|
||||
|
||||
public string Name => "Shapes / Splines Drawing";
|
||||
|
||||
public string Title => "raylib [shapes] example - splines drawing";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// Cubic Bezier spline control points
|
||||
// NOTE: Every segment has two control points
|
||||
private struct ControlPoint
|
||||
{
|
||||
public Vector2 start;
|
||||
public Vector2 end;
|
||||
}
|
||||
|
||||
// Spline types
|
||||
private const int SPLINE_LINEAR = 0; // Linear
|
||||
private const int SPLINE_BASIS = 1; // B-Spline
|
||||
private const int SPLINE_CATMULLROM = 2; // Catmull-Rom
|
||||
private const int SPLINE_BEZIER = 3; // Cubic Bezier
|
||||
|
||||
private Vector2[] points;
|
||||
private Vector2[] pointsInterleaved;
|
||||
|
||||
private int pointCount;
|
||||
private int selectedPoint;
|
||||
private int focusedPoint;
|
||||
private int selectedControlIndex; // -1 when none
|
||||
private bool selectedControlStart;
|
||||
private int focusedControlIndex; // -1 when none
|
||||
private bool focusedControlStart;
|
||||
|
||||
private ControlPoint[] control;
|
||||
|
||||
// Spline config variables
|
||||
private float splineThickness;
|
||||
private int splineTypeActive; // 0-Linear, 1-BSpline, 2-CatmullRom, 3-Bezier
|
||||
private bool splineTypeEditMode;
|
||||
private bool splineHelpersActive;
|
||||
|
||||
// Minimal raygui-like global lock
|
||||
private static bool guiLocked;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
points = new Vector2[MAX_SPLINE_POINTS];
|
||||
points[0] = new Vector2(50.0f, 400.0f);
|
||||
points[1] = new Vector2(160.0f, 220.0f);
|
||||
points[2] = new Vector2(340.0f, 380.0f);
|
||||
points[3] = new Vector2(520.0f, 60.0f);
|
||||
points[4] = new Vector2(710.0f, 260.0f);
|
||||
|
||||
// Array required for spline bezier-cubic,
|
||||
// including control points interleaved with start-end segment points
|
||||
pointsInterleaved = new Vector2[3 * (MAX_SPLINE_POINTS - 1) + 1];
|
||||
|
||||
pointCount = 5;
|
||||
selectedPoint = -1;
|
||||
focusedPoint = -1;
|
||||
selectedControlIndex = -1;
|
||||
focusedControlIndex = -1;
|
||||
|
||||
// Cubic Bezier control points initialization
|
||||
control = new ControlPoint[MAX_SPLINE_POINTS - 1];
|
||||
for (int i = 0; i < pointCount - 1; i++)
|
||||
{
|
||||
control[i].start = new Vector2(points[i].X + 50, points[i].Y);
|
||||
control[i].end = new Vector2(points[i + 1].X - 50, points[i + 1].Y);
|
||||
}
|
||||
|
||||
splineThickness = 8.0f;
|
||||
splineTypeActive = SPLINE_LINEAR;
|
||||
splineTypeEditMode = false;
|
||||
splineHelpersActive = true;
|
||||
|
||||
guiLocked = false;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Spline points creation logic (at the end of spline)
|
||||
if (IsMouseButtonPressed(MouseButton.Right) && (pointCount < MAX_SPLINE_POINTS))
|
||||
{
|
||||
points[pointCount] = GetMousePosition();
|
||||
int i = pointCount - 1;
|
||||
control[i].start = new Vector2(points[i].X + 50, points[i].Y);
|
||||
control[i].end = new Vector2(points[i + 1].X - 50, points[i + 1].Y);
|
||||
pointCount++;
|
||||
}
|
||||
|
||||
// Spline point focus and selection logic
|
||||
if ((selectedPoint == -1) && ((splineTypeActive != SPLINE_BEZIER) || (selectedControlIndex == -1)))
|
||||
{
|
||||
focusedPoint = -1;
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
{
|
||||
if (CheckCollisionPointCircle(GetMousePosition(), points[i], 8.0f))
|
||||
{
|
||||
focusedPoint = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (IsMouseButtonPressed(MouseButton.Left)) selectedPoint = focusedPoint;
|
||||
}
|
||||
|
||||
// Spline point movement logic
|
||||
if (selectedPoint >= 0)
|
||||
{
|
||||
points[selectedPoint] = GetMousePosition();
|
||||
if (IsMouseButtonReleased(MouseButton.Left)) selectedPoint = -1;
|
||||
}
|
||||
|
||||
// Cubic Bezier spline control points logic
|
||||
if ((splineTypeActive == SPLINE_BEZIER) && (focusedPoint == -1))
|
||||
{
|
||||
// Spline control point focus and selection logic
|
||||
if (selectedControlIndex == -1)
|
||||
{
|
||||
focusedControlIndex = -1;
|
||||
for (int i = 0; i < pointCount - 1; i++)
|
||||
{
|
||||
if (CheckCollisionPointCircle(GetMousePosition(), control[i].start, 6.0f))
|
||||
{
|
||||
focusedControlIndex = i;
|
||||
focusedControlStart = true;
|
||||
break;
|
||||
}
|
||||
else if (CheckCollisionPointCircle(GetMousePosition(), control[i].end, 6.0f))
|
||||
{
|
||||
focusedControlIndex = i;
|
||||
focusedControlStart = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (IsMouseButtonPressed(MouseButton.Left))
|
||||
{
|
||||
selectedControlIndex = focusedControlIndex;
|
||||
selectedControlStart = focusedControlStart;
|
||||
}
|
||||
}
|
||||
|
||||
// Spline control point movement logic
|
||||
if (selectedControlIndex != -1)
|
||||
{
|
||||
if (selectedControlStart) control[selectedControlIndex].start = GetMousePosition();
|
||||
else control[selectedControlIndex].end = GetMousePosition();
|
||||
if (IsMouseButtonReleased(MouseButton.Left)) selectedControlIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Spline selection logic
|
||||
if (IsKeyPressed(KeyboardKey.One)) splineTypeActive = 0;
|
||||
else if (IsKeyPressed(KeyboardKey.Two)) splineTypeActive = 1;
|
||||
else if (IsKeyPressed(KeyboardKey.Three)) splineTypeActive = 2;
|
||||
else if (IsKeyPressed(KeyboardKey.Four)) splineTypeActive = 3;
|
||||
|
||||
// Clear selection when changing to a spline without control points
|
||||
if (IsKeyPressed(KeyboardKey.One) || IsKeyPressed(KeyboardKey.Two) || IsKeyPressed(KeyboardKey.Three)) selectedControlIndex = -1;
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
if (splineTypeActive == SPLINE_LINEAR)
|
||||
{
|
||||
// Draw spline: linear
|
||||
DrawSplineLinear(points, pointCount, splineThickness, Color.Red);
|
||||
}
|
||||
else if (splineTypeActive == SPLINE_BASIS)
|
||||
{
|
||||
// Draw spline: basis
|
||||
DrawSplineBasis(points, pointCount, splineThickness, Color.Red); // Provide connected points array
|
||||
}
|
||||
else if (splineTypeActive == SPLINE_CATMULLROM)
|
||||
{
|
||||
// Draw spline: catmull-rom
|
||||
DrawSplineCatmullRom(points, pointCount, splineThickness, Color.Red); // Provide connected points array
|
||||
}
|
||||
else if (splineTypeActive == SPLINE_BEZIER)
|
||||
{
|
||||
// NOTE: Cubic-bezier spline requires the 2 control points of each segment to be
|
||||
// provided interleaved with the start and end point of every segment
|
||||
for (int i = 0; i < (pointCount - 1); i++)
|
||||
{
|
||||
pointsInterleaved[3 * i] = points[i];
|
||||
pointsInterleaved[3 * i + 1] = control[i].start;
|
||||
pointsInterleaved[3 * i + 2] = control[i].end;
|
||||
}
|
||||
|
||||
pointsInterleaved[3 * (pointCount - 1)] = points[pointCount - 1];
|
||||
|
||||
// Draw spline: cubic-bezier (with control points)
|
||||
DrawSplineBezierCubic(pointsInterleaved, 3 * (pointCount - 1) + 1, splineThickness, Color.Red);
|
||||
|
||||
// Draw spline control points
|
||||
for (int i = 0; i < pointCount - 1; i++)
|
||||
{
|
||||
// Every cubic bezier point have two control points
|
||||
DrawCircleV(control[i].start, 6, Color.Gold);
|
||||
DrawCircleV(control[i].end, 6, Color.Gold);
|
||||
if (focusedControlIndex == i && focusedControlStart) DrawCircleV(control[i].start, 8, Color.Green);
|
||||
else if (focusedControlIndex == i && !focusedControlStart) DrawCircleV(control[i].end, 8, Color.Green);
|
||||
DrawLineEx(points[i], control[i].start, 1.0f, Color.LightGray);
|
||||
DrawLineEx(points[i + 1], control[i].end, 1.0f, Color.LightGray);
|
||||
|
||||
// Draw spline control lines
|
||||
DrawLineV(points[i], control[i].start, Color.Gray);
|
||||
DrawLineV(control[i].end, points[i + 1], Color.Gray);
|
||||
}
|
||||
}
|
||||
|
||||
if (splineHelpersActive)
|
||||
{
|
||||
// Draw spline point helpers
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
{
|
||||
DrawCircleLinesV(points[i], (focusedPoint == i) ? 12.0f : 8.0f, (focusedPoint == i) ? Color.Blue : Color.DarkBlue);
|
||||
if ((splineTypeActive != SPLINE_LINEAR) &&
|
||||
(splineTypeActive != SPLINE_BEZIER) &&
|
||||
(i < pointCount - 1)) DrawLineV(points[i], points[i + 1], Color.Gray);
|
||||
|
||||
DrawText($"[{points[i].X:F0}, {points[i].Y:F0}]", (int)points[i].X, (int)points[i].Y + 10, 10, Color.Black);
|
||||
}
|
||||
}
|
||||
|
||||
// Check all possible UI states that require controls lock
|
||||
if (splineTypeEditMode || (selectedPoint != -1) || (selectedControlIndex != -1)) GuiLock();
|
||||
|
||||
// Draw spline config
|
||||
GuiLabel(new Rectangle(12, 62, 140, 24), $"Spline thickness: {(int)splineThickness}");
|
||||
GuiSliderBar(new Rectangle(12, 60 + 24, 140, 16), null, null, ref splineThickness, 1.0f, 40.0f);
|
||||
|
||||
GuiCheckBox(new Rectangle(12, 110, 20, 20), "Show point helpers", ref splineHelpersActive);
|
||||
|
||||
if (splineTypeEditMode) GuiUnlock();
|
||||
|
||||
GuiLabel(new Rectangle(12, 10, 140, 24), "Spline type:");
|
||||
if (GuiDropdownBox(new Rectangle(12, 8 + 24, 140, 28), "LINEAR;BSPLINE;CATMULLROM;BEZIER", ref splineTypeActive, splineTypeEditMode)) splineTypeEditMode = !splineTypeEditMode;
|
||||
|
||||
GuiUnlock();
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Minimal raygui-like widgets (plain raylib re-implementation)
|
||||
//----------------------------------------------------------------------------------
|
||||
private static void GuiLock() => guiLocked = true;
|
||||
private static void GuiUnlock() => guiLocked = false;
|
||||
|
||||
private static void GuiLabel(Rectangle bounds, string text)
|
||||
{
|
||||
DrawText(text, (int)bounds.X, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
|
||||
private static void GuiSliderBar(Rectangle bounds, string textLeft, string textRight, ref float value, float minValue, float maxValue)
|
||||
{
|
||||
Vector2 mouse = GetMousePosition();
|
||||
bool hover = CheckCollisionPointRec(mouse, bounds);
|
||||
if (!guiLocked && 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);
|
||||
DrawRectangleRec(new Rectangle(bounds.X, bounds.Y, bounds.Width * pct, bounds.Height), Color.SkyBlue);
|
||||
DrawRectangleLinesEx(bounds, 1, Color.Gray);
|
||||
if (!string.IsNullOrEmpty(textLeft)) DrawText(textLeft, (int)bounds.X - MeasureText(textLeft, 10) - 5, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
if (!string.IsNullOrEmpty(textRight)) DrawText(textRight, (int)(bounds.X + bounds.Width + 5), (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
}
|
||||
|
||||
private static void GuiCheckBox(Rectangle bounds, string text, ref bool active)
|
||||
{
|
||||
Vector2 mouse = GetMousePosition();
|
||||
bool hover = CheckCollisionPointRec(mouse, bounds);
|
||||
if (!guiLocked && 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 bool GuiDropdownBox(Rectangle bounds, string text, ref int active, bool editMode)
|
||||
{
|
||||
string[] items = text.Split(';');
|
||||
bool pressed = false;
|
||||
Vector2 mouse = GetMousePosition();
|
||||
bool clickable = !guiLocked;
|
||||
|
||||
bool hoverMain = CheckCollisionPointRec(mouse, bounds);
|
||||
DrawRectangleRec(bounds, editMode ? Color.SkyBlue : (hoverMain ? Color.LightGray : Color.RayWhite));
|
||||
DrawRectangleLinesEx(bounds, 1, editMode ? Color.Blue : Color.Gray);
|
||||
DrawText(items[active], (int)bounds.X + 6, (int)(bounds.Y + bounds.Height / 2 - 5), 10, Color.DarkGray);
|
||||
|
||||
if (editMode)
|
||||
{
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
Rectangle itemRec = new Rectangle(bounds.X, bounds.Y + bounds.Height * (i + 1), bounds.Width, bounds.Height);
|
||||
bool hoverItem = CheckCollisionPointRec(mouse, itemRec);
|
||||
DrawRectangleRec(itemRec, hoverItem ? Color.SkyBlue : Color.RayWhite);
|
||||
DrawRectangleLinesEx(itemRec, 1, Color.Gray);
|
||||
DrawText(items[i], (int)itemRec.X + 6, (int)(itemRec.Y + itemRec.Height / 2 - 5), 10, Color.DarkGray);
|
||||
if (clickable && hoverItem && IsMouseButtonPressed(MouseButton.Left))
|
||||
{
|
||||
active = i;
|
||||
pressed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (clickable && hoverMain && IsMouseButtonPressed(MouseButton.Left)) pressed = true;
|
||||
|
||||
return pressed;
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - splines drawing");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new SplinesDrawing();
|
||||
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;
|
||||
}
|
||||
}
|
||||
180
Examples/Shapes/StarfieldEffect.cs
Normal file
180
Examples/Shapes/StarfieldEffect.cs
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - starfield effect
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 5.5, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by JP Mortiboys (@themushroompirates) 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 JP Mortiboys (@themushroompirates)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath; // Required for: Lerp()
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class StarfieldEffect : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int STAR_COUNT = 420;
|
||||
|
||||
public string Name => "Shapes / Starfield Effect";
|
||||
|
||||
public string Title => "raylib [shapes] example - starfield effect";
|
||||
|
||||
private Color bgColor;
|
||||
|
||||
// Speed at which we fly forward
|
||||
private float speed;
|
||||
|
||||
// We're either drawing lines or circles
|
||||
private bool drawLines;
|
||||
|
||||
private Vector3[] stars;
|
||||
private Vector2[] starsScreenPos;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
bgColor = ColorLerp(Color.DarkBlue, Color.Black, 0.69f);
|
||||
|
||||
// Speed at which we fly forward
|
||||
speed = 10.0f / 9.0f;
|
||||
|
||||
// We're either drawing lines or circles
|
||||
drawLines = true;
|
||||
|
||||
stars = new Vector3[STAR_COUNT];
|
||||
starsScreenPos = new Vector2[STAR_COUNT];
|
||||
|
||||
// Setup the stars with a random position
|
||||
for (int i = 0; i < STAR_COUNT; i++)
|
||||
{
|
||||
stars[i].X = (float)GetRandomValue(-screenWidth / 2, (int)screenWidth / 2);
|
||||
stars[i].Y = (float)GetRandomValue(-screenHeight / 2, (int)screenHeight / 2);
|
||||
stars[i].Z = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Change speed based on mouse
|
||||
float mouseMove = GetMouseWheelMove();
|
||||
if ((int)mouseMove != 0) speed += 2.0f * mouseMove / 9.0f;
|
||||
if (speed < 0.0f) speed = 0.1f;
|
||||
else if (speed > 2.0f) speed = 2.0f;
|
||||
|
||||
// Toggle lines / points with space bar
|
||||
if (IsKeyPressed(KeyboardKey.Space)) drawLines = !drawLines;
|
||||
|
||||
float dt = GetFrameTime();
|
||||
for (int i = 0; i < STAR_COUNT; i++)
|
||||
{
|
||||
// Update star's timer
|
||||
stars[i].Z -= dt * speed;
|
||||
|
||||
// Calculate the screen position
|
||||
starsScreenPos[i] = new Vector2(
|
||||
screenWidth * 0.5f + stars[i].X / stars[i].Z,
|
||||
screenHeight * 0.5f + stars[i].Y / stars[i].Z
|
||||
);
|
||||
|
||||
// If the star is too old, or offscreen, it dies and we make a new random one
|
||||
if ((stars[i].Z < 0.0f) || (starsScreenPos[i].X < 0) || (starsScreenPos[i].Y < 0.0f) ||
|
||||
(starsScreenPos[i].X > screenWidth) || (starsScreenPos[i].Y > screenHeight))
|
||||
{
|
||||
stars[i].X = (float)GetRandomValue(-screenWidth / 2, screenWidth / 2);
|
||||
stars[i].Y = (float)GetRandomValue(-screenHeight / 2, screenHeight / 2);
|
||||
stars[i].Z = 1.0f;
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(bgColor);
|
||||
|
||||
for (int i = 0; i < STAR_COUNT; i++)
|
||||
{
|
||||
if (drawLines)
|
||||
{
|
||||
// Get the time a little while ago for this star, but clamp it
|
||||
float t = Clamp(stars[i].Z + 1.0f / 32.0f, 0.0f, 1.0f);
|
||||
|
||||
// If it's different enough from the current time, we proceed
|
||||
if ((t - stars[i].Z) > 1e-3)
|
||||
{
|
||||
// Calculate the screen position of the old point
|
||||
Vector2 startPos = new Vector2(
|
||||
screenWidth * 0.5f + stars[i].X / t,
|
||||
screenHeight * 0.5f + stars[i].Y / t
|
||||
);
|
||||
|
||||
// Draw a line connecting the old point to the current point
|
||||
DrawLineV(startPos, starsScreenPos[i], Color.RayWhite);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make the radius grow as the star ages
|
||||
float radius = Lerp(stars[i].Z, 1.0f, 5.0f);
|
||||
|
||||
// Draw the circle
|
||||
DrawCircleV(starsScreenPos[i], radius, Color.RayWhite);
|
||||
}
|
||||
}
|
||||
|
||||
DrawText($"[MOUSE WHEEL] Current Speed: {9.0f * speed / 2.0f:F0}", 10, 40, 20, Color.RayWhite);
|
||||
DrawText($"[SPACE] Current draw mode: {(drawLines ? "Lines" : "Circles")}", 10, 70, 20, Color.RayWhite);
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - starfield effect");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new StarfieldEffect();
|
||||
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;
|
||||
}
|
||||
}
|
||||
406
Examples/Shapes/TopDownLights.cs
Normal file
406
Examples/Shapes/TopDownLights.cs
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - top down lights
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 4.2, last time updated with raylib 4.2
|
||||
*
|
||||
* Example contributed by Jeffery Myers (@JeffM2501) 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) 2022-2025 Jeffery Myers (@JeffM2501)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath;
|
||||
using static Raylib_cs.Rlgl;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class TopDownLights : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
// Custom Blend Modes
|
||||
private const int RLGL_SRC_ALPHA = 0x0302;
|
||||
private const int RLGL_MIN = 0x8007;
|
||||
private const int RLGL_MAX = 0x8008;
|
||||
|
||||
private const int MAX_BOXES = 20;
|
||||
private const int MAX_SHADOWS = MAX_BOXES * 3; // MAX_BOXES*3 - Each box can cast up to two shadow volumes for the edges it is away from, and one for the box itself
|
||||
private const int MAX_LIGHTS = 16;
|
||||
|
||||
public string Name => "Shapes / Top Down Lights";
|
||||
|
||||
public string Title => "raylib [shapes] example - top down lights";
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// Shadow geometry type
|
||||
private class ShadowGeometry
|
||||
{
|
||||
public Vector2[] vertices = new Vector2[4];
|
||||
}
|
||||
|
||||
// Light info type
|
||||
private class LightInfo
|
||||
{
|
||||
public bool active; // Is this light slot active?
|
||||
public bool dirty; // Does this light need to be updated?
|
||||
public bool valid; // Is this light in a valid position?
|
||||
|
||||
public Vector2 position; // Light position
|
||||
public RenderTexture2D mask; // Alpha mask for the light
|
||||
public float outerRadius; // The distance the light touches
|
||||
public Rectangle bounds; // A cached rectangle of the light bounds to help with culling
|
||||
|
||||
public ShadowGeometry[] shadows = new ShadowGeometry[MAX_SHADOWS];
|
||||
public int shadowCount;
|
||||
|
||||
public LightInfo()
|
||||
{
|
||||
for (int i = 0; i < MAX_SHADOWS; i++) shadows[i] = new ShadowGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Global Variables Definition
|
||||
//------------------------------------------------------------------------------------
|
||||
private LightInfo[] lights;
|
||||
|
||||
private int boxCount;
|
||||
private Rectangle[] boxes;
|
||||
private Texture2D backgroundTexture;
|
||||
private RenderTexture2D lightMask;
|
||||
private int nextLight;
|
||||
private bool showLines;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
lights = new LightInfo[MAX_LIGHTS];
|
||||
for (int i = 0; i < MAX_LIGHTS; i++) lights[i] = new LightInfo();
|
||||
|
||||
// Initialize our 'world' of boxes
|
||||
boxCount = 0;
|
||||
boxes = new Rectangle[MAX_BOXES];
|
||||
SetupBoxes();
|
||||
|
||||
// Create a checkerboard ground texture
|
||||
Image img = GenImageChecked(64, 64, 32, 32, Color.DarkBrown, Color.DarkGray);
|
||||
backgroundTexture = LoadTextureFromImage(img);
|
||||
UnloadImage(img);
|
||||
|
||||
// Create a global light mask to hold all the blended lights
|
||||
lightMask = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());
|
||||
|
||||
// Setup initial light
|
||||
SetupLight(0, 600, 400, 300);
|
||||
nextLight = 1;
|
||||
|
||||
showLines = false;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
// Drag light 0
|
||||
if (IsMouseButtonDown(MouseButton.Left)) MoveLight(0, GetMousePosition().X, GetMousePosition().Y);
|
||||
|
||||
// Make a new light
|
||||
if (IsMouseButtonPressed(MouseButton.Right) && (nextLight < MAX_LIGHTS))
|
||||
{
|
||||
SetupLight(nextLight, GetMousePosition().X, GetMousePosition().Y, 200);
|
||||
nextLight++;
|
||||
}
|
||||
|
||||
// Toggle debug info
|
||||
if (IsKeyPressed(KeyboardKey.F1)) showLines = !showLines;
|
||||
|
||||
// Update the lights and keep track if any were dirty so we know if we need to update the master light mask
|
||||
bool dirtyLights = false;
|
||||
for (int i = 0; i < MAX_LIGHTS; i++)
|
||||
{
|
||||
if (UpdateLight(i, boxes, boxCount)) dirtyLights = true;
|
||||
}
|
||||
|
||||
// Update the light mask
|
||||
if (dirtyLights)
|
||||
{
|
||||
// Build up the light mask
|
||||
BeginTextureMode(lightMask);
|
||||
|
||||
ClearBackground(Color.Black);
|
||||
|
||||
// Force the blend mode to only set the alpha of the destination
|
||||
SetBlendFactors(RLGL_SRC_ALPHA, RLGL_SRC_ALPHA, RLGL_MIN);
|
||||
SetBlendMode(BlendMode.Custom);
|
||||
|
||||
// Merge in all the light masks
|
||||
for (int i = 0; i < MAX_LIGHTS; i++)
|
||||
{
|
||||
if (lights[i].active) DrawTextureRec(lights[i].mask.Texture, new Rectangle(0, 0, (float)GetScreenWidth(), -(float)GetScreenHeight()), Vector2Zero(), Color.White);
|
||||
}
|
||||
|
||||
DrawRenderBatchActive();
|
||||
|
||||
// Go back to normal blend
|
||||
SetBlendMode(BlendMode.Alpha);
|
||||
EndTextureMode();
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.Black);
|
||||
|
||||
// Draw the tile background
|
||||
DrawTextureRec(backgroundTexture, new Rectangle(0, 0, (float)GetScreenWidth(), (float)GetScreenHeight()), Vector2Zero(), Color.White);
|
||||
|
||||
// Overlay the shadows from all the lights
|
||||
DrawTextureRec(lightMask.Texture, new Rectangle(0, 0, (float)GetScreenWidth(), -(float)GetScreenHeight()), Vector2Zero(), ColorAlpha(Color.White, showLines ? 0.75f : 1.0f));
|
||||
|
||||
// Draw the lights
|
||||
for (int i = 0; i < MAX_LIGHTS; i++)
|
||||
{
|
||||
if (lights[i].active) DrawCircle((int)lights[i].position.X, (int)lights[i].position.Y, 10, (i == 0) ? Color.Yellow : Color.White);
|
||||
}
|
||||
|
||||
if (showLines)
|
||||
{
|
||||
for (int s = 0; s < lights[0].shadowCount; s++)
|
||||
{
|
||||
DrawTriangleFan(lights[0].shadows[s].vertices, 4, Color.DarkPurple);
|
||||
}
|
||||
|
||||
for (int b = 0; b < boxCount; b++)
|
||||
{
|
||||
if (CheckCollisionRecs(boxes[b], lights[0].bounds)) DrawRectangleRec(boxes[b], Color.Purple);
|
||||
|
||||
DrawRectangleLines((int)boxes[b].X, (int)boxes[b].Y, (int)boxes[b].Width, (int)boxes[b].Height, Color.DarkBlue);
|
||||
}
|
||||
|
||||
DrawText("(F1) Hide Shadow Volumes", 10, 50, 10, Color.Green);
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawText("(F1) Show Shadow Volumes", 10, 50, 10, Color.Green);
|
||||
}
|
||||
|
||||
DrawFPS(screenWidth - 80, 10);
|
||||
DrawText("Drag to move light #1", 10, 10, 10, Color.DarkGreen);
|
||||
DrawText("Right click to add new light", 10, 30, 10, Color.DarkGreen);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadTexture(backgroundTexture);
|
||||
UnloadRenderTexture(lightMask);
|
||||
for (int i = 0; i < MAX_LIGHTS; i++)
|
||||
{
|
||||
if (lights[i].active) UnloadRenderTexture(lights[i].mask);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//------------------------------------------------------------------------------------
|
||||
// Move a light and mark it as dirty so that we update it's mask next frame
|
||||
private void MoveLight(int slot, float x, float y)
|
||||
{
|
||||
lights[slot].dirty = true;
|
||||
lights[slot].position.X = x;
|
||||
lights[slot].position.Y = y;
|
||||
|
||||
// update the cached bounds
|
||||
lights[slot].bounds.X = x - lights[slot].outerRadius;
|
||||
lights[slot].bounds.Y = y - lights[slot].outerRadius;
|
||||
}
|
||||
|
||||
// Compute a shadow volume for the edge
|
||||
// It takes the edge and projects it back by the light radius and turns it into a quad
|
||||
private void ComputeShadowVolumeForEdge(int slot, Vector2 sp, Vector2 ep)
|
||||
{
|
||||
if (lights[slot].shadowCount >= MAX_SHADOWS) return;
|
||||
|
||||
float extension = lights[slot].outerRadius * 2;
|
||||
|
||||
Vector2 spVector = Vector2Normalize(Vector2Subtract(sp, lights[slot].position));
|
||||
Vector2 spProjection = Vector2Add(sp, Vector2Scale(spVector, extension));
|
||||
|
||||
Vector2 epVector = Vector2Normalize(Vector2Subtract(ep, lights[slot].position));
|
||||
Vector2 epProjection = Vector2Add(ep, Vector2Scale(epVector, extension));
|
||||
|
||||
lights[slot].shadows[lights[slot].shadowCount].vertices[0] = sp;
|
||||
lights[slot].shadows[lights[slot].shadowCount].vertices[1] = ep;
|
||||
lights[slot].shadows[lights[slot].shadowCount].vertices[2] = epProjection;
|
||||
lights[slot].shadows[lights[slot].shadowCount].vertices[3] = spProjection;
|
||||
|
||||
lights[slot].shadowCount++;
|
||||
}
|
||||
|
||||
// Setup a light
|
||||
private void SetupLight(int slot, float x, float y, float radius)
|
||||
{
|
||||
lights[slot].active = true;
|
||||
lights[slot].valid = false; // The light must prove it is valid
|
||||
lights[slot].mask = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());
|
||||
lights[slot].outerRadius = radius;
|
||||
|
||||
lights[slot].bounds.Width = radius * 2;
|
||||
lights[slot].bounds.Height = radius * 2;
|
||||
|
||||
MoveLight(slot, x, y);
|
||||
|
||||
// Force the render texture to have something in it
|
||||
DrawLightMask(slot);
|
||||
}
|
||||
|
||||
// See if a light needs to update it's mask
|
||||
private bool UpdateLight(int slot, Rectangle[] boxes, int count)
|
||||
{
|
||||
if (!lights[slot].active || !lights[slot].dirty) return false;
|
||||
|
||||
lights[slot].dirty = false;
|
||||
lights[slot].shadowCount = 0;
|
||||
lights[slot].valid = false;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
// Are we in a box? if so we are not valid
|
||||
if (CheckCollisionPointRec(lights[slot].position, boxes[i])) return false;
|
||||
|
||||
// If this box is outside our bounds, we can skip it
|
||||
if (!CheckCollisionRecs(lights[slot].bounds, boxes[i])) continue;
|
||||
|
||||
// Check the edges that are on the same side we are, and cast shadow volumes out from them
|
||||
|
||||
// Top
|
||||
Vector2 sp = new Vector2(boxes[i].X, boxes[i].Y);
|
||||
Vector2 ep = new Vector2(boxes[i].X + boxes[i].Width, boxes[i].Y);
|
||||
|
||||
if (lights[slot].position.Y > ep.Y) ComputeShadowVolumeForEdge(slot, sp, ep);
|
||||
|
||||
// Right
|
||||
sp = ep;
|
||||
ep.Y += boxes[i].Height;
|
||||
if (lights[slot].position.X < ep.X) ComputeShadowVolumeForEdge(slot, sp, ep);
|
||||
|
||||
// Bottom
|
||||
sp = ep;
|
||||
ep.X -= boxes[i].Width;
|
||||
if (lights[slot].position.Y < ep.Y) ComputeShadowVolumeForEdge(slot, sp, ep);
|
||||
|
||||
// Left
|
||||
sp = ep;
|
||||
ep.Y -= boxes[i].Height;
|
||||
if (lights[slot].position.X > ep.X) ComputeShadowVolumeForEdge(slot, sp, ep);
|
||||
|
||||
// The box itself
|
||||
lights[slot].shadows[lights[slot].shadowCount].vertices[0] = new Vector2(boxes[i].X, boxes[i].Y);
|
||||
lights[slot].shadows[lights[slot].shadowCount].vertices[1] = new Vector2(boxes[i].X, boxes[i].Y + boxes[i].Height);
|
||||
lights[slot].shadows[lights[slot].shadowCount].vertices[2] = new Vector2(boxes[i].X + boxes[i].Width, boxes[i].Y + boxes[i].Height);
|
||||
lights[slot].shadows[lights[slot].shadowCount].vertices[3] = new Vector2(boxes[i].X + boxes[i].Width, boxes[i].Y);
|
||||
lights[slot].shadowCount++;
|
||||
}
|
||||
|
||||
lights[slot].valid = true;
|
||||
|
||||
DrawLightMask(slot);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Draw the light and shadows to the mask for a light
|
||||
private void DrawLightMask(int slot)
|
||||
{
|
||||
// Use the light mask
|
||||
BeginTextureMode(lights[slot].mask);
|
||||
|
||||
ClearBackground(Color.White);
|
||||
|
||||
// Force the blend mode to only set the alpha of the destination
|
||||
SetBlendFactors(RLGL_SRC_ALPHA, RLGL_SRC_ALPHA, RLGL_MIN);
|
||||
SetBlendMode(BlendMode.Custom);
|
||||
|
||||
// If we are valid, then draw the light radius to the alpha mask
|
||||
if (lights[slot].valid) DrawCircleGradient(lights[slot].position, lights[slot].outerRadius, ColorAlpha(Color.White, 0), Color.White);
|
||||
|
||||
DrawRenderBatchActive();
|
||||
|
||||
// Cut out the shadows from the light radius by forcing the alpha to maximum
|
||||
SetBlendMode(BlendMode.Alpha);
|
||||
SetBlendFactors(RLGL_SRC_ALPHA, RLGL_SRC_ALPHA, RLGL_MAX);
|
||||
SetBlendMode(BlendMode.Custom);
|
||||
|
||||
// Draw the shadows to the alpha mask
|
||||
for (int i = 0; i < lights[slot].shadowCount; i++)
|
||||
{
|
||||
DrawTriangleFan(lights[slot].shadows[i].vertices, 4, Color.White);
|
||||
}
|
||||
|
||||
DrawRenderBatchActive();
|
||||
|
||||
// Go back to normal blend mode
|
||||
SetBlendMode(BlendMode.Alpha);
|
||||
|
||||
EndTextureMode();
|
||||
}
|
||||
|
||||
// Set up some boxes
|
||||
private void SetupBoxes()
|
||||
{
|
||||
boxes[0] = new Rectangle(150, 80, 40, 40);
|
||||
boxes[1] = new Rectangle(1200, 700, 40, 40);
|
||||
boxes[2] = new Rectangle(200, 600, 40, 40);
|
||||
boxes[3] = new Rectangle(1000, 50, 40, 40);
|
||||
boxes[4] = new Rectangle(500, 350, 40, 40);
|
||||
|
||||
for (int i = 5; i < MAX_BOXES; i++)
|
||||
{
|
||||
boxes[i] = new Rectangle((float)GetRandomValue(0, GetScreenWidth()), (float)GetRandomValue(0, GetScreenHeight()), (float)GetRandomValue(10, 100), (float)GetRandomValue(10, 100));
|
||||
}
|
||||
|
||||
boxCount = MAX_BOXES;
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - top down lights");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new TopDownLights();
|
||||
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;
|
||||
}
|
||||
}
|
||||
141
Examples/Shapes/TriangleStrip.cs
Normal file
141
Examples/Shapes/TriangleStrip.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - triangle strip
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Jopestpe (@jopestpe)
|
||||
*
|
||||
* 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 Jopestpe (@jopestpe)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class TriangleStrip : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Shapes / Triangle Strip";
|
||||
|
||||
public string Title => "raylib [shapes] example - triangle strip";
|
||||
|
||||
private Vector2[] points;
|
||||
private Vector2 center;
|
||||
private float segments;
|
||||
private float insideRadius;
|
||||
private float outsideRadius;
|
||||
private bool outline;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
points = new Vector2[122];
|
||||
center = new((screenWidth / 2.0f) - 125.0f, screenHeight / 2.0f);
|
||||
segments = 6.0f;
|
||||
insideRadius = 100.0f;
|
||||
outsideRadius = 150.0f;
|
||||
outline = true;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
int pointCount = (int)(segments);
|
||||
float angleStep = (360.0f / pointCount) * DEG2RAD;
|
||||
|
||||
for (int i = 0, i2 = 0; i < pointCount; i++, i2 += 2)
|
||||
{
|
||||
float angle1 = i * angleStep;
|
||||
points[i2] = new Vector2(center.X + MathF.Cos(angle1) * insideRadius, center.Y + MathF.Sin(angle1) * insideRadius);
|
||||
float angle2 = angle1 + angleStep / 2.0f;
|
||||
points[i2 + 1] = new Vector2(center.X + MathF.Cos(angle2) * outsideRadius, center.Y + MathF.Sin(angle2) * outsideRadius);
|
||||
}
|
||||
|
||||
points[pointCount * 2] = points[0];
|
||||
points[pointCount * 2 + 1] = points[1];
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
for (int i = 0; i < pointCount; i++)
|
||||
{
|
||||
Vector2 a = points[i * 2];
|
||||
Vector2 b = points[i * 2 + 1];
|
||||
Vector2 c = points[i * 2 + 2];
|
||||
Vector2 d = points[i * 2 + 3];
|
||||
|
||||
float angle1 = i * angleStep;
|
||||
DrawTriangle(c, b, a, ColorFromHSV(angle1 * RAD2DEG, 1.0f, 1.0f));
|
||||
DrawTriangle(d, b, c, ColorFromHSV((angle1 + angleStep / 2) * RAD2DEG, 1.0f, 1.0f));
|
||||
|
||||
if (outline)
|
||||
{
|
||||
DrawTriangleLines(a, b, c, Color.Black);
|
||||
DrawTriangleLines(c, b, d, Color.Black);
|
||||
}
|
||||
}
|
||||
|
||||
DrawLine(580, 0, 580, GetScreenHeight(), new Color(218, 218, 218, 255));
|
||||
DrawRectangle(580, 0, GetScreenWidth(), GetScreenHeight(), new Color(232, 232, 232, 255));
|
||||
|
||||
// Draw GUI controls
|
||||
//------------------------------------------------------------------------------
|
||||
// NOTE: raygui is not bound in raylib-cs, so the interactive controls are omitted
|
||||
// and 'segments'/'outline' keep their initial values.
|
||||
//GuiSliderBar(new Rectangle(640, 40, 120, 20), "Segments", TextFormat("%.0f", segments), ref segments, 6.0f, 60.0f);
|
||||
//GuiCheckBox(new Rectangle(640, 70, 20, 20), "Outline", ref outline);
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - triangle strip");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new TriangleStrip();
|
||||
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;
|
||||
}
|
||||
}
|
||||
154
Examples/Shapes/VectorAngle.cs
Normal file
154
Examples/Shapes/VectorAngle.cs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shapes] example - vector angle
|
||||
*
|
||||
* Example complexity rating: [★★☆☆] 2/4
|
||||
*
|
||||
* Example originally created with raylib 1.0, last time updated with raylib 5.0
|
||||
*
|
||||
* 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) 2023-2025 Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System.Numerics;
|
||||
using static Raylib_cs.Raylib;
|
||||
using static Raylib_cs.Raymath; // Required for: Vector2LineAngle()
|
||||
|
||||
namespace Examples.Shapes;
|
||||
|
||||
public partial class VectorAngle : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Shapes / Vector Angle";
|
||||
|
||||
public string Title => "raylib [shapes] example - vector angle";
|
||||
|
||||
private Vector2 v0;
|
||||
private Vector2 v1;
|
||||
private Vector2 v2; // Updated with mouse position
|
||||
|
||||
private float angle; // Angle in degrees
|
||||
private int angleMode; // 0-Vector2Angle(), 1-Vector2LineAngle()
|
||||
|
||||
public void Init()
|
||||
{
|
||||
v0 = new(screenWidth / 2.0f, screenHeight / 2.0f);
|
||||
v1 = Vector2Add(v0, new Vector2(100.0f, 80.0f));
|
||||
v2 = new(0, 0); // Updated with mouse position
|
||||
|
||||
angle = 0.0f; // Angle in degrees
|
||||
angleMode = 0; // 0-Vector2Angle(), 1-Vector2LineAngle()
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
float startangle = 0.0f;
|
||||
|
||||
if (angleMode == 0) startangle = -Vector2LineAngle(v0, v1) * RAD2DEG;
|
||||
if (angleMode == 1) startangle = 0.0f;
|
||||
|
||||
v2 = GetMousePosition();
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.Space)) angleMode = (angleMode == 0) ? 1 : 0;
|
||||
|
||||
if ((angleMode == 0) && IsMouseButtonDown(MouseButton.Right)) v1 = GetMousePosition();
|
||||
|
||||
if (angleMode == 0)
|
||||
{
|
||||
// Calculate angle between two vectors, considering a common origin (v0)
|
||||
Vector2 v1Normal = Vector2Normalize(Vector2Subtract(v1, v0));
|
||||
Vector2 v2Normal = Vector2Normalize(Vector2Subtract(v2, v0));
|
||||
|
||||
angle = Vector2Angle(v1Normal, v2Normal) * RAD2DEG;
|
||||
}
|
||||
else if (angleMode == 1)
|
||||
{
|
||||
// Calculate angle defined by a two vectors line, in reference to horizontal line
|
||||
angle = Vector2LineAngle(v0, v2) * RAD2DEG;
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
if (angleMode == 0)
|
||||
{
|
||||
DrawText("MODE 0: Angle between V1 and V2", 10, 10, 20, Color.Black);
|
||||
DrawText("Right Click to Move V2", 10, 30, 20, Color.DarkGray);
|
||||
|
||||
DrawLineEx(v0, v1, 2.0f, Color.Black);
|
||||
DrawLineEx(v0, v2, 2.0f, Color.Red);
|
||||
|
||||
DrawCircleSector(v0, 40.0f, startangle, startangle + angle, 32, Fade(Color.Green, 0.6f));
|
||||
}
|
||||
else if (angleMode == 1)
|
||||
{
|
||||
DrawText("MODE 1: Angle formed by line V1 to V2", 10, 10, 20, Color.Black);
|
||||
|
||||
DrawLine(0, screenHeight / 2, screenWidth, screenHeight / 2, Color.LightGray);
|
||||
DrawLineEx(v0, v2, 2.0f, Color.Red);
|
||||
|
||||
DrawCircleSector(v0, 40.0f, startangle, startangle - angle, 32, Fade(Color.Green, 0.6f));
|
||||
}
|
||||
|
||||
DrawText("v0", (int)v0.X, (int)v0.Y, 10, Color.DarkGray);
|
||||
|
||||
// If the line from v0 to v1 would overlap the text, move it's position up 10
|
||||
if (angleMode == 0 && Vector2Subtract(v0, v1).Y > 0.0f) DrawText("v1", (int)v1.X, (int)v1.Y - 10, 10, Color.DarkGray);
|
||||
if (angleMode == 0 && Vector2Subtract(v0, v1).Y < 0.0f) DrawText("v1", (int)v1.X, (int)v1.Y, 10, Color.DarkGray);
|
||||
|
||||
// If angle mode 1, use v1 to emphasize the horizontal line
|
||||
if (angleMode == 1) DrawText("v1", (int)v0.X + 40, (int)v0.Y, 10, Color.DarkGray);
|
||||
|
||||
// position adjusted by -10 so it isn't hidden by cursor
|
||||
DrawText("v2", (int)v2.X - 10, (int)v2.Y - 10, 10, Color.DarkGray);
|
||||
|
||||
DrawText("Press SPACE to change MODE", 460, 10, 20, Color.DarkGray);
|
||||
DrawText($"ANGLE: {angle:F2}", 10, 70, 20, Color.Lime);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shapes] example - vector angle");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new VectorAngle();
|
||||
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;
|
||||
}
|
||||
}
|
||||
312
Examples/Text/InlineStyling.cs
Normal file
312
Examples/Text/InlineStyling.cs
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [text] example - inline styling
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by Wagner Barongello (@SultansOfCode) 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 Wagner Barongello (@SultansOfCode) and Ramon Santamaria (@raysan5)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Raylib_cs;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Text;
|
||||
|
||||
public partial class InlineStyling : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
public string Name => "Text / Inline Styling";
|
||||
|
||||
public string Title => "raylib [text] example - inline styling";
|
||||
|
||||
private Vector2 textSize; // Measure text box for provided font and text
|
||||
private Color colRandom; // Random color used on text
|
||||
private int frameCounter; // Used to generate a new random color every certain frames
|
||||
|
||||
public void Init()
|
||||
{
|
||||
textSize = new(0, 0);
|
||||
colRandom = Color.Red;
|
||||
frameCounter = 0;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
frameCounter++;
|
||||
|
||||
if ((frameCounter % 20) == 0)
|
||||
{
|
||||
colRandom.R = (byte)GetRandomValue(0, 255);
|
||||
colRandom.G = (byte)GetRandomValue(0, 255);
|
||||
colRandom.B = (byte)GetRandomValue(0, 255);
|
||||
colRandom.A = 255;
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
// Text inline styling strategy used: [ ] delimiters for format
|
||||
// - Define foreground color: [cRRGGBBAA]
|
||||
// - Define background color: [bRRGGBBAA]
|
||||
// - Reset formating: [r]
|
||||
// Colors defined with [cRRGGBBAA] or [bRRGGBBAA] are multiplied by the base color alpha
|
||||
// This allows global transparency control while keeping per-section styling (ex. text fade effects)
|
||||
// Example: [bAA00AAFF][cFF0000FF]red text on gray background[r] normal text
|
||||
|
||||
DrawTextStyled(GetFontDefault(), "This changes the [cFF0000FF]foreground color[r] of provided text!!!",
|
||||
new Vector2(100, 80), 20.0f, 2.0f, Color.Black);
|
||||
|
||||
DrawTextStyled(GetFontDefault(), "This changes the [bFF00FFFF]background color[r] of provided text!!!",
|
||||
new Vector2(100, 120), 20.0f, 2.0f, Color.Black);
|
||||
|
||||
DrawTextStyled(GetFontDefault(), "This changes the [c00ff00ff][bff0000ff]foreground and background colors[r]!!!",
|
||||
new Vector2(100, 160), 20.0f, 2.0f, Color.Black);
|
||||
|
||||
DrawTextStyled(GetFontDefault(), "This changes the [c00ff00ff]alpha[r] relative [cffffffff][b000000ff]from source[r] [cff000088]color[r]!!!",
|
||||
new Vector2(100, 200), 20.0f, 2.0f, new Color(0, 0, 0, 100));
|
||||
|
||||
// Get pointer to formated text
|
||||
string text = $"Let's be [c{colRandom.R:x2}{colRandom.G:x2}{colRandom.B:x2}FF]CREATIVE[r] !!!";
|
||||
DrawTextStyled(GetFontDefault(), text, new Vector2(100, 240), 40.0f, 2.0f, Color.Black);
|
||||
|
||||
textSize = MeasureTextStyled(GetFontDefault(), text, 40.0f, 2.0f);
|
||||
DrawRectangleLines(100, 240, (int)textSize.X, (int)textSize.Y, Color.Green);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
// Draw text using inline styling
|
||||
// PARAM: color is the default text color, background color is BLANK by default
|
||||
// NOTE: Using input color as the base alpha multiplied to inline styles
|
||||
private static unsafe void DrawTextStyled(Font font, string text, Vector2 position, float fontSize, float spacing, Color color)
|
||||
{
|
||||
// Text inline styling strategy used: [ ] delimiters for format
|
||||
// - Define foreground color: [cRRGGBBAA]
|
||||
// - Define background color: [bRRGGBBAA]
|
||||
// - Reset formating: [r]
|
||||
// Example: [bAA00AAFF][cFF0000FF]red text on gray background[r] normal text
|
||||
|
||||
if (font.Texture.Id == 0)
|
||||
{
|
||||
font = GetFontDefault();
|
||||
}
|
||||
|
||||
using var textNative = new Utf8Buffer(text);
|
||||
sbyte* t = textNative.AsPointer();
|
||||
int textLen = Encoding.UTF8.GetByteCount(text);
|
||||
|
||||
Color colFront = color;
|
||||
Color colBack = Color.Blank;
|
||||
int backRecPadding = 4; // Background rectangle padding
|
||||
|
||||
float textOffsetY = 0.0f;
|
||||
float textOffsetX = 0.0f;
|
||||
float textLineSpacing = 0.0f;
|
||||
float scaleFactor = fontSize / font.BaseSize;
|
||||
|
||||
for (int i = 0; i < textLen;)
|
||||
{
|
||||
int codepointByteCount = 0;
|
||||
int codepoint = GetCodepointNext(&t[i], &codepointByteCount);
|
||||
|
||||
if (codepoint == '\n')
|
||||
{
|
||||
textOffsetY += (fontSize + textLineSpacing);
|
||||
textOffsetX = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (codepoint == '[') // Process pipe styling
|
||||
{
|
||||
if (((i + 2) < textLen) && ((char)t[i + 1] == 'r') && ((char)t[i + 2] == ']')) // Reset styling
|
||||
{
|
||||
colFront = color;
|
||||
colBack = Color.Blank;
|
||||
|
||||
i += 3; // Skip "[r]"
|
||||
continue; // Do not draw characters
|
||||
}
|
||||
else if (((i + 1) < textLen) && (((char)t[i + 1] == 'c') || ((char)t[i + 1] == 'b')))
|
||||
{
|
||||
i += 2; // Skip "[c" or "[b" to start parsing color
|
||||
|
||||
// Parse following color
|
||||
var colHexText = new StringBuilder();
|
||||
int colHexCount = 0;
|
||||
while ((i + colHexCount < textLen) && (t[i + colHexCount] != 0) && ((char)t[i + colHexCount] != ']'))
|
||||
{
|
||||
char ch = (char)t[i + colHexCount];
|
||||
if (((ch >= '0') && (ch <= '9')) ||
|
||||
((ch >= 'A') && (ch <= 'F')) ||
|
||||
((ch >= 'a') && (ch <= 'f')))
|
||||
{
|
||||
colHexText.Append(ch);
|
||||
colHexCount++;
|
||||
}
|
||||
else break; // Only affects while loop
|
||||
}
|
||||
|
||||
// Convert hex color text into actual Color
|
||||
uint colHexValue = colHexText.Length > 0 ? Convert.ToUInt32(colHexText.ToString(), 16) : 0;
|
||||
if ((char)t[i - 1] == 'c')
|
||||
{
|
||||
colFront = GetColor(colHexValue);
|
||||
}
|
||||
else if ((char)t[i - 1] == 'b')
|
||||
{
|
||||
colBack = GetColor(colHexValue);
|
||||
}
|
||||
|
||||
i += (colHexCount + 1); // Skip color value retrieved and ']'
|
||||
continue; // Do not draw characters
|
||||
}
|
||||
}
|
||||
|
||||
int index = GetGlyphIndex(font, codepoint);
|
||||
float increaseX = 0.0f;
|
||||
|
||||
if (font.Glyphs[index].AdvanceX == 0) increaseX = (font.Recs[index].Width * scaleFactor + spacing);
|
||||
else increaseX += (font.Glyphs[index].AdvanceX * scaleFactor + spacing);
|
||||
|
||||
// Draw background rectangle color (if required)
|
||||
if (colBack.A > 0) DrawRectangleRec(new Rectangle(position.X + textOffsetX, position.Y + textOffsetY - backRecPadding, increaseX, fontSize + 2 * backRecPadding), colBack);
|
||||
|
||||
if ((codepoint != ' ') && (codepoint != '\t'))
|
||||
{
|
||||
DrawTextCodepoint(font, codepoint, new Vector2(position.X + textOffsetX, position.Y + textOffsetY), fontSize, colFront);
|
||||
}
|
||||
|
||||
textOffsetX += increaseX;
|
||||
}
|
||||
|
||||
i += codepointByteCount;
|
||||
}
|
||||
}
|
||||
|
||||
// Measure inline styled text
|
||||
// NOTE: Measuring styled text requires skipping styling data
|
||||
// WARNING: Not considering line breaks
|
||||
private static unsafe Vector2 MeasureTextStyled(Font font, string text, float fontSize, float spacing)
|
||||
{
|
||||
Vector2 textSize = new(0, 0);
|
||||
|
||||
if ((font.Texture.Id == 0) || (text == null) || (text.Length == 0)) return textSize; // Security check
|
||||
|
||||
using var textNative = new Utf8Buffer(text);
|
||||
sbyte* t = textNative.AsPointer();
|
||||
int textLen = Encoding.UTF8.GetByteCount(text); // Get size in bytes of text
|
||||
|
||||
float textWidth = 0.0f;
|
||||
float textHeight = fontSize;
|
||||
float scaleFactor = fontSize / (float)font.BaseSize;
|
||||
|
||||
int codepoint = 0; // Current character
|
||||
int index = 0; // Index position in sprite font
|
||||
int validCodepointCounter = 0;
|
||||
|
||||
for (int i = 0; i < textLen;)
|
||||
{
|
||||
int codepointByteCount = 0;
|
||||
codepoint = GetCodepointNext(&t[i], &codepointByteCount);
|
||||
|
||||
if (codepoint == '[') // Ignore pipe inline styling
|
||||
{
|
||||
if (((i + 2) < textLen) && ((char)t[i + 1] == 'r') && ((char)t[i + 2] == ']')) // Reset styling
|
||||
{
|
||||
i += 3; // Skip "[r]"
|
||||
continue; // Do not measure characters
|
||||
}
|
||||
else if (((i + 1) < textLen) && (((char)t[i + 1] == 'c') || ((char)t[i + 1] == 'b')))
|
||||
{
|
||||
i += 2; // Skip "[c" or "[b" to start parsing color
|
||||
|
||||
int colHexCount = 0;
|
||||
while ((i + colHexCount < textLen) && (t[i + colHexCount] != 0) && ((char)t[i + colHexCount] != ']'))
|
||||
{
|
||||
char ch = (char)t[i + colHexCount];
|
||||
if (((ch >= '0') && (ch <= '9')) ||
|
||||
((ch >= 'A') && (ch <= 'F')) ||
|
||||
((ch >= 'a') && (ch <= 'f')))
|
||||
{
|
||||
colHexCount++;
|
||||
}
|
||||
else break; // Only affects while loop
|
||||
}
|
||||
|
||||
i += (colHexCount + 1); // Skip color value retrieved and ']'
|
||||
continue; // Do not measure characters
|
||||
}
|
||||
}
|
||||
else if (codepoint != '\n')
|
||||
{
|
||||
index = GetGlyphIndex(font, codepoint);
|
||||
|
||||
if (font.Glyphs[index].AdvanceX > 0) textWidth += font.Glyphs[index].AdvanceX;
|
||||
else textWidth += (font.Recs[index].Width + font.Glyphs[index].OffsetX);
|
||||
|
||||
validCodepointCounter++;
|
||||
i += codepointByteCount;
|
||||
}
|
||||
}
|
||||
|
||||
textSize.X = textWidth * scaleFactor + (validCodepointCounter - 1) * spacing;
|
||||
textSize.Y = textHeight;
|
||||
|
||||
return textSize;
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [text] example - inline styling");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new InlineStyling();
|
||||
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;
|
||||
}
|
||||
}
|
||||
530
Examples/Text/StringsManagement.cs
Normal file
530
Examples/Text/StringsManagement.cs
Normal file
|
|
@ -0,0 +1,530 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [text] example - strings management
|
||||
*
|
||||
* Example complexity rating: [★★★☆] 3/4
|
||||
*
|
||||
* Example originally created with raylib 6.0, last time updated with raylib 6.0
|
||||
*
|
||||
* Example contributed by David Buzatto (@davidbuzatto) 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 David Buzatto (@davidbuzatto)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Raylib_cs;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Text;
|
||||
|
||||
public partial class StringsManagement : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
private const int MaxTextLength = 100;
|
||||
private const int MaxTextParticles = 100;
|
||||
private const int FontSize = 30;
|
||||
|
||||
public string Name => "Text / Strings Management";
|
||||
|
||||
public string Title => "raylib [text] example - strings management";
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
private class TextParticle
|
||||
{
|
||||
public string Text;
|
||||
public Rectangle Rect; // Boundary
|
||||
public Vector2 Vel; // Velocity
|
||||
public Vector2 Ppos; // Previous position
|
||||
public float Padding;
|
||||
public float BorderWidth;
|
||||
public float Friction;
|
||||
public float Elasticity;
|
||||
public Color Color;
|
||||
public bool Grabbed;
|
||||
}
|
||||
|
||||
private List<TextParticle> textParticles;
|
||||
private TextParticle grabbedTextParticle;
|
||||
private Vector2 pressOffset;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
textParticles = new();
|
||||
grabbedTextParticle = null;
|
||||
pressOffset = new(0, 0);
|
||||
|
||||
PrepareFirstTextParticle("raylib => fun videogames programming!");
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
float delta = GetFrameTime();
|
||||
Vector2 mousePos = GetMousePosition();
|
||||
|
||||
// Checks if a text particle was grabbed
|
||||
if (IsMouseButtonPressed(MouseButton.Left))
|
||||
{
|
||||
for (int i = textParticles.Count - 1; i >= 0; i--)
|
||||
{
|
||||
TextParticle tp = textParticles[i];
|
||||
if (CheckCollisionPointRec(mousePos, tp.Rect))
|
||||
{
|
||||
pressOffset.X = mousePos.X - tp.Rect.X;
|
||||
pressOffset.Y = mousePos.Y - tp.Rect.Y;
|
||||
tp.Grabbed = true;
|
||||
grabbedTextParticle = tp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Releases any text particle the was grabbed
|
||||
if (IsMouseButtonReleased(MouseButton.Left))
|
||||
{
|
||||
if (grabbedTextParticle != null)
|
||||
{
|
||||
grabbedTextParticle.Grabbed = false;
|
||||
grabbedTextParticle = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Slice os shatter a text particle
|
||||
if (IsMouseButtonPressed(MouseButton.Right))
|
||||
{
|
||||
for (int i = textParticles.Count - 1; i >= 0; i--)
|
||||
{
|
||||
TextParticle tp = textParticles[i];
|
||||
if (CheckCollisionPointRec(mousePos, tp.Rect))
|
||||
{
|
||||
if (IsKeyDown(KeyboardKey.LeftShift))
|
||||
{
|
||||
ShatterTextParticle(tp, i);
|
||||
}
|
||||
else
|
||||
{
|
||||
SliceTextParticle(tp, i, tp.Text.Length / 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shake text particles
|
||||
if (IsMouseButtonPressed(MouseButton.Middle))
|
||||
{
|
||||
for (int i = 0; i < textParticles.Count; i++)
|
||||
{
|
||||
if (!textParticles[i].Grabbed)
|
||||
{
|
||||
textParticles[i].Vel = new Vector2(GetRandomValue(-2000, 2000), GetRandomValue(-2000, 2000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset using TextTo* functions
|
||||
if (IsKeyPressed(KeyboardKey.One)) PrepareFirstTextParticle("raylib => fun videogames programming!");
|
||||
if (IsKeyPressed(KeyboardKey.Two)) PrepareFirstTextParticle(TextToUpper("raylib => fun videogames programming!"));
|
||||
if (IsKeyPressed(KeyboardKey.Three)) PrepareFirstTextParticle(TextToLower("raylib => fun videogames programming!"));
|
||||
if (IsKeyPressed(KeyboardKey.Four)) PrepareFirstTextParticle(TextToPascal("raylib_fun_videogames_programming"));
|
||||
if (IsKeyPressed(KeyboardKey.Five)) PrepareFirstTextParticle(TextToSnake("RaylibFunVideogamesProgramming"));
|
||||
if (IsKeyPressed(KeyboardKey.Six)) PrepareFirstTextParticle(TextToCamel("raylib_fun_videogames_programming"));
|
||||
|
||||
// Slice by char pressed only when we have one text particle
|
||||
int charPressed = GetCharPressed();
|
||||
if ((charPressed >= 'A') && (charPressed <= 'z') && (textParticles.Count == 1))
|
||||
{
|
||||
SliceTextParticleByChar(textParticles[0], (char)charPressed);
|
||||
}
|
||||
|
||||
// Updates each text particle state
|
||||
for (int i = 0; i < textParticles.Count; i++)
|
||||
{
|
||||
TextParticle tp = textParticles[i];
|
||||
|
||||
// The text particle is not grabbed
|
||||
if (!tp.Grabbed)
|
||||
{
|
||||
// text particle repositioning using the velocity
|
||||
tp.Rect.X += tp.Vel.X * delta;
|
||||
tp.Rect.Y += tp.Vel.Y * delta;
|
||||
|
||||
// Does the text particle hit the screen right boundary?
|
||||
if ((tp.Rect.X + tp.Rect.Width) >= screenWidth)
|
||||
{
|
||||
tp.Rect.X = screenWidth - tp.Rect.Width; // Text particle repositioning
|
||||
tp.Vel.X = -tp.Vel.X * tp.Elasticity; // Elasticity makes the text particle lose 10% of its velocity on hit
|
||||
}
|
||||
// Does the text particle hit the screen left boundary?
|
||||
else if (tp.Rect.X <= 0)
|
||||
{
|
||||
tp.Rect.X = 0.0f;
|
||||
tp.Vel.X = -tp.Vel.X * tp.Elasticity;
|
||||
}
|
||||
|
||||
// The same for y axis
|
||||
if ((tp.Rect.Y + tp.Rect.Height) >= screenHeight)
|
||||
{
|
||||
tp.Rect.Y = screenHeight - tp.Rect.Height;
|
||||
tp.Vel.Y = -tp.Vel.Y * tp.Elasticity;
|
||||
}
|
||||
else if (tp.Rect.Y <= 0)
|
||||
{
|
||||
tp.Rect.Y = 0.0f;
|
||||
tp.Vel.Y = -tp.Vel.Y * tp.Elasticity;
|
||||
}
|
||||
|
||||
// Friction makes the text particle lose 1% of its velocity each frame
|
||||
tp.Vel.X = tp.Vel.X * tp.Friction;
|
||||
tp.Vel.Y = tp.Vel.Y * tp.Friction;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Text particle repositioning using the mouse position
|
||||
tp.Rect.X = mousePos.X - pressOffset.X;
|
||||
tp.Rect.Y = mousePos.Y - pressOffset.Y;
|
||||
|
||||
// While the text particle is grabbed, recalculates its velocity
|
||||
tp.Vel.X = (tp.Rect.X - tp.Ppos.X) / delta;
|
||||
tp.Vel.Y = (tp.Rect.Y - tp.Ppos.Y) / delta;
|
||||
tp.Ppos.X = tp.Rect.X;
|
||||
tp.Ppos.Y = tp.Rect.Y;
|
||||
|
||||
// Glue text particles when dragging and pressing left ctrl
|
||||
if (IsKeyDown(KeyboardKey.LeftControl))
|
||||
{
|
||||
for (int j = 0; j < textParticles.Count; j++)
|
||||
{
|
||||
if (textParticles[j] != grabbedTextParticle && grabbedTextParticle.Grabbed)
|
||||
{
|
||||
if (CheckCollisionRecs(grabbedTextParticle.Rect, textParticles[j].Rect))
|
||||
{
|
||||
GlueTextParticles(grabbedTextParticle, textParticles[j]);
|
||||
grabbedTextParticle = textParticles[textParticles.Count - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
for (int i = 0; i < textParticles.Count; i++)
|
||||
{
|
||||
TextParticle tp = textParticles[i];
|
||||
DrawRectangleRec(new Rectangle(tp.Rect.X - tp.BorderWidth, tp.Rect.Y - tp.BorderWidth, tp.Rect.Width + tp.BorderWidth * 2, tp.Rect.Height + tp.BorderWidth * 2), Color.Black);
|
||||
DrawRectangleRec(tp.Rect, tp.Color);
|
||||
DrawText(tp.Text, (int)(tp.Rect.X + tp.Padding), (int)(tp.Rect.Y + tp.Padding), FontSize, Color.Black);
|
||||
}
|
||||
|
||||
DrawText("grab a text particle by pressing with the mouse and throw it by releasing", 10, 10, 10, Color.DarkGray);
|
||||
DrawText("slice a text particle by pressing it with the mouse right button", 10, 30, 10, Color.DarkGray);
|
||||
DrawText("shatter a text particle keeping left shift pressed and pressing it with the mouse right button", 10, 50, 10, Color.DarkGray);
|
||||
DrawText("glue text particles by grabbing than and keeping left control pressed", 10, 70, 10, Color.DarkGray);
|
||||
DrawText("1 to 6 to reset", 10, 90, 10, Color.DarkGray);
|
||||
DrawText("when you have only one text particle, you can slice it by pressing a char", 10, 110, 10, Color.DarkGray);
|
||||
DrawText($"TEXT PARTICLE COUNT: {textParticles.Count}", 10, GetScreenHeight() - 30, 20, Color.Black);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------
|
||||
// Module Functions Definition
|
||||
//----------------------------------------------------------------------------------
|
||||
private void PrepareFirstTextParticle(string text)
|
||||
{
|
||||
TextParticle first = CreateTextParticle(
|
||||
text,
|
||||
GetScreenWidth() / 2.0f,
|
||||
GetScreenHeight() / 2.0f,
|
||||
Color.RayWhite
|
||||
);
|
||||
|
||||
textParticles.Clear();
|
||||
textParticles.Add(first);
|
||||
}
|
||||
|
||||
private static TextParticle CreateTextParticle(string text, float x, float y, Color color)
|
||||
{
|
||||
TextParticle tp = new()
|
||||
{
|
||||
Text = "",
|
||||
Rect = new Rectangle(x, y, 30, 30),
|
||||
Vel = new Vector2(GetRandomValue(-200, 200), GetRandomValue(-200, 200)),
|
||||
Ppos = new Vector2(0, 0),
|
||||
Padding = 5.0f,
|
||||
BorderWidth = 5.0f,
|
||||
Friction = 0.99f,
|
||||
Elasticity = 0.9f,
|
||||
Color = color,
|
||||
Grabbed = false
|
||||
};
|
||||
|
||||
// Emulate C TextCopy() into a fixed size buffer
|
||||
if (text.Length > MaxTextLength - 1)
|
||||
{
|
||||
text = text.Substring(0, MaxTextLength - 1);
|
||||
}
|
||||
tp.Text = text;
|
||||
|
||||
tp.Rect.Width = MeasureText(tp.Text, FontSize) + tp.Padding * 2;
|
||||
tp.Rect.Height = FontSize + tp.Padding * 2;
|
||||
return tp;
|
||||
}
|
||||
|
||||
private void SliceTextParticle(TextParticle tp, int particlePos, int sliceLength)
|
||||
{
|
||||
int length = tp.Text.Length;
|
||||
|
||||
if ((length > 1) && ((textParticles.Count + length) < MaxTextParticles))
|
||||
{
|
||||
for (int i = 0; i < length; i += sliceLength)
|
||||
{
|
||||
string text = sliceLength == 1 ? tp.Text[i].ToString() : Subtext(tp.Text, i, sliceLength);
|
||||
textParticles.Add(CreateTextParticle(
|
||||
text,
|
||||
tp.Rect.X + i * tp.Rect.Width / length,
|
||||
tp.Rect.Y,
|
||||
new Color(GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255)
|
||||
));
|
||||
}
|
||||
RealocateTextParticles(particlePos);
|
||||
}
|
||||
}
|
||||
|
||||
private void SliceTextParticleByChar(TextParticle tp, char charToSlice)
|
||||
{
|
||||
string[] tokens = tp.Text.Split(charToSlice);
|
||||
int tokenCount = tokens.Length;
|
||||
|
||||
if (tokenCount > 1)
|
||||
{
|
||||
int textLength = tp.Text.Length;
|
||||
for (int i = 0; i < textLength; i++)
|
||||
{
|
||||
if (tp.Text[i] == charToSlice)
|
||||
{
|
||||
textParticles.Add(CreateTextParticle(
|
||||
charToSlice.ToString(),
|
||||
tp.Rect.X,
|
||||
tp.Rect.Y,
|
||||
new Color(GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255)
|
||||
));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < tokenCount; i++)
|
||||
{
|
||||
int tokenLength = tokens[i].Length;
|
||||
textParticles.Add(CreateTextParticle(
|
||||
tokens[i],
|
||||
tp.Rect.X + i * tp.Rect.Width / tokenLength,
|
||||
tp.Rect.Y,
|
||||
new Color(GetRandomValue(0, 255), GetRandomValue(0, 255), GetRandomValue(0, 255), 255)
|
||||
));
|
||||
}
|
||||
RealocateTextParticles(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShatterTextParticle(TextParticle tp, int particlePos)
|
||||
{
|
||||
SliceTextParticle(tp, particlePos, 1);
|
||||
}
|
||||
|
||||
private void GlueTextParticles(TextParticle grabbed, TextParticle target)
|
||||
{
|
||||
int p1 = textParticles.IndexOf(grabbed);
|
||||
int p2 = textParticles.IndexOf(target);
|
||||
|
||||
if ((p1 != -1) && (p2 != -1))
|
||||
{
|
||||
TextParticle tp = CreateTextParticle(
|
||||
grabbed.Text + target.Text,
|
||||
grabbed.Rect.X,
|
||||
grabbed.Rect.Y,
|
||||
Color.RayWhite
|
||||
);
|
||||
tp.Grabbed = true;
|
||||
textParticles.Add(tp);
|
||||
grabbed.Grabbed = false;
|
||||
if (p1 < p2)
|
||||
{
|
||||
RealocateTextParticles(p2);
|
||||
RealocateTextParticles(p1);
|
||||
}
|
||||
else
|
||||
{
|
||||
RealocateTextParticles(p1);
|
||||
RealocateTextParticles(p2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RealocateTextParticles(int particlePos)
|
||||
{
|
||||
textParticles.RemoveAt(particlePos);
|
||||
}
|
||||
|
||||
// Extract a substring, clamping length to the available characters (like raylib TextSubtext)
|
||||
private static string Subtext(string text, int position, int length)
|
||||
{
|
||||
if (position >= text.Length)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
int maxLength = text.Length - position;
|
||||
if (length > maxLength)
|
||||
{
|
||||
length = maxLength;
|
||||
}
|
||||
|
||||
return text.Substring(position, length);
|
||||
}
|
||||
|
||||
// C# equivalents of raylib TextTo* helpers (behaviour kept identical)
|
||||
private static string TextToUpper(string text)
|
||||
{
|
||||
var sb = new StringBuilder(text.Length);
|
||||
foreach (char c in text)
|
||||
{
|
||||
sb.Append((c >= 'a' && c <= 'z') ? (char)(c - 32) : c);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string TextToLower(string text)
|
||||
{
|
||||
var sb = new StringBuilder(text.Length);
|
||||
foreach (char c in text)
|
||||
{
|
||||
sb.Append((c >= 'A' && c <= 'Z') ? (char)(c + 32) : c);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string TextToPascal(string text)
|
||||
{
|
||||
var sb = new StringBuilder(text.Length);
|
||||
|
||||
if (text.Length > 0)
|
||||
{
|
||||
sb.Append(char.ToUpperInvariant(text[0]));
|
||||
|
||||
for (int i = 1; i < text.Length; i++)
|
||||
{
|
||||
if (text[i] == '_' && (i + 1) < text.Length)
|
||||
{
|
||||
sb.Append(char.ToUpperInvariant(text[i + 1]));
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(text[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string TextToSnake(string text)
|
||||
{
|
||||
var sb = new StringBuilder(text.Length);
|
||||
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
char c = text[i];
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
{
|
||||
if (i > 0)
|
||||
{
|
||||
sb.Append('_');
|
||||
}
|
||||
sb.Append((char)(c + 32));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string TextToCamel(string text)
|
||||
{
|
||||
var sb = new StringBuilder(text.Length);
|
||||
|
||||
if (text.Length > 0)
|
||||
{
|
||||
sb.Append(char.ToLowerInvariant(text[0]));
|
||||
|
||||
for (int i = 1; i < text.Length; i++)
|
||||
{
|
||||
if (text[i] == '_' && (i + 1) < text.Length)
|
||||
{
|
||||
sb.Append(char.ToUpperInvariant(text[i + 1]));
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(text[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
InitWindow(screenWidth, screenHeight, "raylib [text] example - strings management");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new StringsManagement();
|
||||
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;
|
||||
}
|
||||
}
|
||||
739
Examples/Text/Text3D.cs
Normal file
739
Examples/Text/Text3D.cs
Normal file
|
|
@ -0,0 +1,739 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [text] example - 3d drawing
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* NOTE: Draw a 2D text in 3D space, each letter is drawn in a quad (or 2 quads if backface is set)
|
||||
* where the texture coodinates of each quad map to the texture coordinates of the glyphs
|
||||
* inside the font texture
|
||||
*
|
||||
* A more efficient approach, i believe, would be to render the text in a render texture and
|
||||
* map that texture to a plane and render that, or maybe a shader but my method allows more
|
||||
* flexibility...for example to change position of each letter individually to make somethink
|
||||
* like a wavy text effect
|
||||
*
|
||||
* Special thanks to:
|
||||
* @Nighten for the DrawTextStyle() code https://github.com/NightenDushi/Raylib_DrawTextStyle
|
||||
* Chris Camacho (codifies - http://bedroomcoders.co.uk/) for the alpha discard shader
|
||||
*
|
||||
* Example originally created with raylib 3.5, last time updated with raylib 4.0
|
||||
*
|
||||
* Example contributed by Vlad Adrian (@demizdor) 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) 2021-2025 Vlad Adrian (@demizdor)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Raylib_cs;
|
||||
using static Raylib_cs.Raylib;
|
||||
|
||||
namespace Examples.Text;
|
||||
|
||||
public partial class Text3D : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
#if BROWSER
|
||||
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
|
||||
#else
|
||||
private const int GlslVersion = 330;
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Global variables
|
||||
//--------------------------------------------------------------------------------------
|
||||
private const float LetterBoundrySize = 0.25f;
|
||||
private const int TextMaxLayers = 32;
|
||||
private static readonly Color LetterBoundryColor = Color.Violet;
|
||||
|
||||
private bool showLetterBoundry;
|
||||
private bool showTextBoundry;
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Types and Structures Definition
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Configuration structure for waving the text
|
||||
private struct WaveTextConfig
|
||||
{
|
||||
public Vector3 WaveRange;
|
||||
public Vector3 WaveSpeed;
|
||||
public Vector3 WaveOffset;
|
||||
}
|
||||
|
||||
public string Name => "Text / Text 3D";
|
||||
|
||||
public string Title => "raylib [text] example - 3d drawing";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint;
|
||||
|
||||
public bool CursorDisabled => true;
|
||||
|
||||
private bool spin; // Spin the camera?
|
||||
private bool multicolor; // Multicolor mode
|
||||
|
||||
private Camera3D camera;
|
||||
private CameraMode cameraMode;
|
||||
|
||||
private Vector3 cubePosition;
|
||||
private Vector3 cubeSize;
|
||||
|
||||
private Font font;
|
||||
private float fontSize;
|
||||
private float fontSpacing;
|
||||
private float lineSpacing;
|
||||
|
||||
private string text;
|
||||
private Vector3 tbox;
|
||||
private int layers;
|
||||
private int quads;
|
||||
private float layerDistance;
|
||||
|
||||
private WaveTextConfig wcfg;
|
||||
private float time;
|
||||
|
||||
private Color light;
|
||||
private Color dark;
|
||||
|
||||
private Shader alphaDiscard;
|
||||
private Color[] multi;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
spin = true; // Spin the camera?
|
||||
multicolor = false; // Multicolor mode
|
||||
showLetterBoundry = false;
|
||||
showTextBoundry = false;
|
||||
|
||||
// Define the camera to look into our 3d world
|
||||
camera = new();
|
||||
camera.Position = new Vector3(-10.0f, 15.0f, -10.0f); // Camera position
|
||||
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera projection type
|
||||
|
||||
cameraMode = CameraMode.Orbital;
|
||||
|
||||
cubePosition = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
cubeSize = new Vector3(2.0f, 2.0f, 2.0f);
|
||||
|
||||
// Use the default font
|
||||
font = GetFontDefault();
|
||||
fontSize = 0.8f;
|
||||
fontSpacing = 0.05f;
|
||||
lineSpacing = -0.1f;
|
||||
|
||||
// Set the text (using markdown!)
|
||||
text = "Hello ~~World~~ in 3D!";
|
||||
tbox = new Vector3(0, 0, 0);
|
||||
layers = 1;
|
||||
quads = 0;
|
||||
layerDistance = 0.01f;
|
||||
|
||||
wcfg = new WaveTextConfig();
|
||||
wcfg.WaveSpeed.X = wcfg.WaveSpeed.Y = 3.0f; wcfg.WaveSpeed.Z = 0.5f;
|
||||
wcfg.WaveOffset.X = wcfg.WaveOffset.Y = wcfg.WaveOffset.Z = 0.35f;
|
||||
wcfg.WaveRange.X = wcfg.WaveRange.Y = wcfg.WaveRange.Z = 0.45f;
|
||||
|
||||
time = 0.0f;
|
||||
|
||||
// Setup a light and dark color
|
||||
light = Color.Maroon;
|
||||
dark = Color.Red;
|
||||
|
||||
// Load the alpha discard shader
|
||||
alphaDiscard = LoadShader(null, $"resources/shaders/glsl{GlslVersion}/alpha_discard.fs");
|
||||
|
||||
// Array filled with multiple random colors (when multicolor mode is set)
|
||||
multi = new Color[TextMaxLayers];
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
UpdateCamera(ref camera, cameraMode);
|
||||
|
||||
// Handle font files dropped
|
||||
if (IsFileDropped())
|
||||
{
|
||||
string[] droppedFiles = GetDroppedFiles();
|
||||
|
||||
// NOTE: We only support first ttf file dropped
|
||||
if (IsFileExtension(droppedFiles[0], ".ttf"))
|
||||
{
|
||||
UnloadFont(font);
|
||||
font = LoadFontEx(droppedFiles[0], (int)fontSize, null, 0);
|
||||
}
|
||||
else if (IsFileExtension(droppedFiles[0], ".fnt"))
|
||||
{
|
||||
UnloadFont(font);
|
||||
font = LoadFont(droppedFiles[0]);
|
||||
fontSize = (float)font.BaseSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle Events
|
||||
if (IsKeyPressed(KeyboardKey.F1)) showLetterBoundry = !showLetterBoundry;
|
||||
if (IsKeyPressed(KeyboardKey.F2)) showTextBoundry = !showTextBoundry;
|
||||
if (IsKeyPressed(KeyboardKey.F3))
|
||||
{
|
||||
// Handle camera change
|
||||
spin = !spin;
|
||||
// we need to reset the camera when changing modes
|
||||
camera = new();
|
||||
camera.Target = new Vector3(0.0f, 0.0f, 0.0f); // Camera looking at point
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
|
||||
camera.FovY = 45.0f; // Camera field-of-view Y
|
||||
camera.Projection = CameraProjection.Perspective; // Camera mode type
|
||||
|
||||
if (spin)
|
||||
{
|
||||
camera.Position = new Vector3(-10.0f, 15.0f, -10.0f); // Camera position
|
||||
cameraMode = CameraMode.Orbital;
|
||||
}
|
||||
else
|
||||
{
|
||||
camera.Position = new Vector3(10.0f, 10.0f, -10.0f); // Camera position
|
||||
cameraMode = CameraMode.Free;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle clicking the cube
|
||||
if (IsMouseButtonPressed(MouseButton.Left))
|
||||
{
|
||||
Ray ray = GetScreenToWorldRay(GetMousePosition(), camera);
|
||||
|
||||
// Check collision between ray and box
|
||||
RayCollision collision = GetRayCollisionBox(ray,
|
||||
new BoundingBox(
|
||||
new Vector3(cubePosition.X - cubeSize.X / 2, cubePosition.Y - cubeSize.Y / 2, cubePosition.Z - cubeSize.Z / 2),
|
||||
new Vector3(cubePosition.X + cubeSize.X / 2, cubePosition.Y + cubeSize.Y / 2, cubePosition.Z + cubeSize.Z / 2)));
|
||||
if (collision.Hit)
|
||||
{
|
||||
// Generate new random colors
|
||||
light = GenerateRandomColor(0.5f, 0.78f);
|
||||
dark = GenerateRandomColor(0.4f, 0.58f);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle text layers changes
|
||||
if (IsKeyPressed(KeyboardKey.Home)) { if (layers > 1) --layers; }
|
||||
else if (IsKeyPressed(KeyboardKey.End)) { if (layers < TextMaxLayers) ++layers; }
|
||||
|
||||
// Handle text changes
|
||||
if (IsKeyPressed(KeyboardKey.Left)) fontSize -= 0.5f;
|
||||
else if (IsKeyPressed(KeyboardKey.Right)) fontSize += 0.5f;
|
||||
else if (IsKeyPressed(KeyboardKey.Up)) fontSpacing -= 0.1f;
|
||||
else if (IsKeyPressed(KeyboardKey.Down)) fontSpacing += 0.1f;
|
||||
else if (IsKeyPressed(KeyboardKey.PageUp)) lineSpacing -= 0.1f;
|
||||
else if (IsKeyPressed(KeyboardKey.PageDown)) lineSpacing += 0.1f;
|
||||
else if (IsKeyDown(KeyboardKey.Insert)) layerDistance -= 0.001f;
|
||||
else if (IsKeyDown(KeyboardKey.Delete)) layerDistance += 0.001f;
|
||||
else if (IsKeyPressed(KeyboardKey.Tab))
|
||||
{
|
||||
multicolor = !multicolor; // Enable /disable multicolor mode
|
||||
|
||||
if (multicolor)
|
||||
{
|
||||
// Fill color array with random colors
|
||||
for (int i = 0; i < TextMaxLayers; i++)
|
||||
{
|
||||
multi[i] = GenerateRandomColor(0.5f, 0.8f);
|
||||
multi[i].A = (byte)GetRandomValue(0, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle text input
|
||||
int ch = GetCharPressed();
|
||||
if (IsKeyPressed(KeyboardKey.Backspace))
|
||||
{
|
||||
// Remove last char
|
||||
if (text.Length > 0) text = text.Substring(0, text.Length - 1);
|
||||
}
|
||||
else if (IsKeyPressed(KeyboardKey.Enter))
|
||||
{
|
||||
// handle newline
|
||||
if (text.Length < 63) text += '\n';
|
||||
}
|
||||
else
|
||||
{
|
||||
// append only printable chars
|
||||
if ((ch != 0) && (text.Length < 63)) text += char.ConvertFromUtf32(ch);
|
||||
}
|
||||
|
||||
// Measure 3D text so we can center it
|
||||
tbox = MeasureTextWave3D(font, text, fontSize, fontSpacing, lineSpacing);
|
||||
|
||||
quads = 0; // Reset quad counter
|
||||
time += GetFrameTime(); // Update timer needed by `DrawTextWave3D()`
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
BeginMode3D(camera);
|
||||
DrawCubeV(cubePosition, cubeSize, dark);
|
||||
DrawCubeWires(cubePosition, 2.1f, 2.1f, 2.1f, light);
|
||||
|
||||
DrawGrid(10, 2.0f);
|
||||
|
||||
// Use a shader to handle the depth buffer issue with transparent textures
|
||||
// NOTE: more info at https://bedroomcoders.co.uk/posts/198
|
||||
BeginShaderMode(alphaDiscard);
|
||||
|
||||
// Draw the 3D text above the red cube
|
||||
Rlgl.PushMatrix();
|
||||
Rlgl.Rotatef(90.0f, 1.0f, 0.0f, 0.0f);
|
||||
Rlgl.Rotatef(90.0f, 0.0f, 0.0f, -1.0f);
|
||||
|
||||
for (int i = 0; i < layers; i++)
|
||||
{
|
||||
Color clr = light;
|
||||
if (multicolor) clr = multi[i];
|
||||
DrawTextWave3D(font, text, new Vector3(-tbox.X / 2.0f, layerDistance * i, -4.5f), fontSize, fontSpacing, lineSpacing, true, wcfg, time, clr);
|
||||
}
|
||||
|
||||
// Draw the text boundry if set
|
||||
if (showTextBoundry) DrawCubeWiresV(new Vector3(0.0f, 0.0f, -4.5f + tbox.Z / 2), tbox, dark);
|
||||
Rlgl.PopMatrix();
|
||||
|
||||
// Don't draw the letter boundries for the 3D text below
|
||||
bool slb = showLetterBoundry;
|
||||
showLetterBoundry = false;
|
||||
|
||||
// Draw 3D options (use default font)
|
||||
//-------------------------------------------------------------------------
|
||||
Rlgl.PushMatrix();
|
||||
Rlgl.Rotatef(180.0f, 0.0f, 1.0f, 0.0f);
|
||||
string opt = $"< SIZE: {fontSize:0.0} >";
|
||||
quads += opt.Length;
|
||||
Vector2 m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
|
||||
Vector3 pos = new(-m.X / 2.0f, 0.01f, 2.0f);
|
||||
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Blue);
|
||||
pos.Z += 0.5f + m.Y;
|
||||
|
||||
opt = $"< SPACING: {fontSpacing:0.0} >";
|
||||
quads += opt.Length;
|
||||
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
|
||||
pos.X = -m.X / 2.0f;
|
||||
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Blue);
|
||||
pos.Z += 0.5f + m.Y;
|
||||
|
||||
opt = $"< LINE: {lineSpacing:0.0} >";
|
||||
quads += opt.Length;
|
||||
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
|
||||
pos.X = -m.X / 2.0f;
|
||||
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Blue);
|
||||
pos.Z += 0.5f + m.Y;
|
||||
|
||||
opt = $"< LBOX: {(slb ? "ON" : "OFF"),3} >";
|
||||
quads += opt.Length;
|
||||
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
|
||||
pos.X = -m.X / 2.0f;
|
||||
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Red);
|
||||
pos.Z += 0.5f + m.Y;
|
||||
|
||||
opt = $"< TBOX: {(showTextBoundry ? "ON" : "OFF"),3} >";
|
||||
quads += opt.Length;
|
||||
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
|
||||
pos.X = -m.X / 2.0f;
|
||||
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.Red);
|
||||
pos.Z += 0.5f + m.Y;
|
||||
|
||||
opt = $"< LAYER DISTANCE: {layerDistance:0.000} >";
|
||||
quads += opt.Length;
|
||||
m = MeasureTextEx(GetFontDefault(), opt, 0.8f, 0.1f);
|
||||
pos.X = -m.X / 2.0f;
|
||||
DrawText3D(GetFontDefault(), opt, pos, 0.8f, 0.1f, 0.0f, false, Color.DarkPurple);
|
||||
Rlgl.PopMatrix();
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Draw 3D info text (use default font)
|
||||
//-------------------------------------------------------------------------
|
||||
opt = "All the text displayed here is in 3D";
|
||||
quads += 36;
|
||||
m = MeasureTextEx(GetFontDefault(), opt, 1.0f, 0.05f);
|
||||
pos = new Vector3(-m.X / 2.0f, 0.01f, 2.0f);
|
||||
DrawText3D(GetFontDefault(), opt, pos, 1.0f, 0.05f, 0.0f, false, Color.DarkBlue);
|
||||
pos.Z += 1.5f + m.Y;
|
||||
|
||||
opt = "press [Left]/[Right] to change the font size";
|
||||
quads += 44;
|
||||
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
|
||||
pos.X = -m.X / 2.0f;
|
||||
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
|
||||
pos.Z += 0.5f + m.Y;
|
||||
|
||||
opt = "press [Up]/[Down] to change the font spacing";
|
||||
quads += 44;
|
||||
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
|
||||
pos.X = -m.X / 2.0f;
|
||||
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
|
||||
pos.Z += 0.5f + m.Y;
|
||||
|
||||
opt = "press [PgUp]/[PgDown] to change the line spacing";
|
||||
quads += 48;
|
||||
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
|
||||
pos.X = -m.X / 2.0f;
|
||||
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
|
||||
pos.Z += 0.5f + m.Y;
|
||||
|
||||
opt = "press [F1] to toggle the letter boundry";
|
||||
quads += 39;
|
||||
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
|
||||
pos.X = -m.X / 2.0f;
|
||||
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
|
||||
pos.Z += 0.5f + m.Y;
|
||||
|
||||
opt = "press [F2] to toggle the text boundry";
|
||||
quads += 37;
|
||||
m = MeasureTextEx(GetFontDefault(), opt, 0.6f, 0.05f);
|
||||
pos.X = -m.X / 2.0f;
|
||||
DrawText3D(GetFontDefault(), opt, pos, 0.6f, 0.05f, 0.0f, false, Color.DarkBlue);
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
showLetterBoundry = slb;
|
||||
EndShaderMode();
|
||||
|
||||
EndMode3D();
|
||||
|
||||
// Draw 2D info text & stats
|
||||
//-------------------------------------------------------------------------
|
||||
DrawText("Drag & drop a font file to change the font!\nType something, see what happens!\n\n" +
|
||||
"Press [F3] to toggle the camera", 10, 35, 10, Color.Black);
|
||||
|
||||
quads += TextLengthUtf8(text) * 2 * layers;
|
||||
string tmp = $"{layers,2} layer(s) | {(spin ? "ORBITAL" : "FREE")} camera | {quads,4} quads ({quads * 4,4} verts)";
|
||||
int width = MeasureText(tmp, 10);
|
||||
DrawText(tmp, screenWidth - 20 - width, 10, 10, Color.DarkGreen);
|
||||
|
||||
tmp = "[Home]/[End] to add/remove 3D text layers";
|
||||
width = MeasureText(tmp, 10);
|
||||
DrawText(tmp, screenWidth - 20 - width, 25, 10, Color.DarkGray);
|
||||
|
||||
tmp = "[Insert]/[Delete] to increase/decrease distance between layers";
|
||||
width = MeasureText(tmp, 10);
|
||||
DrawText(tmp, screenWidth - 20 - width, 40, 10, Color.DarkGray);
|
||||
|
||||
tmp = "click the [CUBE] for a random color";
|
||||
width = MeasureText(tmp, 10);
|
||||
DrawText(tmp, screenWidth - 20 - width, 55, 10, Color.DarkGray);
|
||||
|
||||
tmp = "[Tab] to toggle multicolor mode";
|
||||
width = MeasureText(tmp, 10);
|
||||
DrawText(tmp, screenWidth - 20 - width, 70, 10, Color.DarkGray);
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
DrawFPS(10, 10);
|
||||
|
||||
EndDrawing();
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
UnloadFont(font);
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Module Functions Definitions
|
||||
//--------------------------------------------------------------------------------------
|
||||
// Get the total length in bytes of a UTF-8 string (raylib TextLength equivalent)
|
||||
private static int TextLengthUtf8(string text)
|
||||
{
|
||||
return Encoding.UTF8.GetByteCount(text);
|
||||
}
|
||||
|
||||
// Draw codepoint at specified position in 3D space
|
||||
private unsafe void DrawTextCodepoint3D(Font font, int codepoint, Vector3 position, float fontSize, bool backface, Color tint)
|
||||
{
|
||||
// Character index position in sprite font
|
||||
// NOTE: In case a codepoint is not available in the font, index returned points to '?'
|
||||
int index = GetGlyphIndex(font, codepoint);
|
||||
float scale = fontSize / (float)font.BaseSize;
|
||||
|
||||
// Character destination rectangle on screen
|
||||
// NOTE: We consider charsPadding on drawing
|
||||
position.X += (font.Glyphs[index].OffsetX - font.GlyphPadding) * scale;
|
||||
position.Z += (font.Glyphs[index].OffsetY - font.GlyphPadding) * scale;
|
||||
|
||||
// Character source rectangle from font texture atlas
|
||||
// NOTE: We consider chars padding when drawing, it could be required for outline/glow shader effects
|
||||
Rectangle srcRec = new(font.Recs[index].X - font.GlyphPadding, font.Recs[index].Y - font.GlyphPadding,
|
||||
font.Recs[index].Width + 2.0f * font.GlyphPadding, font.Recs[index].Height + 2.0f * font.GlyphPadding);
|
||||
|
||||
float width = (font.Recs[index].Width + 2.0f * font.GlyphPadding) * scale;
|
||||
float height = (font.Recs[index].Height + 2.0f * font.GlyphPadding) * scale;
|
||||
|
||||
if (font.Texture.Id > 0)
|
||||
{
|
||||
const float x = 0.0f;
|
||||
const float y = 0.0f;
|
||||
const float z = 0.0f;
|
||||
|
||||
// normalized texture coordinates of the glyph inside the font texture (0.0f -> 1.0f)
|
||||
float tx = srcRec.X / font.Texture.Width;
|
||||
float ty = srcRec.Y / font.Texture.Height;
|
||||
float tw = (srcRec.X + srcRec.Width) / font.Texture.Width;
|
||||
float th = (srcRec.Y + srcRec.Height) / font.Texture.Height;
|
||||
|
||||
if (showLetterBoundry) DrawCubeWiresV(new Vector3(position.X + width / 2, position.Y, position.Z + height / 2), new Vector3(width, LetterBoundrySize, height), LetterBoundryColor);
|
||||
|
||||
Rlgl.CheckRenderBatchLimit(4 + 4 * (backface ? 1 : 0));
|
||||
Rlgl.SetTexture(font.Texture.Id);
|
||||
|
||||
Rlgl.PushMatrix();
|
||||
Rlgl.Translatef(position.X, position.Y, position.Z);
|
||||
|
||||
Rlgl.Begin(DrawMode.Quads);
|
||||
Rlgl.Color4ub(tint.R, tint.G, tint.B, tint.A);
|
||||
|
||||
// Front Face
|
||||
Rlgl.Normal3f(0.0f, 1.0f, 0.0f); // Normal Pointing Up
|
||||
Rlgl.TexCoord2f(tx, ty); Rlgl.Vertex3f(x, y, z); // Top Left Of The Texture and Quad
|
||||
Rlgl.TexCoord2f(tx, th); Rlgl.Vertex3f(x, y, z + height); // Bottom Left Of The Texture and Quad
|
||||
Rlgl.TexCoord2f(tw, th); Rlgl.Vertex3f(x + width, y, z + height); // Bottom Right Of The Texture and Quad
|
||||
Rlgl.TexCoord2f(tw, ty); Rlgl.Vertex3f(x + width, y, z); // Top Right Of The Texture and Quad
|
||||
|
||||
if (backface)
|
||||
{
|
||||
// Back Face
|
||||
Rlgl.Normal3f(0.0f, -1.0f, 0.0f); // Normal Pointing Down
|
||||
Rlgl.TexCoord2f(tx, ty); Rlgl.Vertex3f(x, y, z); // Top Right Of The Texture and Quad
|
||||
Rlgl.TexCoord2f(tw, ty); Rlgl.Vertex3f(x + width, y, z); // Top Left Of The Texture and Quad
|
||||
Rlgl.TexCoord2f(tw, th); Rlgl.Vertex3f(x + width, y, z + height); // Bottom Left Of The Texture and Quad
|
||||
Rlgl.TexCoord2f(tx, th); Rlgl.Vertex3f(x, y, z + height); // Bottom Right Of The Texture and Quad
|
||||
}
|
||||
Rlgl.End();
|
||||
Rlgl.PopMatrix();
|
||||
|
||||
Rlgl.SetTexture(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a 2D text in 3D space
|
||||
private unsafe void DrawText3D(Font font, string text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, Color tint)
|
||||
{
|
||||
using var textNative = new Utf8Buffer(text);
|
||||
sbyte* t = textNative.AsPointer();
|
||||
int length = Encoding.UTF8.GetByteCount(text); // Total length in bytes of the text, scanned by codepoints in loop
|
||||
|
||||
float textOffsetY = 0.0f; // Offset between lines (on line break '\n')
|
||||
float textOffsetX = 0.0f; // Offset X to next character to draw
|
||||
|
||||
float scale = fontSize / (float)font.BaseSize;
|
||||
|
||||
for (int i = 0; i < length;)
|
||||
{
|
||||
// Get next codepoint from byte string and glyph index in font
|
||||
int codepointByteCount = 0;
|
||||
int codepoint = GetCodepoint(&t[i], &codepointByteCount);
|
||||
int index = GetGlyphIndex(font, codepoint);
|
||||
|
||||
// NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
|
||||
// but we need to draw all of the bad bytes using the '?' symbol moving one byte
|
||||
if (codepoint == 0x3f) codepointByteCount = 1;
|
||||
|
||||
if (codepoint == '\n')
|
||||
{
|
||||
// NOTE: Fixed line spacing of 1.5 line-height
|
||||
// TODO: Support custom line spacing defined by user
|
||||
textOffsetY += fontSize + lineSpacing;
|
||||
textOffsetX = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((codepoint != ' ') && (codepoint != '\t'))
|
||||
{
|
||||
DrawTextCodepoint3D(font, codepoint, new Vector3(position.X + textOffsetX, position.Y, position.Z + textOffsetY), fontSize, backface, tint);
|
||||
}
|
||||
|
||||
if (font.Glyphs[index].AdvanceX == 0) textOffsetX += font.Recs[index].Width * scale + fontSpacing;
|
||||
else textOffsetX += font.Glyphs[index].AdvanceX * scale + fontSpacing;
|
||||
}
|
||||
|
||||
i += codepointByteCount; // Move text bytes counter to next codepoint
|
||||
}
|
||||
}
|
||||
|
||||
// Draw a 2D text in 3D space and wave the parts that start with `~~` and end with `~~`
|
||||
// This is a modified version of the original code by @Nighten found here https://github.com/NightenDushi/Raylib_DrawTextStyle
|
||||
private unsafe void DrawTextWave3D(Font font, string text, Vector3 position, float fontSize, float fontSpacing, float lineSpacing, bool backface, WaveTextConfig config, float time, Color tint)
|
||||
{
|
||||
using var textNative = new Utf8Buffer(text);
|
||||
sbyte* t = textNative.AsPointer();
|
||||
int length = Encoding.UTF8.GetByteCount(text); // Total length in bytes of the text, scanned by codepoints in loop
|
||||
|
||||
float textOffsetY = 0.0f; // Offset between lines (on line break '\n')
|
||||
float textOffsetX = 0.0f; // Offset X to next character to draw
|
||||
|
||||
float scale = fontSize / (float)font.BaseSize;
|
||||
|
||||
bool wave = false;
|
||||
|
||||
for (int i = 0, k = 0; i < length; ++k)
|
||||
{
|
||||
// Get next codepoint from byte string and glyph index in font
|
||||
int codepointByteCount = 0;
|
||||
int codepoint = GetCodepoint(&t[i], &codepointByteCount);
|
||||
int index = GetGlyphIndex(font, codepoint);
|
||||
|
||||
// NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
|
||||
// but we need to draw all of the bad bytes using the '?' symbol moving one byte
|
||||
if (codepoint == 0x3f) codepointByteCount = 1;
|
||||
|
||||
if (codepoint == '\n')
|
||||
{
|
||||
// NOTE: Fixed line spacing of 1.5 line-height
|
||||
// TODO: Support custom line spacing defined by user
|
||||
textOffsetY += fontSize + lineSpacing;
|
||||
textOffsetX = 0.0f;
|
||||
k = 0;
|
||||
}
|
||||
else if (codepoint == '~')
|
||||
{
|
||||
if (GetCodepoint(&t[i + 1], &codepointByteCount) == '~')
|
||||
{
|
||||
codepointByteCount += 1;
|
||||
wave = !wave;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((codepoint != ' ') && (codepoint != '\t'))
|
||||
{
|
||||
Vector3 pos = position;
|
||||
if (wave) // Apply the wave effect
|
||||
{
|
||||
pos.X += MathF.Sin(time * config.WaveSpeed.X - k * config.WaveOffset.X) * config.WaveRange.X;
|
||||
pos.Y += MathF.Sin(time * config.WaveSpeed.Y - k * config.WaveOffset.Y) * config.WaveRange.Y;
|
||||
pos.Z += MathF.Sin(time * config.WaveSpeed.Z - k * config.WaveOffset.Z) * config.WaveRange.Z;
|
||||
}
|
||||
|
||||
DrawTextCodepoint3D(font, codepoint, new Vector3(pos.X + textOffsetX, pos.Y, pos.Z + textOffsetY), fontSize, backface, tint);
|
||||
}
|
||||
|
||||
if (font.Glyphs[index].AdvanceX == 0) textOffsetX += font.Recs[index].Width * scale + fontSpacing;
|
||||
else textOffsetX += font.Glyphs[index].AdvanceX * scale + fontSpacing;
|
||||
}
|
||||
|
||||
i += codepointByteCount; // Move text bytes counter to next codepoint
|
||||
}
|
||||
}
|
||||
|
||||
// Measure a text in 3D ignoring the `~~` chars
|
||||
private unsafe Vector3 MeasureTextWave3D(Font font, string text, float fontSize, float fontSpacing, float lineSpacing)
|
||||
{
|
||||
using var textNative = new Utf8Buffer(text);
|
||||
sbyte* t = textNative.AsPointer();
|
||||
int len = Encoding.UTF8.GetByteCount(text);
|
||||
int tempLen = 0; // Used to count longer text line num chars
|
||||
int lenCounter = 0;
|
||||
|
||||
float tempTextWidth = 0.0f; // Used to count longer text line width
|
||||
|
||||
float scale = fontSize / (float)font.BaseSize;
|
||||
float textHeight = scale;
|
||||
float textWidth = 0.0f;
|
||||
|
||||
int letter = 0; // Current character
|
||||
int index = 0; // Index position in sprite font
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
int next = 0;
|
||||
letter = GetCodepoint(&t[i], &next);
|
||||
index = GetGlyphIndex(font, letter);
|
||||
|
||||
// NOTE: normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f)
|
||||
// but we need to draw all of the bad bytes using the '?' symbol so to not skip any we set next = 1
|
||||
if (letter == 0x3f) next = 1;
|
||||
i += next - 1;
|
||||
|
||||
if (letter != '\n')
|
||||
{
|
||||
if (letter == '~' && GetCodepoint(&t[i + 1], &next) == '~')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
lenCounter++;
|
||||
if (font.Glyphs[index].AdvanceX != 0) textWidth += font.Glyphs[index].AdvanceX * scale;
|
||||
else textWidth += (font.Recs[index].Width + font.Glyphs[index].OffsetX) * scale;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
|
||||
lenCounter = 0;
|
||||
textWidth = 0.0f;
|
||||
textHeight += fontSize + lineSpacing;
|
||||
}
|
||||
|
||||
if (tempLen < lenCounter) tempLen = lenCounter;
|
||||
}
|
||||
|
||||
if (tempTextWidth < textWidth) tempTextWidth = textWidth;
|
||||
|
||||
Vector3 vec = new(0, 0, 0);
|
||||
vec.X = tempTextWidth + ((tempLen - 1) * fontSpacing); // Adds chars spacing to measure
|
||||
vec.Y = 0.25f;
|
||||
vec.Z = textHeight;
|
||||
|
||||
return vec;
|
||||
}
|
||||
|
||||
// Generates a nice color with a random hue
|
||||
private static Color GenerateRandomColor(float s, float v)
|
||||
{
|
||||
const float Phi = 0.618033988749895f; // Golden ratio conjugate
|
||||
float h = (float)GetRandomValue(0, 360);
|
||||
h = (h + h * Phi) % 360.0f;
|
||||
return ColorFromHSV(h, s, v);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [text] example - 3d drawing");
|
||||
|
||||
DisableCursor(); // Limit cursor to relative movement inside the window
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new Text3D();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,11 +22,20 @@ using static Raylib_cs.Raylib;
|
|||
|
||||
namespace Examples.Text;
|
||||
|
||||
public class Unicode
|
||||
public partial class Unicode : IExample
|
||||
{
|
||||
private const int screenWidth = 800;
|
||||
private const int screenHeight = 450;
|
||||
|
||||
const int EmojiPerWidth = 8;
|
||||
const int EmojiPerHeight = 4;
|
||||
|
||||
public string Name => "Text / Unicode Emojis";
|
||||
|
||||
public string Title => "raylib [text] example - unicode emojis";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint;
|
||||
|
||||
// Arrays that holds the random emojis
|
||||
struct EmojiInfo
|
||||
{
|
||||
|
|
@ -35,10 +44,10 @@ public class Unicode
|
|||
public Color Color; // Emoji color
|
||||
}
|
||||
|
||||
static EmojiInfo[] emoji = new EmojiInfo[EmojiPerWidth * EmojiPerHeight];
|
||||
private EmojiInfo[] emoji;
|
||||
|
||||
static int hovered = -1;
|
||||
static int selected = -1;
|
||||
private int hovered;
|
||||
private int selected;
|
||||
|
||||
struct Message
|
||||
{
|
||||
|
|
@ -130,34 +139,35 @@ public class Unicode
|
|||
new Message("\xED\x95\x9C\xEA\xB5\xAD\xEB\xA7\x90\x20\xED\x95\x98\xEC\x8B\xA4\x20\xEC\xA4\x84\x20\xEC\x95\x84\xEC\x84\xB8\xEC\x9A\x94\x3F", "Korean"),
|
||||
};
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
const int screenWidth = 800;
|
||||
const int screenHeight = 450;
|
||||
private Font fontDefault;
|
||||
private Font fontAsian;
|
||||
private Font fontEmoji;
|
||||
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [text] example - unicode emojis");
|
||||
private Vector2 hoveredPos;
|
||||
private Vector2 selectedPos;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
emoji = new EmojiInfo[EmojiPerWidth * EmojiPerHeight];
|
||||
|
||||
hovered = -1;
|
||||
selected = -1;
|
||||
|
||||
// Load the font resources
|
||||
// NOTE: fontAsian is for asian languages,
|
||||
// fontEmoji is the emojis and fontDefault is used for everything else
|
||||
Font fontDefault = LoadFont("resources/fonts/dejavu.fnt"); // Requires "resources/fonts/dejavu.png"
|
||||
Font fontAsian = LoadFont("resources/fonts/noto_cjk.fnt"); // Requires "resources/fonts/noto_cjk.png"
|
||||
Font fontEmoji = LoadFont("resources/fonts/symbola.fnt"); // Requires "resources/fonts/symbola.png"
|
||||
fontDefault = LoadFont("resources/fonts/dejavu.fnt"); // Requires "resources/fonts/dejavu.png"
|
||||
fontAsian = LoadFont("resources/fonts/noto_cjk.fnt"); // Requires "resources/fonts/noto_cjk.png"
|
||||
fontEmoji = LoadFont("resources/fonts/symbola.fnt"); // Requires "resources/fonts/symbola.png"
|
||||
|
||||
Vector2 hoveredPos = new(0.0f, 0.0f);
|
||||
Vector2 selectedPos = new(0.0f, 0.0f);
|
||||
hoveredPos = new(0.0f, 0.0f);
|
||||
selectedPos = new(0.0f, 0.0f);
|
||||
|
||||
// Set a random set of emojis when starting up
|
||||
RandomizeEmoji();
|
||||
}
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
// Main loop
|
||||
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||
public void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
|
|
@ -301,20 +311,15 @@ public class Unicode
|
|||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
public void Unload()
|
||||
{
|
||||
UnloadFont(fontDefault); // Unload font resource
|
||||
UnloadFont(fontAsian); // Unload font resource
|
||||
UnloadFont(fontEmoji); // Unload font resource
|
||||
|
||||
CloseWindow(); // Close window and OpenGL context
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fills the emoji array with random emoji (only those emojis present in fontEmoji)
|
||||
static void RandomizeEmoji()
|
||||
void RandomizeEmoji()
|
||||
{
|
||||
hovered = selected = -1;
|
||||
int start = GetRandomValue(45, 360);
|
||||
|
|
@ -510,4 +515,33 @@ public class Unicode
|
|||
textOffsetX += glyphWidth;
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint | ConfigFlags.VSyncHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [text] example - unicode emojis");
|
||||
|
||||
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new Unicode();
|
||||
game.Init();
|
||||
|
||||
// Main loop
|
||||
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||
{
|
||||
game.Update();
|
||||
}
|
||||
|
||||
game.Unload();
|
||||
|
||||
// De-Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
CloseWindow(); // Close window and OpenGL context
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue