Compare commits
9 Commits
4dbe04b250
...
d06c820a44
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d06c820a44 | ||
|
|
3e3453d54e | ||
|
|
4dca02daa5 | ||
|
|
c059ece2a4 | ||
|
|
b5caef1ffb | ||
|
|
6dcd4cd564 | ||
|
|
80b6b7fc2a | ||
|
|
d172a24bb0 | ||
|
|
9c73b0eb37 |
@ -77,7 +77,7 @@ RAYLIB_SRC_PATH ?= ../src
|
|||||||
|
|
||||||
# Locations of raylib.h and libraylib.a/libraylib.so
|
# Locations of raylib.h and libraylib.a/libraylib.so
|
||||||
# NOTE: Those variables are only used for PLATFORM_OS: LINUX, BSD
|
# NOTE: Those variables are only used for PLATFORM_OS: LINUX, BSD
|
||||||
DESTDIR ?= /usr/local
|
DESTDIR ?= /usr/local
|
||||||
RAYLIB_INCLUDE_PATH ?= $(DESTDIR)/include
|
RAYLIB_INCLUDE_PATH ?= $(DESTDIR)/include
|
||||||
RAYLIB_LIB_PATH ?= $(DESTDIR)/lib
|
RAYLIB_LIB_PATH ?= $(DESTDIR)/lib
|
||||||
|
|
||||||
@ -703,6 +703,7 @@ SHADERS = \
|
|||||||
shaders/shaders_vertex_displacement
|
shaders/shaders_vertex_displacement
|
||||||
|
|
||||||
AUDIO = \
|
AUDIO = \
|
||||||
|
audio/audio_fft_spectrum_visualizer \
|
||||||
audio/audio_mixed_processor \
|
audio/audio_mixed_processor \
|
||||||
audio/audio_module_playing \
|
audio/audio_module_playing \
|
||||||
audio/audio_music_stream \
|
audio/audio_music_stream \
|
||||||
|
|||||||
279
examples/audio/audio_fft_spectrum_visualizer.c
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
/*******************************************************************************************
|
||||||
|
*
|
||||||
|
* raylib [audio] example - fft spectrum visualizer
|
||||||
|
*
|
||||||
|
* Example complexity rating: [★★★☆] 3/4
|
||||||
|
*
|
||||||
|
* Example originally created with raylib 6.0
|
||||||
|
*
|
||||||
|
* Inspired by Inigo Quilez's https://www.shadertoy.com/
|
||||||
|
* Resources/specification: https://gist.github.com/soulthreads/2efe50da4be1fb5f7ab60ff14ca434b8
|
||||||
|
*
|
||||||
|
* Example created by created by IANN (@meisei4) 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 IANN (@meisei4)
|
||||||
|
*
|
||||||
|
********************************************************************************************/
|
||||||
|
|
||||||
|
#include "raylib.h"
|
||||||
|
#include "raymath.h"
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define MONO 1
|
||||||
|
#define SAMPLE_RATE 44100
|
||||||
|
#define SAMPLE_RATE_F 44100.0f
|
||||||
|
#define FFT_WINDOW_SIZE 1024
|
||||||
|
#define BUFFER_SIZE 512
|
||||||
|
#define PER_SAMPLE_BIT_DEPTH 16
|
||||||
|
#define AUDIO_STREAM_RING_BUFFER_SIZE (FFT_WINDOW_SIZE*2)
|
||||||
|
#define EFFECTIVE_SAMPLE_RATE (SAMPLE_RATE_F*0.5f)
|
||||||
|
#define WINDOW_TIME ((double)FFT_WINDOW_SIZE/(double)EFFECTIVE_SAMPLE_RATE)
|
||||||
|
#define FFT_HISTORICAL_SMOOTHING_DUR 2.0f
|
||||||
|
#define MIN_DECIBELS (-100.0f) // https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/minDecibels
|
||||||
|
#define MAX_DECIBELS (-30.0f) // https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/maxDecibels
|
||||||
|
#define INVERSE_DECIBEL_RANGE (1.0f/(MAX_DECIBELS - MIN_DECIBELS))
|
||||||
|
#define DB_TO_LINEAR_SCALE (20.0f/2.302585092994046f)
|
||||||
|
#define SMOOTHING_TIME_CONSTANT 0.8f // https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/smoothingTimeConstant
|
||||||
|
#define TEXTURE_HEIGHT 1
|
||||||
|
#define FFT_ROW 0
|
||||||
|
#define UNUSED_CHANNEL 0.0f
|
||||||
|
|
||||||
|
typedef struct FFTComplex { float real, imaginary; } FFTComplex;
|
||||||
|
|
||||||
|
typedef struct FFTData {
|
||||||
|
FFTComplex *spectrum;
|
||||||
|
FFTComplex *workBuffer;
|
||||||
|
float *prevMagnitudes;
|
||||||
|
float (*fftHistory)[BUFFER_SIZE];
|
||||||
|
int fftHistoryLen;
|
||||||
|
int historyPos;
|
||||||
|
double lastFftTime;
|
||||||
|
float tapbackPos;
|
||||||
|
} FFTData;
|
||||||
|
|
||||||
|
static void CaptureFrame(FFTData *fftData, const float *audioSamples);
|
||||||
|
static void RenderFrame(const FFTData *fftData, Image *fftImage);
|
||||||
|
static void CooleyTukeyFFTSlow(FFTComplex *spectrum, int n);
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
// Program main entry point
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
//----------------------------------------------------------------------------------- ---
|
||||||
|
const int screenWidth = 800;
|
||||||
|
const int screenHeight = 450;
|
||||||
|
|
||||||
|
InitWindow(screenWidth, screenHeight, "raylib [audio] example - fft spectrum visualizer");
|
||||||
|
|
||||||
|
Image fftImage = GenImageColor(BUFFER_SIZE, TEXTURE_HEIGHT, WHITE);
|
||||||
|
Texture2D fftTexture = LoadTextureFromImage(fftImage);
|
||||||
|
RenderTexture2D bufferA = LoadRenderTexture(screenWidth, screenHeight);
|
||||||
|
Vector2 iResolution = { (float)screenWidth, (float)screenHeight };
|
||||||
|
|
||||||
|
Shader shader = LoadShader(NULL, "resources/fft.glsl");
|
||||||
|
int iResolutionLocation = GetShaderLocation(shader, "iResolution");
|
||||||
|
int iChannel0Location = GetShaderLocation(shader, "iChannel0");
|
||||||
|
SetShaderValue(shader, iResolutionLocation, &iResolution, SHADER_UNIFORM_VEC2);
|
||||||
|
SetShaderValueTexture(shader, iChannel0Location, fftTexture);
|
||||||
|
|
||||||
|
InitAudioDevice();
|
||||||
|
SetAudioStreamBufferSizeDefault(AUDIO_STREAM_RING_BUFFER_SIZE);
|
||||||
|
|
||||||
|
Wave wav = LoadWave("resources/country.mp3");
|
||||||
|
WaveFormat(&wav, SAMPLE_RATE, PER_SAMPLE_BIT_DEPTH, MONO);
|
||||||
|
|
||||||
|
AudioStream audioStream = LoadAudioStream(SAMPLE_RATE, PER_SAMPLE_BIT_DEPTH, MONO);
|
||||||
|
PlayAudioStream(audioStream);
|
||||||
|
|
||||||
|
int fftHistoryLen = (int)ceilf(FFT_HISTORICAL_SMOOTHING_DUR/WINDOW_TIME) + 1;
|
||||||
|
|
||||||
|
FFTData fft = {
|
||||||
|
.spectrum = malloc(sizeof(FFTComplex)*FFT_WINDOW_SIZE),
|
||||||
|
.workBuffer = malloc(sizeof(FFTComplex)*FFT_WINDOW_SIZE),
|
||||||
|
.prevMagnitudes = calloc(BUFFER_SIZE, sizeof(float)),
|
||||||
|
.fftHistory = calloc(fftHistoryLen, sizeof(float[BUFFER_SIZE])),
|
||||||
|
.fftHistoryLen = fftHistoryLen,
|
||||||
|
.historyPos = 0,
|
||||||
|
.lastFftTime = 0.0,
|
||||||
|
.tapbackPos = 0.01f
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t wavCursor = 0;
|
||||||
|
const short *wavPCM16 = wav.data;
|
||||||
|
|
||||||
|
short chunkSamples[AUDIO_STREAM_RING_BUFFER_SIZE] = { 0 };
|
||||||
|
float audioSamples[FFT_WINDOW_SIZE] = { 0 };
|
||||||
|
|
||||||
|
SetTargetFPS(60);
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Main game loop
|
||||||
|
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||||
|
{
|
||||||
|
// Update
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
while (IsAudioStreamProcessed(audioStream))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < AUDIO_STREAM_RING_BUFFER_SIZE; i++)
|
||||||
|
{
|
||||||
|
int left = (wav.channels == 2)? wavPCM16[wavCursor*2 + 0] : wavPCM16[wavCursor];
|
||||||
|
int right = (wav.channels == 2)? wavPCM16[wavCursor*2 + 1] : left;
|
||||||
|
chunkSamples[i] = (short)((left + right)/2);
|
||||||
|
|
||||||
|
if (++wavCursor >= wav.frameCount)
|
||||||
|
wavCursor = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateAudioStream(audioStream, chunkSamples, AUDIO_STREAM_RING_BUFFER_SIZE);
|
||||||
|
|
||||||
|
for (int i = 0; i < FFT_WINDOW_SIZE; i++)
|
||||||
|
audioSamples[i] = (chunkSamples[i*2] + chunkSamples[i*2 + 1])*0.5f/32767.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptureFrame(&fft, audioSamples);
|
||||||
|
RenderFrame(&fft, &fftImage);
|
||||||
|
UpdateTexture(fftTexture, fftImage.data);
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
BeginDrawing();
|
||||||
|
ClearBackground(BLACK);
|
||||||
|
BeginShaderMode(shader);
|
||||||
|
SetShaderValueTexture(shader, iChannel0Location, fftTexture);
|
||||||
|
DrawTextureRec(bufferA.texture,
|
||||||
|
(Rectangle){ 0, 0, (float)screenWidth, (float)-screenHeight },
|
||||||
|
(Vector2){ 0, 0 },
|
||||||
|
WHITE);
|
||||||
|
EndShaderMode();
|
||||||
|
EndDrawing();
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
}
|
||||||
|
|
||||||
|
// De-Initialization
|
||||||
|
//--------------------------------------------------------------------------------------
|
||||||
|
UnloadShader(shader);
|
||||||
|
UnloadRenderTexture(bufferA);
|
||||||
|
UnloadTexture(fftTexture);
|
||||||
|
UnloadImage(fftImage);
|
||||||
|
UnloadAudioStream(audioStream);
|
||||||
|
UnloadWave(wav);
|
||||||
|
CloseAudioDevice();
|
||||||
|
|
||||||
|
free(fft.spectrum);
|
||||||
|
free(fft.workBuffer);
|
||||||
|
free(fft.prevMagnitudes);
|
||||||
|
free(fft.fftHistory);
|
||||||
|
|
||||||
|
CloseWindow(); // Close window and OpenGL context
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cooley–Tukey FFT https://en.wikipedia.org/wiki/Cooley%E2%80%93Tukey_FFT_algorithm#Data_reordering,_bit_reversal,_and_in-place_algorithms
|
||||||
|
static void CooleyTukeyFFTSlow(FFTComplex *spectrum, int n)
|
||||||
|
{
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 1; i < n - 1; i++)
|
||||||
|
{
|
||||||
|
int bit = n >> 1;
|
||||||
|
while (j >= bit)
|
||||||
|
{
|
||||||
|
j -= bit;
|
||||||
|
bit >>= 1;
|
||||||
|
}
|
||||||
|
j += bit;
|
||||||
|
if (i < j)
|
||||||
|
{
|
||||||
|
FFTComplex temp = spectrum[i];
|
||||||
|
spectrum[i] = spectrum[j];
|
||||||
|
spectrum[j] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int len = 2; len <= n; len <<= 1)
|
||||||
|
{
|
||||||
|
float angle = -2.0f*PI/len;
|
||||||
|
FFTComplex twiddleUnit = { cosf(angle), sinf(angle) };
|
||||||
|
for (int i = 0; i < n; i += len)
|
||||||
|
{
|
||||||
|
FFTComplex twiddleCurrent = { 1.0f, 0.0f };
|
||||||
|
for (int j = 0; j < len/2; j++)
|
||||||
|
{
|
||||||
|
FFTComplex even = spectrum[i + j];
|
||||||
|
FFTComplex odd = spectrum[i + j + len/2];
|
||||||
|
FFTComplex twiddledOdd = {
|
||||||
|
odd.real*twiddleCurrent.real - odd.imaginary*twiddleCurrent.imaginary,
|
||||||
|
odd.real*twiddleCurrent.imaginary + odd.imaginary*twiddleCurrent.real
|
||||||
|
};
|
||||||
|
|
||||||
|
spectrum[i + j].real = even.real + twiddledOdd.real;
|
||||||
|
spectrum[i + j].imaginary = even.imaginary + twiddledOdd.imaginary;
|
||||||
|
spectrum[i + j + len/2].real = even.real - twiddledOdd.real;
|
||||||
|
spectrum[i + j + len/2].imaginary = even.imaginary - twiddledOdd.imaginary;
|
||||||
|
|
||||||
|
float twiddleRealNext = twiddleCurrent.real*twiddleUnit.real - twiddleCurrent.imaginary*twiddleUnit.imaginary;
|
||||||
|
twiddleCurrent.imaginary = twiddleCurrent.real*twiddleUnit.imaginary + twiddleCurrent.imaginary*twiddleUnit.real;
|
||||||
|
twiddleCurrent.real = twiddleRealNext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CaptureFrame(FFTData *fftData, const float *audioSamples)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < FFT_WINDOW_SIZE; i++)
|
||||||
|
{
|
||||||
|
float x = (2.0f*PI*i)/(FFT_WINDOW_SIZE - 1.0f);
|
||||||
|
float blackmanWeight = 0.42f - 0.5f*cosf(x) + 0.08f*cosf(2.0f*x); // https://en.wikipedia.org/wiki/Window_function#Blackman_window
|
||||||
|
fftData->workBuffer[i].real = audioSamples[i]*blackmanWeight;
|
||||||
|
fftData->workBuffer[i].imaginary = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
CooleyTukeyFFTSlow(fftData->workBuffer, FFT_WINDOW_SIZE);
|
||||||
|
memcpy(fftData->spectrum, fftData->workBuffer, sizeof(FFTComplex)*FFT_WINDOW_SIZE);
|
||||||
|
|
||||||
|
float smoothedSpectrum[BUFFER_SIZE];
|
||||||
|
|
||||||
|
for (int bin = 0; bin < BUFFER_SIZE; bin++)
|
||||||
|
{
|
||||||
|
float re = fftData->workBuffer[bin].real;
|
||||||
|
float im = fftData->workBuffer[bin].imaginary;
|
||||||
|
float linearMagnitude = sqrtf(re*re + im*im)/FFT_WINDOW_SIZE;
|
||||||
|
|
||||||
|
float smoothedMagnitude = SMOOTHING_TIME_CONSTANT*fftData->prevMagnitudes[bin] + (1.0f - SMOOTHING_TIME_CONSTANT)*linearMagnitude;
|
||||||
|
fftData->prevMagnitudes[bin] = smoothedMagnitude;
|
||||||
|
|
||||||
|
float db = logf(fmaxf(smoothedMagnitude, 1e-40f))*DB_TO_LINEAR_SCALE;
|
||||||
|
float normalized = (db - MIN_DECIBELS)*INVERSE_DECIBEL_RANGE;
|
||||||
|
smoothedSpectrum[bin] = Clamp(normalized, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
fftData->lastFftTime = GetTime();
|
||||||
|
memcpy(fftData->fftHistory[fftData->historyPos], smoothedSpectrum, sizeof(smoothedSpectrum));
|
||||||
|
fftData->historyPos = (fftData->historyPos + 1) % fftData->fftHistoryLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RenderFrame(const FFTData *fftData, Image *fftImage)
|
||||||
|
{
|
||||||
|
double framesSinceTapback = floor(fftData->tapbackPos/WINDOW_TIME);
|
||||||
|
framesSinceTapback = Clamp(framesSinceTapback, 0.0, fftData->fftHistoryLen - 1);
|
||||||
|
|
||||||
|
int historyPosition = (fftData->historyPos - 1 - (int)framesSinceTapback) % fftData->fftHistoryLen;
|
||||||
|
if (historyPosition < 0)
|
||||||
|
historyPosition += fftData->fftHistoryLen;
|
||||||
|
|
||||||
|
const float *amplitude = fftData->fftHistory[historyPosition];
|
||||||
|
for (int bin = 0; bin < BUFFER_SIZE; bin++) {
|
||||||
|
ImageDrawPixel(fftImage, bin, FFT_ROW, ColorFromNormalized((Vector4){ amplitude[bin], UNUSED_CHANNEL, UNUSED_CHANNEL, UNUSED_CHANNEL }));
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
examples/audio/audio_fft_spectrum_visualizer.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
32
examples/audio/resources/fft.glsl
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#version 330
|
||||||
|
|
||||||
|
in vec2 fragTexCoord;
|
||||||
|
in vec4 fragColor;
|
||||||
|
|
||||||
|
out vec4 finalColor;
|
||||||
|
|
||||||
|
uniform vec2 iResolution;
|
||||||
|
uniform sampler2D iChannel0;
|
||||||
|
|
||||||
|
const vec4 BLACK = vec4(0.0, 0.0, 0.0, 1.0);
|
||||||
|
const vec4 WHITE = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
|
const float FFT_ROW = 0.0;
|
||||||
|
const float NUM_OF_BINS = 512.0;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 fragCoord = fragTexCoord*iResolution;
|
||||||
|
float cell_width = iResolution.x/NUM_OF_BINS;
|
||||||
|
float bin_index = floor(fragCoord.x/cell_width);
|
||||||
|
float local_x = mod(fragCoord.x, cell_width);
|
||||||
|
float bar_width = cell_width - 1.0;
|
||||||
|
vec4 color = BLACK;
|
||||||
|
if (local_x <= bar_width) {
|
||||||
|
float sample_x = (bin_index + 0.5)/NUM_OF_BINS;
|
||||||
|
vec2 sample_coord = vec2(sample_x, FFT_ROW);
|
||||||
|
float amplitude = texture(iChannel0, sample_coord).r; // only filled the red channel, all channels left open for alternative use
|
||||||
|
if (fragTexCoord.y < amplitude) {
|
||||||
|
color = WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalColor = color;
|
||||||
|
}
|
||||||
@ -196,7 +196,6 @@ int main(void)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
// Draw background: generic
|
// Draw background: generic
|
||||||
DrawRectangleRounded((Rectangle){ 175, 110, 460, 220}, 0.3f, 16, DARKGRAY);
|
DrawRectangleRounded((Rectangle){ 175, 110, 460, 220}, 0.3f, 16, DARKGRAY);
|
||||||
|
|
||||||
@ -269,7 +268,6 @@ int main(void)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
DrawText(TextFormat("GP%d: NOT DETECTED", gamepad), 10, 10, 10, GRAY);
|
DrawText(TextFormat("GP%d: NOT DETECTED", gamepad), 10, 10, 10, GRAY);
|
||||||
|
|
||||||
DrawTexture(texXboxPad, 0, 0, LIGHTGRAY);
|
DrawTexture(texXboxPad, 0, 0, LIGHTGRAY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
//------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------
|
||||||
// Program main entry point
|
// Program main entry point
|
||||||
//------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------
|
||||||
int main()
|
int main(void)
|
||||||
{
|
{
|
||||||
// Initialization
|
// Initialization
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
|
|||||||
116
examples/models/models_directional_billboard.c
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
/*******************************************************************************************
|
||||||
|
*
|
||||||
|
* raylib [models] example - directional billboard
|
||||||
|
*
|
||||||
|
* Example complexity rating: [★★☆☆] 2/4
|
||||||
|
*
|
||||||
|
* Example originally created with raylib 5.6-dev, 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-2025 Robin (@RobinsAviary)
|
||||||
|
* Killbot art by patvanmackelberg https://opengameart.org/content/killbot-8-directional under CC0
|
||||||
|
*
|
||||||
|
********************************************************************************************/
|
||||||
|
|
||||||
|
#include "raylib.h"
|
||||||
|
#include "raymath.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
// Program main entry point
|
||||||
|
//------------------------------------------------------------------------------------
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
// Initialization
|
||||||
|
//--------------------------------------------------------------------------------------
|
||||||
|
const int screenWidth = 800;
|
||||||
|
const int screenHeight = 450;
|
||||||
|
|
||||||
|
InitWindow(screenWidth, screenHeight, "raylib [models] example - directional billboard");
|
||||||
|
|
||||||
|
// Set up the camera
|
||||||
|
Camera camera = { 0 };
|
||||||
|
camera.position = (Vector3){ 2.0f, 1.0f, 2.0f }; // Starting position
|
||||||
|
camera.target = (Vector3){ 0.0f, 0.5f, 0.0f }; // Target position
|
||||||
|
camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Up vector
|
||||||
|
camera.fovy = 45.0f; // FOV
|
||||||
|
camera.projection = CAMERA_PERSPECTIVE; // Projection type (Standard 3D perspective)
|
||||||
|
|
||||||
|
// Load billboard texture
|
||||||
|
Texture skillbot = LoadTexture("resources/skillbot.png");
|
||||||
|
|
||||||
|
// Timer to update animation
|
||||||
|
float anim_timer = 0.0f;
|
||||||
|
// Animation frame
|
||||||
|
unsigned int anim = 0;
|
||||||
|
|
||||||
|
SetTargetFPS(60);
|
||||||
|
//--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Main game loop
|
||||||
|
while (!WindowShouldClose()) // Detect window close button or ESC key
|
||||||
|
{
|
||||||
|
// Update
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
UpdateCamera(&camera, CAMERA_ORBITAL);
|
||||||
|
|
||||||
|
// Update timer with delta time
|
||||||
|
anim_timer += GetFrameTime();
|
||||||
|
|
||||||
|
// Update frame index after a certain amount of time (half a second)
|
||||||
|
if (anim_timer > 0.5f)
|
||||||
|
{
|
||||||
|
anim_timer = 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
|
||||||
|
float dir = (float)floor(((Vector2Angle((Vector2){ 2.0f, 0.0f }, (Vector2){ camera.position.x, camera.position.z })/PI)*4.0f) + 0.25f);
|
||||||
|
|
||||||
|
// Correct frame index if angle is negative
|
||||||
|
if (dir < 0.0f)
|
||||||
|
{
|
||||||
|
dir = 8.0f - (float)abs((int)dir);
|
||||||
|
}
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
BeginDrawing();
|
||||||
|
|
||||||
|
ClearBackground(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, (Rectangle){ 0.0f + (anim*24.0f), 0.0f + (dir*24.0f), 24.0f, 24.0f }, Vector3Zero(), (Vector3){ 0.0f, 1.0f, 0.0f }, Vector2One(), (Vector2){ 0.5f, 0.0f }, 0, WHITE);
|
||||||
|
|
||||||
|
EndMode3D();
|
||||||
|
|
||||||
|
// Render various variables for reference
|
||||||
|
DrawText(TextFormat("animation: %d", anim), 10, 10, 20, DARKGRAY);
|
||||||
|
DrawText(TextFormat("direction frame: %.0f", dir), 10, 40, 20, DARKGRAY);
|
||||||
|
|
||||||
|
EndDrawing();
|
||||||
|
//----------------------------------------------------------------------------------
|
||||||
|
}
|
||||||
|
|
||||||
|
// De-Initialization
|
||||||
|
//--------------------------------------------------------------------------------------
|
||||||
|
// Unload billboard texture
|
||||||
|
UnloadTexture(skillbot);
|
||||||
|
|
||||||
|
CloseWindow(); // Close window and OpenGL context
|
||||||
|
//--------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
BIN
examples/models/models_directional_billboard.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
@ -32,7 +32,7 @@ static Mesh GenMeshPoints(int numPoints);
|
|||||||
//------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------
|
||||||
// Program main entry point
|
// Program main entry point
|
||||||
//------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------
|
||||||
int main()
|
int main(void)
|
||||||
{
|
{
|
||||||
// Initialization
|
// Initialization
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
//------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------
|
||||||
// Program main entry point
|
// Program main entry point
|
||||||
//------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------
|
||||||
int main()
|
int main(void)
|
||||||
{
|
{
|
||||||
// Initialization
|
// Initialization
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
|
|||||||
BIN
examples/models/resources/skillbot.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
@ -36,7 +36,7 @@ void UpdateDrawFrame(void); // Update and Draw one frame
|
|||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
// Program main entry point
|
// Program main entry point
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
int main()
|
int main(void)
|
||||||
{
|
{
|
||||||
// Initialization
|
// Initialization
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
|
|||||||
@ -77,7 +77,7 @@ static void UpdateLight(Shader shader, Light light);
|
|||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
// Program main entry point
|
// Program main entry point
|
||||||
//----------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------
|
||||||
int main()
|
int main(void)
|
||||||
{
|
{
|
||||||
// Initialization
|
// Initialization
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
| explosion.png | [Unity Labs Paris](https://blogs.unity3d.com/2016/11/28/free-vfx-image-sequences-flipbooks/) | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) |
|
| explosion.png | [Unity Labs Paris](https://blogs.unity3d.com/2016/11/28/free-vfx-image-sequences-flipbooks/) | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) |
|
||||||
| parrots.png | [Kodak set](http://r0k.us/graphics/kodak/) | ❔ | Original name: `kodim23.png`
|
| parrots.png | [Kodak set](http://r0k.us/graphics/kodak/) | ❔ | Original name: `kodim23.png`
|
||||||
| cat.png | ❔ | ❔ | - |
|
| cat.png | ❔ | ❔ | - |
|
||||||
| wabbit_alpha.png | ❔ | ❔ | - |
|
| raybunny.png | [VoidSrc*](https://x.com/voidsrc) | [CC0](https://creativecommons.org/publicdomain/zero/1.0/) | - |
|
||||||
| custom_jupiter_crash.png | [Brian Kent (AEnigma)](https://www.dafont.com/es/aenigma.d188) | [Freeware](https://www.dafont.com/es/jupiter-crash.font) | Atlas created by [@raysan5](https://github.com/raysan5) |
|
| custom_jupiter_crash.png | [Brian Kent (AEnigma)](https://www.dafont.com/es/aenigma.d188) | [Freeware](https://www.dafont.com/es/jupiter-crash.font) | Atlas created by [@raysan5](https://github.com/raysan5) |
|
||||||
| KAISG.ttf | [Dieter Steffmann](http://www.steffmann.de/wordpress/) | [Freeware](https://www.1001fonts.com/users/steffmann/) | [Kaiserzeit Gotisch](https://www.dafont.com/es/kaiserzeit-gotisch.font) font |
|
| KAISG.ttf | [Dieter Steffmann](http://www.steffmann.de/wordpress/) | [Freeware](https://www.1001fonts.com/users/steffmann/) | [Kaiserzeit Gotisch](https://www.dafont.com/es/kaiserzeit-gotisch.font) font |
|
||||||
| fudesumi.png | [Eiden Marsal](https://www.artstation.com/marshall_z) | [CC-BY-NC](https://creativecommons.org/licenses/by-nc/4.0/) | - |
|
| fudesumi.png | [Eiden Marsal](https://www.artstation.com/marshall_z) | [CC-BY-NC](https://creativecommons.org/licenses/by-nc/4.0/) | - |
|
||||||
|
|||||||
BIN
examples/textures/resources/raybunny.png
Normal file
|
After Width: | Height: | Size: 466 B |
|
Before Width: | Height: | Size: 496 B |
@ -45,7 +45,7 @@ int main(void)
|
|||||||
InitWindow(screenWidth, screenHeight, "raylib [textures] example - bunnymark");
|
InitWindow(screenWidth, screenHeight, "raylib [textures] example - bunnymark");
|
||||||
|
|
||||||
// Load bunny texture
|
// Load bunny texture
|
||||||
Texture2D texBunny = LoadTexture("resources/wabbit_alpha.png");
|
Texture2D texBunny = LoadTexture("resources/raybunny.png");
|
||||||
|
|
||||||
Bunny *bunnies = (Bunny *)malloc(MAX_BUNNIES*sizeof(Bunny)); // Bunnies array
|
Bunny *bunnies = (Bunny *)malloc(MAX_BUNNIES*sizeof(Bunny)); // Bunnies array
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 427 KiB After Width: | Height: | Size: 423 KiB |
@ -49,7 +49,7 @@ static void DrawTexturedCurve(void);
|
|||||||
//------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------
|
||||||
// Program main entry point
|
// Program main entry point
|
||||||
//------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------
|
||||||
int main()
|
int main(void)
|
||||||
{
|
{
|
||||||
// Initialization
|
// Initialization
|
||||||
//--------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------
|
||||||
|
|||||||
@ -121,6 +121,7 @@ typedef enum {
|
|||||||
OP_VALIDATE = 5, // Validate examples, using [examples_list.txt] as main source by default
|
OP_VALIDATE = 5, // Validate examples, using [examples_list.txt] as main source by default
|
||||||
OP_UPDATE = 6, // Validate and update required examples (as far as possible)
|
OP_UPDATE = 6, // Validate and update required examples (as far as possible)
|
||||||
OP_BUILD = 7, // Build example for desktop and web, copy web output
|
OP_BUILD = 7, // Build example for desktop and web, copy web output
|
||||||
|
OP_TEST = 8, // Test example: check output LOG WARNINGS
|
||||||
} rlExampleOperation;
|
} rlExampleOperation;
|
||||||
|
|
||||||
static const char *exCategories[REXM_MAX_EXAMPLE_CATEGORIES] = { "core", "shapes", "textures", "text", "models", "shaders", "audio", "others" };
|
static const char *exCategories[REXM_MAX_EXAMPLE_CATEGORIES] = { "core", "shapes", "textures", "text", "models", "shaders", "audio", "others" };
|
||||||
@ -404,7 +405,7 @@ int main(int argc, char *argv[])
|
|||||||
char *exColInfo = LoadFileText(exCollectionFilePath);
|
char *exColInfo = LoadFileText(exCollectionFilePath);
|
||||||
if (TextFindIndex(exColInfo, argv[2]) != -1) // Example in the collection
|
if (TextFindIndex(exColInfo, argv[2]) != -1) // Example in the collection
|
||||||
{
|
{
|
||||||
strcpy(exName, argv[2]); // Register example name for removal
|
strcpy(exName, argv[2]); // Register example name
|
||||||
strncpy(exCategory, exName, TextFindIndex(exName, "_"));
|
strncpy(exCategory, exName, TextFindIndex(exName, "_"));
|
||||||
opCode = OP_BUILD;
|
opCode = OP_BUILD;
|
||||||
}
|
}
|
||||||
@ -413,6 +414,36 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (strcmp(argv[1], "test") == 0)
|
||||||
|
{
|
||||||
|
// Build and test example for PLATFORM_DESKTOP
|
||||||
|
// NOTE: Build outputs to default directory, usually where the .c file is located,
|
||||||
|
// to avoid issues with copying resources (at least on Desktop)
|
||||||
|
if (argc == 2) LOG("WARNING: No example name provided to test\n");
|
||||||
|
else if (argc > 3) LOG("WARNING: Too many arguments provided\n");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Support building not only individual examples but categories and "ALL"
|
||||||
|
if ((strcmp(argv[2], "ALL") == 0) || TextInList(argv[2], exCategories, REXM_MAX_EXAMPLE_CATEGORIES))
|
||||||
|
{
|
||||||
|
// Category/ALL rebuilt requested
|
||||||
|
strcpy(exRebuildRequested, argv[2]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Verify example exists in collection to be removed
|
||||||
|
char *exColInfo = LoadFileText(exCollectionFilePath);
|
||||||
|
if (TextFindIndex(exColInfo, argv[2]) != -1) // Example in the collection
|
||||||
|
{
|
||||||
|
strcpy(exName, argv[2]); // Register example name
|
||||||
|
strncpy(exCategory, exName, TextFindIndex(exName, "_"));
|
||||||
|
opCode = OP_TEST;
|
||||||
|
}
|
||||||
|
else LOG("WARNING: TEST: Example requested not available in the collection\n");
|
||||||
|
UnloadFileText(exColInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Process command line options arguments
|
// Process command line options arguments
|
||||||
for (int i = 1; i < argc; i++)
|
for (int i = 1; i < argc; i++)
|
||||||
@ -1487,6 +1518,94 @@ int main(int argc, char *argv[])
|
|||||||
UnloadExamplesData(exCollection);
|
UnloadExamplesData(exCollection);
|
||||||
//------------------------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
} break;
|
||||||
|
case OP_TEST:
|
||||||
|
{
|
||||||
|
LOG("INFO: Command requested: TEST\n");
|
||||||
|
LOG("INFO: Example to be built and tested: %s\n", exName);
|
||||||
|
|
||||||
|
// Steps to follow
|
||||||
|
// STEP 1: Load example.c and replace required code to inject basic testing code: frames to run
|
||||||
|
// OPTION 1: Code injection required multiple changes for testing but it does not require raylib changes!
|
||||||
|
// OPTION 2: Support testing on raylib side: Args processing and events injection: SUPPORT_AUTOMATD_TESTING_SYSTEM, EVENTS_TESTING_MODE
|
||||||
|
// STEP 2: Build example (PLATFORM_DESKTOP)
|
||||||
|
// STEP 3: Run example with arguments: --frames 2 > <example>.out.log
|
||||||
|
// STEP 4: Load <example>.out.log and check "WARNING:" messages -> Some could maybe be ignored
|
||||||
|
// STEP 5: Generate report with results
|
||||||
|
|
||||||
|
// STEP 1: Load example and inject required code
|
||||||
|
// PROBLEM: As we need to modify the example source code for building, we need to keep a copy or something
|
||||||
|
// WARNING: If we make a copy and something fails, it could not be restored at the end
|
||||||
|
// PROBLEM: Trying to build a copy won't work because Makefile is setup to look for specific example on specific path -> No output dir config
|
||||||
|
// IDEA: Create directory for testing data -> It implies moving files and set working dir...
|
||||||
|
// SOLUTION: Make a copy of original file -> Modify original -> Build -> Rename to <example>.test.exe
|
||||||
|
FileCopy(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName),
|
||||||
|
TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName));
|
||||||
|
char *srcText = LoadFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName));
|
||||||
|
|
||||||
|
static const char *mainReplaceText =
|
||||||
|
"#include <string.h>\n"
|
||||||
|
"#include <stdlib.h>\n"
|
||||||
|
"int main(int argc, char *argv[])\n{\n"
|
||||||
|
" int requestedTestFrames = 0;\n"
|
||||||
|
" int testFramesCount = 0;\n"
|
||||||
|
" if ((argc > 1) && (argc == 3) && (strcmp(argv[1], \"--frames\") != 0)) requestedTestFrames = atoi(argv[2]);\n";
|
||||||
|
|
||||||
|
char *srcTextUpdated[3] = { 0 };
|
||||||
|
srcTextUpdated[0] = TextReplace(srcText, "int main(void)\n{", mainReplaceText);
|
||||||
|
srcTextUpdated[1] = TextReplace(srcTextUpdated[0], "WindowShouldClose()", "WindowShouldClose() && (testFramesCount < requestedTestFrames)");
|
||||||
|
srcTextUpdated[2] = TextReplace(srcTextUpdated[1], "EndDrawing();", "EndDrawing(); testFramesCount++;");
|
||||||
|
UnloadFileText(srcText);
|
||||||
|
|
||||||
|
SaveFileText(TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName), srcTextUpdated[2]);
|
||||||
|
for (int i = 0; i < 3; i++) { MemFree(srcTextUpdated[i]); srcTextUpdated[i] = NULL; }
|
||||||
|
|
||||||
|
// STEP 2: Build example for DESKTOP platform
|
||||||
|
#if defined(_WIN32)
|
||||||
|
// Set required environment variables
|
||||||
|
//putenv(TextFormat("RAYLIB_DIR=%s\\..", exBasePath));
|
||||||
|
_putenv("PATH=%PATH%;C:\\raylib\\w64devkit\\bin");
|
||||||
|
//putenv("MAKE=mingw32-make");
|
||||||
|
//ChangeDirectory(exBasePath);
|
||||||
|
#endif
|
||||||
|
// Build example for PLATFORM_DESKTOP
|
||||||
|
#if defined(_WIN32)
|
||||||
|
LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: Win32)\n", exName);
|
||||||
|
system(TextFormat("mingw32-make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName));
|
||||||
|
#else
|
||||||
|
LOG("INFO: [%s] Building example for PLATFORM_DESKTOP (Host: POSIX)\n", exName);
|
||||||
|
system(TextFormat("make -C %s %s/%s PLATFORM=PLATFORM_DESKTOP -B", exBasePath, exCategory, exName));
|
||||||
|
#endif
|
||||||
|
// Restore original source code before continue
|
||||||
|
FileCopy(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName),
|
||||||
|
TextFormat("%s/%s/%s.c", exBasePath, exCategory, exName));
|
||||||
|
FileRemove(TextFormat("%s/%s/%s.original.c", exBasePath, exCategory, exName));
|
||||||
|
|
||||||
|
// STEP 3: Run example with required arguments
|
||||||
|
ChangeDirectory(TextFormat("%s/%s", exBasePath, exCategory));
|
||||||
|
system(TextFormat("%s --frames 2 > %s.log", exName, exName));
|
||||||
|
|
||||||
|
// STEP 4: Load and validate log -> WARNINGS
|
||||||
|
char *exTestLog = LoadFileText(TextFormat("%s/%s/%s.log", exBasePath, exCategory, exName));
|
||||||
|
int exTestLogLinesCount = 0;
|
||||||
|
char **exTestLogLines = LoadTextLines(exTestLog, &exTestLogLinesCount);
|
||||||
|
UnloadFileText(exTestLog);
|
||||||
|
|
||||||
|
int issueCounter = false;
|
||||||
|
for (int i = 0; i < exTestLogLinesCount; i++)
|
||||||
|
{
|
||||||
|
if (TextFindIndex(exTestLogLines[i], "WARNING") >= 0)
|
||||||
|
{
|
||||||
|
LOG("TEST: [%s] %s\n", exName, exTestLogLines[i]);
|
||||||
|
issueCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UnloadTextLines(exTestLogLines, exTestLogLinesCount);
|
||||||
|
|
||||||
|
// STEP 5: Generate auto-test report
|
||||||
|
//if (issueCounter > 0)
|
||||||
|
|
||||||
} break;
|
} break;
|
||||||
default: // Help
|
default: // Help
|
||||||
{
|
{
|
||||||
@ -2155,11 +2274,13 @@ static char **ScanExampleResources(const char *filePath, int *resPathCount)
|
|||||||
int functionIndex02 = TextFindIndex(ptr - 10, "TraceLog"); // Check TraceLog()
|
int functionIndex02 = TextFindIndex(ptr - 10, "TraceLog"); // Check TraceLog()
|
||||||
int functionIndex03 = TextFindIndex(ptr - 40, "TakeScreenshot"); // Check TakeScreenshot()
|
int functionIndex03 = TextFindIndex(ptr - 40, "TakeScreenshot"); // Check TakeScreenshot()
|
||||||
int functionIndex04 = TextFindIndex(ptr - 40, "SaveFileData"); // Check SaveFileData()
|
int functionIndex04 = TextFindIndex(ptr - 40, "SaveFileData"); // Check SaveFileData()
|
||||||
|
int functionIndex05 = TextFindIndex(ptr - 40, "SaveFileText"); // Check SaveFileText()
|
||||||
|
|
||||||
if (!((functionIndex01 != -1) && (functionIndex01 < 40)) && // Not found ExportImage() before ""
|
if (!((functionIndex01 != -1) && (functionIndex01 < 40)) && // Not found ExportImage() before ""
|
||||||
!((functionIndex02 != -1) && (functionIndex02 < 10)) && // Not found TraceLog() before ""
|
!((functionIndex02 != -1) && (functionIndex02 < 10)) && // Not found TraceLog() before ""
|
||||||
!((functionIndex03 != -1) && (functionIndex03 < 40)) && // Not found TakeScreenshot() before ""
|
!((functionIndex03 != -1) && (functionIndex03 < 40)) && // Not found TakeScreenshot() before ""
|
||||||
!((functionIndex04 != -1) && (functionIndex04 < 40))) // Not found SaveFileData() before ""
|
!((functionIndex04 != -1) && (functionIndex04 < 40)) && // Not found TakeScreenshot() before ""
|
||||||
|
!((functionIndex05 != -1) && (functionIndex05 < 40))) // Not found SaveFileText() before ""
|
||||||
{
|
{
|
||||||
int len = (int)(end - start);
|
int len = (int)(end - start);
|
||||||
if ((len > 0) && (len < REXM_MAX_RESOURCE_PATH_LEN))
|
if ((len > 0) && (len < REXM_MAX_RESOURCE_PATH_LEN))
|
||||||
|
|||||||