4 Commits

Author SHA1 Message Date
a043255aa7 [examples] models_animation_blending: avoid crash when animations fail to load (#6234)
If resources/models/gltf/robot.glb can't be found (e.g. the example is
run from another working directory), LoadModelAnimations() returns NULL
with animCount = 0 and the example indexed anims[] in the update loop
and in the timeline drawing, crashing. Skip both when no animations
were loaded, so it just logs the usual load warnings.

Reported in #5664.
2026-10-08 15:33:10 +02:00
8c23ce44a0 [rcore][win32] Clean up window state in CloseWindow() (#6233) 2026-10-08 15:32:37 +02:00
48aaf6e2df [rcore][win32] Allow SetWindowSize() to change only the width or height (#6232) 2026-10-08 15:30:41 +02:00
0ed3570469 [raymath] Fix QuaternionFromVector3ToVector3() returning a zero quaternion for opposite vectors (#6230) 2026-10-08 15:26:36 +02:00
3 changed files with 62 additions and 19 deletions

View File

@ -114,7 +114,7 @@ int main(void)
if (IsKeyPressed(KEY_P)) animPause = !animPause; if (IsKeyPressed(KEY_P)) animPause = !animPause;
if (!animPause) if (!animPause && (animCount > 0)) // Animations could fail to load
{ {
// Start transition from anim0[] to anim1[] // Start transition from anim0[] to anim1[]
if (IsKeyPressed(KEY_SPACE) && !animTransition) if (IsKeyPressed(KEY_SPACE) && !animTransition)
@ -256,21 +256,24 @@ int main(void)
GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetFont().baseSize); GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetFont().baseSize);
GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT);
// Draw playing timeline with keyframes for anim0[] if (animCount > 0)
GuiProgressBar((Rectangle){ 60, GetScreenHeight() - 60.0f, GetScreenWidth() - 180.0f, 20 }, "ANIM 0", {
TextFormat("FRAME: %.2f / %i", animFrameProgress0, anims[animIndex0].keyframeCount), // Draw playing timeline with keyframes for anim0[]
&animFrameProgress0, 0.0f, (float)anims[animIndex0].keyframeCount); GuiProgressBar((Rectangle){ 60, GetScreenHeight() - 60.0f, GetScreenWidth() - 180.0f, 20 }, "ANIM 0",
for (int i = 0; i < anims[animIndex0].keyframeCount; i++) TextFormat("FRAME: %.2f / %i", animFrameProgress0, anims[animIndex0].keyframeCount),
DrawRectangle(60 + (int)(((float)(GetScreenWidth() - 180)/(float)anims[animIndex0].keyframeCount)*(float)i), &animFrameProgress0, 0.0f, (float)anims[animIndex0].keyframeCount);
GetScreenHeight() - 60, 1, 20, BLUE); for (int i = 0; i < anims[animIndex0].keyframeCount; i++)
DrawRectangle(60 + (int)(((float)(GetScreenWidth() - 180)/(float)anims[animIndex0].keyframeCount)*(float)i),
GetScreenHeight() - 60, 1, 20, BLUE);
// Draw playing timeline with keyframes for anim1[] // Draw playing timeline with keyframes for anim1[]
GuiProgressBar((Rectangle){ 60, GetScreenHeight() - 30.0f, GetScreenWidth() - 180.0f, 20 }, "ANIM 1", GuiProgressBar((Rectangle){ 60, GetScreenHeight() - 30.0f, GetScreenWidth() - 180.0f, 20 }, "ANIM 1",
TextFormat("FRAME: %.2f / %i", animFrameProgress1, anims[animIndex1].keyframeCount), TextFormat("FRAME: %.2f / %i", animFrameProgress1, anims[animIndex1].keyframeCount),
&animFrameProgress1, 0.0f, (float)anims[animIndex1].keyframeCount); &animFrameProgress1, 0.0f, (float)anims[animIndex1].keyframeCount);
for (int i = 0; i < anims[animIndex1].keyframeCount; i++) for (int i = 0; i < anims[animIndex1].keyframeCount; i++)
DrawRectangle(60 + (int)(((float)(GetScreenWidth() - 180)/(float)anims[animIndex1].keyframeCount)*(float)i), DrawRectangle(60 + (int)(((float)(GetScreenWidth() - 180)/(float)anims[animIndex1].keyframeCount)*(float)i),
GetScreenHeight() - 30, 1, 20, BLUE); GetScreenHeight() - 30, 1, 20, BLUE);
}
//--------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------
EndDrawing(); EndDrawing();

View File

@ -393,7 +393,7 @@ static bool UpdateWindowSize(int mode, HWND hwnd, int width, int height, unsigne
SIZE clientSize = { rect.right, rect.bottom }; SIZE clientSize = { rect.right, rect.bottom };
// If client size is alread desired size, no need to update // If client size is alread desired size, no need to update
if ((clientSize.cx == desiredSize.cx) || (clientSize.cy == desiredSize.cy)) return false; if ((clientSize.cx == desiredSize.cx) && (clientSize.cy == desiredSize.cy)) return false;
TRACELOG(LOG_INFO, "WIN32: Restoring client size from [%dx%d] to [%dx%d] (dpi:%lu dpiScaling:%d app:%ix%i)", TRACELOG(LOG_INFO, "WIN32: Restoring client size from [%dx%d] to [%dx%d] (dpi:%lu dpiScaling:%d app:%ix%i)",
clientSize.cx, clientSize.cy, desiredSize.cx, desiredSize.cy, dpi, dpiScaling, width, height); clientSize.cx, clientSize.cy, desiredSize.cx, desiredSize.cy, dpi, dpiScaling, width, height);
@ -1767,12 +1767,31 @@ int InitPlatform(void)
// Close platform // Close platform
void ClosePlatform(void) void ClosePlatform(void)
{ {
// Undo disable cursor if it's locked and unregister raw input devices
EnableCursor();
// Calling DestroyWindow invokes a WindowProc event that sets the FLAG_WINDOW_HIDDEN flag to true
// If InitWindow is called again, the hidden flag will be interpreted as something the user requested
// Save the current flags and restore them after
unsigned int savedFlags = CORE.Window.flags;
if (platform.hwnd) if (platform.hwnd)
{ {
int result = DestroyWindow(platform.hwnd); BOOL result = DestroyWindow(platform.hwnd);
if (result == 0) TRACELOG(LOG_WARNING, "WIN32: WINDOW: Failed on window destroy [ERROR: %u]", GetLastError()); if (!result) TRACELOG(LOG_WARNING, "WIN32: WINDOW: Failed on window destroy [ERROR: %u]", GetLastError());
HINSTANCE hInstance = GetModuleHandleW(0);
result = UnregisterClassW(CLASS_NAME, hInstance);
if (!result) TRACELOG(LOG_WARNING, "WIN32: WINDOW: Failed to unregister window class [ERROR: %u]", GetLastError());
platform.hwnd = NULL; platform.hwnd = NULL;
} }
CORE.Window.flags = savedFlags;
#if SUPPORT_WINMM_HIGHRES_TIMER && !SUPPORT_BUSY_WAIT_LOOP
timeEndPeriod(1); // Restore time period
#endif
} }
// Window procedure, message processing callback // Window procedure, message processing callback

View File

@ -2381,10 +2381,31 @@ RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to)
float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to)
Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to)
float lengths = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z + cos2Theta*cos2Theta); // |from|*|to|
result.x = cross.x; result.x = cross.x;
result.y = cross.y; result.y = cross.y;
result.z = cross.z; result.z = cross.z;
result.w = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z + cos2Theta*cos2Theta) + cos2Theta; result.w = lengths + cos2Theta;
// Opposite vectors: cross product is zero, rotate 180 degrees around any axis perpendicular to 'from'
if ((lengths > 0.0f) && (result.w <= lengths*EPSILON))
{
if (fabsf(from.x) > fabsf(from.z))
{
result.x = -from.y;
result.y = from.x;
result.z = 0.0f;
}
else
{
result.x = 0.0f;
result.y = -from.z;
result.z = from.y;
}
result.w = 0.0f;
}
// QuaternionNormalize(q); // QuaternionNormalize(q);
// NOTE: Normalize to essentially nlerp the original and identity to 0.5 // NOTE: Normalize to essentially nlerp the original and identity to 0.5