From 7fab03c0b4f079a6450f03d82ca17f2e18be3477 Mon Sep 17 00:00:00 2001 From: hanaxars <156359933+hanaxars@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:41:20 +0300 Subject: [PATCH 01/28] Fix warnings (#4264) Fix following gcc warnings when SVG enabled: rtextures.c: In function 'LoadImageSvg': rtextures.c:374:52: warning: pointer targets in passing argument 1 of 'nsvgParse' differ in signedness [-Wpointer-sign] 374 | struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); | ^~~~~~~~ | | | unsigned char * In file included from rtextures.c:230: external/nanosvg.h:2952:28: note: expected 'char *' but argument is of type 'unsigned char *' 2952 | NSVGimage* nsvgParse(char* input, const char* units, float dpi) | ~~~~~~^~~~~ rtextures.c:407:43: warning: comparison of distinct pointer types lacks a cast [-Wcompare-distinct-pointer-types] 407 | if (isSvgStringValid && (fileData != fileNameOrString)) UnloadFileData(fileData); | ^~ rtextures.c: In function 'LoadImageFromMemory': rtextures.c:614:52: warning: passing argument 1 of 'nsvgParse' discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers] 614 | struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); | ^~~~~~~~ external/nanosvg.h:2952:28: note: expected 'char *' but argument is of type 'const unsigned char *' 2952 | NSVGimage* nsvgParse(char* input, const char* units, float dpi) | ~~~~~~^~~~~ --- src/rtextures.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rtextures.c b/src/rtextures.c index 5fa01c9df..f2faa1850 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -371,7 +371,7 @@ Image LoadImageSvg(const char *fileNameOrString, int width, int height) if (isSvgStringValid) { - struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); + struct NSVGimage *svgImage = nsvgParse((char *)fileData, "px", 96.0f); unsigned char *img = RL_MALLOC(width*height*4); @@ -404,7 +404,7 @@ Image LoadImageSvg(const char *fileNameOrString, int width, int height) nsvgDeleteRasterizer(rast); } - if (isSvgStringValid && (fileData != fileNameOrString)) UnloadFileData(fileData); + if (isSvgStringValid && (fileData != (unsigned char *)fileNameOrString)) UnloadFileData(fileData); } #else TRACELOG(LOG_WARNING, "SVG image support not enabled, image can not be loaded"); @@ -611,7 +611,7 @@ Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, i (fileData[2] == 'v') && (fileData[3] == 'g')) { - struct NSVGimage *svgImage = nsvgParse(fileData, "px", 96.0f); + struct NSVGimage *svgImage = nsvgParse((char *)fileData, "px", 96.0f); unsigned char *img = RL_MALLOC(svgImage->width*svgImage->height*4); // Rasterize From 039df36f4b73cfd685c143f47e71abf93270e709 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 20 Aug 2024 19:13:18 +0200 Subject: [PATCH 02/28] REVIEWED: Automation events mouse wheel #4263 --- examples/core/core_automation_events.c | 70 +++++++++++++------------- src/rcore.c | 7 +-- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/examples/core/core_automation_events.c b/examples/core/core_automation_events.c index 319b013ba..64ca51fda 100644 --- a/examples/core/core_automation_events.c +++ b/examples/core/core_automation_events.c @@ -88,7 +88,7 @@ int main(void) // Update //---------------------------------------------------------------------------------- float deltaTime = 0.015f;//GetFrameTime(); - + // Dropped files logic //---------------------------------------------------------------------------------- if (IsFileDropped()) @@ -157,11 +157,6 @@ int main(void) } else player.canJump = true; - 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; - if (IsKeyPressed(KEY_R)) { // Reset game state @@ -176,12 +171,44 @@ int main(void) } //---------------------------------------------------------------------------------- + // 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(LOG_INFO, "FINISH PLAYING!"); + break; + } + } + + playFrameCounter++; + } + //---------------------------------------------------------------------------------- + // Update camera //---------------------------------------------------------------------------------- camera.target = player.position; camera.offset = (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]; @@ -200,8 +227,8 @@ int main(void) if (min.y > 0) camera.offset.y = screenHeight/2 - min.y; //---------------------------------------------------------------------------------- - // Toggle events recording - if (IsKeyPressed(KEY_S)) + // Events management + if (IsKeyPressed(KEY_S)) // Toggle events recording { if (!eventPlaying) { @@ -222,7 +249,7 @@ int main(void) } } } - else if (IsKeyPressed(KEY_A)) + else if (IsKeyPressed(KEY_A)) // Toggle events playing (WARNING: Starts next frame) { if (!eventRecording && (aelist.count > 0)) { @@ -241,32 +268,7 @@ int main(void) camera.zoom = 1.0f; } } - - if (eventPlaying) - { - // NOTE: Multiple events could be executed in a single frame - while (playFrameCounter == aelist.events[currentPlayFrame].frame) - { - TraceLog(LOG_INFO, "PLAYING: PlayFrameCount: %i | currentPlayFrame: %i | Event Frame: %i, param: %i", - playFrameCounter, currentPlayFrame, aelist.events[currentPlayFrame].frame, aelist.events[currentPlayFrame].params[0]); - - PlayAutomationEvent(aelist.events[currentPlayFrame]); - currentPlayFrame++; - if (currentPlayFrame == aelist.count) - { - eventPlaying = false; - currentPlayFrame = 0; - playFrameCounter = 0; - - TraceLog(LOG_INFO, "FINISH PLAYING!"); - break; - } - } - - playFrameCounter++; - } - if (eventRecording || eventPlaying) frameCounter++; else frameCounter = 0; //---------------------------------------------------------------------------------- diff --git a/src/rcore.c b/src/rcore.c index 5485ce8ce..e8041ea41 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -669,7 +669,6 @@ void InitWindow(int width, int height, const char *title) #endif #endif - CORE.Time.frameCounter = 0; CORE.Window.shouldClose = false; @@ -2707,8 +2706,8 @@ void PlayAutomationEvent(AutomationEvent event) } break; case INPUT_MOUSE_WHEEL_MOTION: // param[0]: x delta, param[1]: y delta { - CORE.Input.Mouse.currentWheelMove.x = (float)event.params[0]; break; - CORE.Input.Mouse.currentWheelMove.y = (float)event.params[1]; break; + CORE.Input.Mouse.currentWheelMove.x = (float)event.params[0]; + CORE.Input.Mouse.currentWheelMove.y = (float)event.params[1]; } break; case INPUT_TOUCH_UP: CORE.Input.Touch.currentTouchState[event.params[0]] = false; break; // param[0]: id case INPUT_TOUCH_DOWN: CORE.Input.Touch.currentTouchState[event.params[0]] = true; break; // param[0]: id @@ -2745,6 +2744,8 @@ void PlayAutomationEvent(AutomationEvent event) case ACTION_SETTARGETFPS: SetTargetFPS(event.params[0]); break; default: break; } + + TRACELOG(LOG_INFO, "AUTOMATION PLAY: Frame: %i | Event type: %i | Event parameters: %i, %i, %i", event.frame, event.type, event.params[0], event.params[1], event.params[2]); } #endif } From fa3f73d88174fb66d9b275e2970e4d201e6a9ecb Mon Sep 17 00:00:00 2001 From: Paperdomo101 <38697390+Paperdomo101@users.noreply.github.com> Date: Wed, 21 Aug 2024 20:07:52 +0800 Subject: [PATCH 03/28] [rshapes] Review DrawGradient color parameter names (#4270) void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2); void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2); void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2); void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); Have been changed to: void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); --- src/raylib.h | 8 ++++---- src/rshapes.c | 30 +++++++++++++----------------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/raylib.h b/src/raylib.h index 1cf34f004..0759c259f 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1238,7 +1238,7 @@ RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color c RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline -RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2); // Draw a gradient-filled circle +RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) @@ -1250,9 +1250,9 @@ RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color) RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters -RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a vertical-gradient-filled rectangle -RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle -RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges diff --git a/src/rshapes.c b/src/rshapes.c index 676b2521a..66dfbf3b6 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -430,17 +430,16 @@ void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float } // Draw a gradient-filled circle -// NOTE: Gradient goes from center (color1) to border (color2) -void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2) +void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer) { rlBegin(RL_TRIANGLES); for (int i = 0; i < 360; i += 10) { - rlColor4ub(color1.r, color1.g, color1.b, color1.a); + rlColor4ub(inner.r, inner.g, inner.b, inner.a); rlVertex2f((float)centerX, (float)centerY); - rlColor4ub(color2.r, color2.g, color2.b, color2.a); + rlColor4ub(outer.r, outer.g, outer.b, outer.a); rlVertex2f((float)centerX + cosf(DEG2RAD*(i + 10))*radius, (float)centerY + sinf(DEG2RAD*(i + 10))*radius); - rlColor4ub(color2.r, color2.g, color2.b, color2.a); + rlColor4ub(outer.r, outer.g, outer.b, outer.a); rlVertex2f((float)centerX + cosf(DEG2RAD*i)*radius, (float)centerY + sinf(DEG2RAD*i)*radius); } rlEnd(); @@ -761,22 +760,19 @@ void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color } // Draw a vertical-gradient-filled rectangle -// NOTE: Gradient goes from bottom (color1) to top (color2) -void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) +void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom) { - DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color1, color2, color2, color1); + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, top, bottom, bottom, top); } // Draw a horizontal-gradient-filled rectangle -// NOTE: Gradient goes from bottom (color1) to top (color2) -void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2) +void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right) { - DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, color1, color1, color2, color2); + DrawRectangleGradientEx((Rectangle){ (float)posX, (float)posY, (float)width, (float)height }, left, left, right, right); } // Draw a gradient-filled rectangle -// NOTE: Colors refer to corners, starting at top-lef corner and counter-clockwise -void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4) +void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight) { rlSetTexture(GetShapesTexture().id); Rectangle shapeRect = GetShapesTextureRectangle(); @@ -785,19 +781,19 @@ void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, rlNormal3f(0.0f, 0.0f, 1.0f); // NOTE: Default raylib font character 95 is a white square - rlColor4ub(col1.r, col1.g, col1.b, col1.a); + rlColor4ub(topLeft.r, topLeft.g, topLeft.b, topLeft.a); rlTexCoord2f(shapeRect.x/texShapes.width, shapeRect.y/texShapes.height); rlVertex2f(rec.x, rec.y); - rlColor4ub(col2.r, col2.g, col2.b, col2.a); + rlColor4ub(bottomLeft.r, bottomLeft.g, bottomLeft.b, bottomLeft.a); rlTexCoord2f(shapeRect.x/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); rlVertex2f(rec.x, rec.y + rec.height); - rlColor4ub(col3.r, col3.g, col3.b, col3.a); + rlColor4ub(topRight.r, topRight.g, topRight.b, topRight.a); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, (shapeRect.y + shapeRect.height)/texShapes.height); rlVertex2f(rec.x + rec.width, rec.y + rec.height); - rlColor4ub(col4.r, col4.g, col4.b, col4.a); + rlColor4ub(bottomRight.r, bottomRight.g, bottomRight.b, bottomRight.a); rlTexCoord2f((shapeRect.x + shapeRect.width)/texShapes.width, shapeRect.y/texShapes.height); rlVertex2f(rec.x + rec.width, rec.y); rlEnd(); From bae0af241ed8ecf0b8107d3d3c43fef297e038d3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 21 Aug 2024 12:08:09 +0000 Subject: [PATCH 04/28] Update raylib_api.* by CI --- parser/output/raylib_api.json | 20 ++++++++++---------- parser/output/raylib_api.lua | 20 ++++++++++---------- parser/output/raylib_api.txt | 20 ++++++++++---------- parser/output/raylib_api.xml | 20 ++++++++++---------- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index f149d628f..c4ce91e87 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -5485,11 +5485,11 @@ }, { "type": "Color", - "name": "color1" + "name": "inner" }, { "type": "Color", - "name": "color2" + "name": "outer" } ] }, @@ -5785,11 +5785,11 @@ }, { "type": "Color", - "name": "color1" + "name": "top" }, { "type": "Color", - "name": "color2" + "name": "bottom" } ] }, @@ -5816,11 +5816,11 @@ }, { "type": "Color", - "name": "color1" + "name": "left" }, { "type": "Color", - "name": "color2" + "name": "right" } ] }, @@ -5835,19 +5835,19 @@ }, { "type": "Color", - "name": "col1" + "name": "topLeft" }, { "type": "Color", - "name": "col2" + "name": "bottomLeft" }, { "type": "Color", - "name": "col3" + "name": "topRight" }, { "type": "Color", - "name": "col4" + "name": "bottomRight" } ] }, diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 7c00fff33..3a9e2f04f 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -4697,8 +4697,8 @@ return { {type = "int", name = "centerX"}, {type = "int", name = "centerY"}, {type = "float", name = "radius"}, - {type = "Color", name = "color1"}, - {type = "Color", name = "color2"} + {type = "Color", name = "inner"}, + {type = "Color", name = "outer"} } }, { @@ -4835,8 +4835,8 @@ return { {type = "int", name = "posY"}, {type = "int", name = "width"}, {type = "int", name = "height"}, - {type = "Color", name = "color1"}, - {type = "Color", name = "color2"} + {type = "Color", name = "top"}, + {type = "Color", name = "bottom"} } }, { @@ -4848,8 +4848,8 @@ return { {type = "int", name = "posY"}, {type = "int", name = "width"}, {type = "int", name = "height"}, - {type = "Color", name = "color1"}, - {type = "Color", name = "color2"} + {type = "Color", name = "left"}, + {type = "Color", name = "right"} } }, { @@ -4858,10 +4858,10 @@ return { returnType = "void", params = { {type = "Rectangle", name = "rec"}, - {type = "Color", name = "col1"}, - {type = "Color", name = "col2"}, - {type = "Color", name = "col3"}, - {type = "Color", name = "col4"} + {type = "Color", name = "topLeft"}, + {type = "Color", name = "bottomLeft"}, + {type = "Color", name = "topRight"}, + {type = "Color", name = "bottomRight"} } }, { diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 73170c860..afa9b525d 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -2176,8 +2176,8 @@ Function 217: DrawCircleGradient() (5 input parameters) Param[1]: centerX (type: int) Param[2]: centerY (type: int) Param[3]: radius (type: float) - Param[4]: color1 (type: Color) - Param[5]: color2 (type: Color) + Param[4]: inner (type: Color) + Param[5]: outer (type: Color) Function 218: DrawCircleV() (3 input parameters) Name: DrawCircleV Return type: void @@ -2278,8 +2278,8 @@ Function 229: DrawRectangleGradientV() (6 input parameters) Param[2]: posY (type: int) Param[3]: width (type: int) Param[4]: height (type: int) - Param[5]: color1 (type: Color) - Param[6]: color2 (type: Color) + Param[5]: top (type: Color) + Param[6]: bottom (type: Color) Function 230: DrawRectangleGradientH() (6 input parameters) Name: DrawRectangleGradientH Return type: void @@ -2288,17 +2288,17 @@ Function 230: DrawRectangleGradientH() (6 input parameters) Param[2]: posY (type: int) Param[3]: width (type: int) Param[4]: height (type: int) - Param[5]: color1 (type: Color) - Param[6]: color2 (type: Color) + Param[5]: left (type: Color) + Param[6]: right (type: Color) Function 231: DrawRectangleGradientEx() (5 input parameters) Name: DrawRectangleGradientEx Return type: void Description: Draw a gradient-filled rectangle with custom vertex colors Param[1]: rec (type: Rectangle) - Param[2]: col1 (type: Color) - Param[3]: col2 (type: Color) - Param[4]: col3 (type: Color) - Param[5]: col4 (type: Color) + Param[2]: topLeft (type: Color) + Param[3]: bottomLeft (type: Color) + Param[4]: topRight (type: Color) + Param[5]: bottomRight (type: Color) Function 232: DrawRectangleLines() (5 input parameters) Name: DrawRectangleLines Return type: void diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 474bc0473..8a6560f48 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -1353,8 +1353,8 @@ - - + + @@ -1431,23 +1431,23 @@ - - + + - - + + - - - - + + + + From c8bee7c439d3a49d63ab0e67441fffb56ea4bf2a Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Wed, 21 Aug 2024 08:11:59 -0700 Subject: [PATCH 05/28] [rmodels] Add a warning when loading an OBJ with multiple materials. (#4271) * Update raylib_api.* by CI * Add a temp warning about material assignments during OBJ loading if the file has more than one material. To be replaced when the OBJ translation code is fixed. --------- Co-authored-by: github-actions[bot] --- src/rmodels.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/rmodels.c b/src/rmodels.c index 36a7e254d..8afa3df57 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -4092,6 +4092,12 @@ static Model LoadOBJ(const char *fileName) model.materialCount = 1; TRACELOG(LOG_INFO, "MODEL: No materials provided, setting one default material for all meshes"); } + else if (model.materialCount > 1 && model.meshCount > 1) + { + // TEMP warning about multiple materials, to be removed when proper splitting code is implemented + // any obj with multiple materials will need to have it's materials assigned by the user in code to work at this time + TRACELOG(LOG_INFO, "MODEL: OBJ has multiple materials, manual material assignment will be required."); + } // Init model meshes and materials model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); From cc88e0b780a7cc265e60a2e0842e587dd9b1c293 Mon Sep 17 00:00:00 2001 From: listeria <56203103+ListeriaM@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:45:14 -0300 Subject: [PATCH 06/28] rtext: always multiply by sign in TextToFloat() (#4273) Co-authored-by: Listeria monocytogenes --- src/rtext.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rtext.c b/src/rtext.c index 755b15efd..8c1dc3c5e 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1467,8 +1467,7 @@ float TextToFloat(const char *text) int i = 0; for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); - if (text[i++] != '.') value *= sign; - else + if (text[i++] == '.') { float divisor = 10.0f; for (; ((text[i] >= '0') && (text[i] <= '9')); i++) @@ -1478,7 +1477,7 @@ float TextToFloat(const char *text) } } - return value; + return value*sign; } #if defined(SUPPORT_TEXT_MANIPULATION) From 74350fa7cf89f7e04e532a4cb89ff036f98fe59c Mon Sep 17 00:00:00 2001 From: Peter0x44 Date: Fri, 23 Aug 2024 21:21:22 +0100 Subject: [PATCH 07/28] [build] CMake: Delete BuildOptions.cmake (#4277) This file seems to not do anything useful. From what I can tell the OSX_FATLIB option sets CMAKE_OSX_ARCHITECTURES to "x86_64;i386". This doesn't account for the arm that apple now has, as well as 32 bit support being completely removed, and I think it's entirely reasonable to expect users to pass the necessary architectures they want themselves. It's possible this could break some users who rely on this, but I sincerely doubt anyone does. The solution is trivial either way (put -DCMAKE_OSX_ARCHITECTURES="i386;x86_64" on the command line yourself) The second part of BuildOptions.cmake claims to set PLATFORM to "Web" if the emscripten toolchain file is used (if (EMSCRIPTEN)), but it does not work correctly anyway. Currently, glfw searches for wayland and x11 libraries and fails likeso: CMake Error at /usr/share/cmake/Modules/FindPkgConfig.cmake:645 (message): The following required packages were not found: - wayland-client>=0.2.7 - wayland-cursor>=0.2.7 - wayland-egl>=0.2.7 - xkbcommon>=0.5.0 Call Stack (most recent call first): /usr/share/cmake/Modules/FindPkgConfig.cmake:873 (_pkg_check_modules_internal) src/external/glfw/src/CMakeLists.txt:163 (pkg_check_modules) Considering this code doesn't work as described, it's okay to delete it. I think a better check should be implemented, but that is for a different PR. --- CMakeLists.txt | 3 --- CMakeOptions.txt | 1 - cmake/BuildOptions.cmake | 18 ------------------ 3 files changed, 22 deletions(-) delete mode 100644 cmake/BuildOptions.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index a5e3408c8..678d8e372 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,9 +36,6 @@ include(CompilerFlags) # Registers build options that are exposed to cmake include(CMakeOptions.txt) -# Enforces a few environment and compiler configurations -include(BuildOptions) - if (UNIX AND NOT APPLE) if (NOT GLFW_BUILD_WAYLAND AND NOT GLFW_BUILD_X11) MESSAGE(FATAL_ERROR "Cannot disable both Wayland and X11") diff --git a/CMakeOptions.txt b/CMakeOptions.txt index b063f02a1..ce9141e22 100644 --- a/CMakeOptions.txt +++ b/CMakeOptions.txt @@ -16,7 +16,6 @@ option(ENABLE_MSAN "Enable MemorySanitizer (MSan) for debugging (not recommended # Shared library is always PIC. Static library should be PIC too if linked into a shared library option(WITH_PIC "Compile static library as position-independent code" OFF) option(BUILD_SHARED_LIBS "Build raylib as a shared library" OFF) -option(MACOS_FATLIB "Build fat library for both i386 and x86_64 on macOS" OFF) cmake_dependent_option(USE_AUDIO "Build raylib with audio module" ON CUSTOMIZE_BUILD ON) enum_option(USE_EXTERNAL_GLFW "OFF;IF_POSSIBLE;ON" "Link raylib against system GLFW instead of embedded one") diff --git a/cmake/BuildOptions.cmake b/cmake/BuildOptions.cmake deleted file mode 100644 index 0fce64292..000000000 --- a/cmake/BuildOptions.cmake +++ /dev/null @@ -1,18 +0,0 @@ -if(${PLATFORM} MATCHES "Desktop" AND APPLE) - if(MACOS_FATLIB) - if (CMAKE_OSX_ARCHITECTURES) - message(FATAL_ERROR "User supplied -DCMAKE_OSX_ARCHITECTURES overrides -DMACOS_FATLIB=ON") - else() - set(CMAKE_OSX_ARCHITECTURES "x86_64;i386") - endif() - endif() -endif() - -# This helps support the case where emsdk toolchain file is used -# either by setting it with -DCMAKE_TOOLCHAIN_FILE=/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -# or by using "emcmake cmake -B build -S ." as described in https://emscripten.org/docs/compiling/Building-Projects.html -if(EMSCRIPTEN) - SET(PLATFORM Web CACHE STRING "Forcing PLATFORM_WEB because EMSCRIPTEN was detected") -endif() - -# vim: ft=cmake From 77172e34db5d27bb5413f9232215321b8cceac67 Mon Sep 17 00:00:00 2001 From: Bugsia <74007012+Bugsia@users.noreply.github.com> Date: Fri, 23 Aug 2024 22:22:36 +0200 Subject: [PATCH 08/28] Fixing GenImagePerlinNoise() being stretched, if Image is not rectangular (#4276) --- src/rtextures.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/rtextures.c b/src/rtextures.c index f2faa1850..867add38e 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -1112,6 +1112,7 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float { Color *pixels = (Color *)RL_MALLOC(width*height*sizeof(Color)); + float aspectRatio = (float)width / (float)height; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) @@ -1119,6 +1120,10 @@ Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float float nx = (float)(x + offsetX)*(scale/(float)width); float ny = (float)(y + offsetY)*(scale/(float)height); + // Apply aspect ratio compensation to wider side + if (width > height) nx *= aspectRatio; + else ny /= aspectRatio; + // Basic perlin noise implementation (not used) //float p = (stb_perlin_noise3(nx, ny, 0.0f, 0, 0, 0); From ecf863969c2ca0cf903acb5bef0d403f611943b4 Mon Sep 17 00:00:00 2001 From: Peter0x44 Date: Fri, 23 Aug 2024 21:29:40 +0100 Subject: [PATCH 09/28] [build] CMake: Fix warnings in projects/CMake/CMakeLists.txt (#4278) Currently, when building, the cmake example in projects/CMake gives this warning, with CMake 3.30.2 CMake Warning (dev) at /usr/share/cmake/Modules/FetchContent.cmake:1953 (message): Calling FetchContent_Populate(raylib) is deprecated, call FetchContent_MakeAvailable(raylib) instead. Policy CMP0169 can be set to OLD to allow FetchContent_Populate(raylib) to be called directly for now, but the ability to call it with declared details will be removed completely in a future version. Call Stack (most recent call first): CMakeLists.txt:20 (FetchContent_Populate) This warning is for project developers. Use -Wno-dev to suppress it. Changing FetchContent_Populate to FetchContent_MakeAvailable didn't cause any issues I could observe when building. I'm not sure why it wasn't like that to begin with. --- projects/CMake/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/CMake/CMakeLists.txt b/projects/CMake/CMakeLists.txt index 3b22964c5..a95d68a4f 100644 --- a/projects/CMake/CMakeLists.txt +++ b/projects/CMake/CMakeLists.txt @@ -17,9 +17,8 @@ if (NOT raylib_FOUND) # If there's none, fetch and build raylib FetchContent_GetProperties(raylib) if (NOT raylib_POPULATED) # Have we downloaded raylib yet? set(FETCHCONTENT_QUIET NO) - FetchContent_Populate(raylib) + FetchContent_MakeAvailable(raylib) set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples - add_subdirectory(${raylib_SOURCE_DIR} ${raylib_BINARY_DIR}) endif() endif() From 3079c69725b3e4f07f6984dc2f67262bba48b153 Mon Sep 17 00:00:00 2001 From: Menno van der Graaf Date: Fri, 23 Aug 2024 22:32:20 +0200 Subject: [PATCH 10/28] Replace deprecated Android function ALooper_pollAll with ALooper_pollOnce (#4275) --- src/platforms/rcore_android.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 68ae979ee..7a2162025 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -281,14 +281,15 @@ void android_main(struct android_app *app) // Request to end the native activity ANativeActivity_finish(app->activity); - // Android ALooper_pollAll() variables + // Android ALooper_pollOnce() variables int pollResult = 0; int pollEvents = 0; // Waiting for application events before complete finishing while (!app->destroyRequested) { - while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void **)&platform.source)) >= 0) + // Poll all events until we reach return value TIMEOUT, meaning no events left to process + while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, (void **)&platform.source)) > ALOOPER_POLL_TIMEOUT) { if (platform.source != NULL) platform.source->process(app, platform.source); } @@ -682,13 +683,13 @@ void PollInputEvents(void) CORE.Input.Keyboard.keyRepeatInFrame[i] = 0; } - // Android ALooper_pollAll() variables + // Android ALooper_pollOnce() variables int pollResult = 0; int pollEvents = 0; - // Poll Events (registered events) + // Poll Events (registered events) until we reach TIMEOUT which indicates there are no events left to poll // NOTE: Activity is paused if not enabled (platform.appEnabled) - while ((pollResult = ALooper_pollAll(platform.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&platform.source)) >= 0) + while ((pollResult = ALooper_pollOnce(platform.appEnabled? 0 : -1, NULL, &pollEvents, (void**)&platform.source)) > ALOOPER_POLL_TIMEOUT) { // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); @@ -767,15 +768,15 @@ int InitPlatform(void) TRACELOG(LOG_INFO, "PLATFORM: ANDROID: Initialized successfully"); - // Android ALooper_pollAll() variables + // Android ALooper_pollOnce() variables int pollResult = 0; int pollEvents = 0; // Wait for window to be initialized (display and context) while (!CORE.Window.ready) { - // Process events loop - while ((pollResult = ALooper_pollAll(0, NULL, &pollEvents, (void**)&platform.source)) >= 0) + // Process events until we reach TIMEOUT, which indicates no more events queued. + while ((pollResult = ALooper_pollOnce(0, NULL, &pollEvents, (void**)&platform.source)) > ALOOPER_POLL_TIMEOUT) { // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); From b0c3013b51c5dbb03e584aad04fe00d89e121946 Mon Sep 17 00:00:00 2001 From: konstruktor227 Date: Fri, 23 Aug 2024 22:51:29 +0200 Subject: [PATCH 11/28] [raudio] Support 24-bit FLACs in `LoadMusicStreamFromMemory` (#4279) Force conversion to 16-bit, same as how it is done in `LoadMusicStream`. This fixes the problem where 24-bit FLACs play silence or broken sound. --- src/raudio.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/raudio.c b/src/raudio.c index 21d1adae3..79d53b565 100644 --- a/src/raudio.c +++ b/src/raudio.c @@ -1626,7 +1626,9 @@ Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, { music.ctxType = MUSIC_AUDIO_FLAC; music.ctxData = ctxFlac; - music.stream = LoadAudioStream(ctxFlac->sampleRate, ctxFlac->bitsPerSample, ctxFlac->channels); + int sampleSize = ctxFlac->bitsPerSample; + if (ctxFlac->bitsPerSample == 24) sampleSize = 16; // Forcing conversion to s16 on UpdateMusicStream() + music.stream = LoadAudioStream(ctxFlac->sampleRate, sampleSize, ctxFlac->channels); music.frameCount = (unsigned int)ctxFlac->totalPCMFrameCount; music.looping = true; // Looping enabled by default musicLoaded = true; From 7bde76ca2c649213546f412c28b7b05260338ad4 Mon Sep 17 00:00:00 2001 From: Reese Gallagher Date: Sat, 24 Aug 2024 12:42:38 -0400 Subject: [PATCH 12/28] [rmodels] More performant point cloud rendering with `DrawModelPoints()` (#4203) * Added the ability to draw a model as a point cloud * Added example to demonstrate drawing a model as a point cloud * polished the demo a bit * picture for example * adhere to conventions for example * update png to match aspect ratio * minor changes * address code convention comments * added point rendering to makefiles * added point rendering to readme and renumbered examples * comment formatting --------- Co-authored-by: Reese Gallagher Co-authored-by: Ray --- examples/Makefile | 1 + examples/Makefile.Web | 1 + examples/README.md | 50 +++--- examples/models/models_point_rendering.c | 171 +++++++++++++++++++++ examples/models/models_point_rendering.png | Bin 0 -> 130183 bytes src/raylib.h | 2 + src/rmodels.c | 24 +++ 7 files changed, 224 insertions(+), 25 deletions(-) create mode 100644 examples/models/models_point_rendering.c create mode 100644 examples/models/models_point_rendering.png diff --git a/examples/Makefile b/examples/Makefile index 93b05a0c7..904917f7c 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -582,6 +582,7 @@ MODELS = \ models/models_mesh_generation \ models/models_mesh_picking \ models/models_orthographic_projection \ + models/models_point_rendering \ models/models_rlgl_solar_system \ models/models_skybox \ models/models_waving_cubes \ diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 46a8a98ea..7057da1c0 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -447,6 +447,7 @@ MODELS = \ models/models_mesh_generation \ models/models_mesh_picking \ models/models_orthographic_projection \ + models/models_point_rendering \ models/models_rlgl_solar_system \ models/models_skybox \ models/models_waving_cubes \ diff --git a/examples/README.md b/examples/README.md index 317f43c89..d573ff23e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -147,11 +147,12 @@ Examples using raylib models functionality, including models loading/generation | 91 | [models_loading_vox](models/models_loading_vox.c) | models_loading_vox | ⭐️☆☆☆ | **4.0** | **4.0** | [Johann Nadalutti](https://github.com/procfxgen) | | 92 | [models_loading_m3d](models/models_loading_m3d.c) | models_loading_m3d | ⭐️☆☆☆ | **4.2** | **4.2** | [bzt](https://bztsrc.gitlab.io/model3d) | | 93 | [models_orthographic_projection](models/models_orthographic_projection.c) | models_orthographic_projection | ⭐️☆☆☆ | 2.0 | 3.7 | [Max Danielsson](https://github.com/autious) | -| 94 | [models_rlgl_solar_system](models/models_rlgl_solar_system.c) | models_rlgl_solar_system | ⭐️⭐️⭐️⭐️ | 2.5 | **4.0** | [Ray](https://github.com/raysan5) | -| 95 | [models_yaw_pitch_roll](models/models_yaw_pitch_roll.c) | models_yaw_pitch_roll | ⭐️⭐️☆☆ | 1.8 | **4.0** | [Berni](https://github.com/Berni8k) | -| 96 | [models_waving_cubes](models/models_waving_cubes.c) | models_waving_cubes | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [codecat](https://github.com/codecat) | -| 97 | [models_heightmap](models/models_heightmap.c) | models_heightmap | ⭐️☆☆☆ | 1.8 | 3.5 | [Ray](https://github.com/raysan5) | -| 98 | [models_skybox](models/models_skybox.c) | models_skybox | ⭐️⭐️☆☆ | 1.8 | **4.0** | [Ray](https://github.com/raysan5) | +| 94 | [models_point_rendering](models/models_point_rendering.c) | models_point_rendering | ⭐️⭐️☆☆ | 5.0 | 5.0 | [Reese Gallagher](https://github.com/satchelfrost) | +| 95 | [models_rlgl_solar_system](models/models_rlgl_solar_system.c) | models_rlgl_solar_system | ⭐️⭐️⭐️⭐️ | 2.5 | **4.0** | [Ray](https://github.com/raysan5) | +| 96 | [models_yaw_pitch_roll](models/models_yaw_pitch_roll.c) | models_yaw_pitch_roll | ⭐️⭐️☆☆ | 1.8 | **4.0** | [Berni](https://github.com/Berni8k) | +| 97 | [models_waving_cubes](models/models_waving_cubes.c) | models_waving_cubes | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [codecat](https://github.com/codecat) | +| 98 | [models_heightmap](models/models_heightmap.c) | models_heightmap | ⭐️☆☆☆ | 1.8 | 3.5 | [Ray](https://github.com/raysan5) | +| 99 | [models_skybox](models/models_skybox.c) | models_skybox | ⭐️⭐️☆☆ | 1.8 | **4.0** | [Ray](https://github.com/raysan5) | ### category: shaders @@ -159,26 +160,25 @@ Examples using raylib shaders functionality, including shaders loading, paramete | ## | example | image | difficulty
level | version
created | last version
updated | original
developer | |----|----------|--------|:-------------------:|:------------------:|:------------------:|:----------| -| 99 | [shaders_basic_lighting](shaders/shaders_basic_lighting.c) | shaders_basic_lighting | ⭐️⭐️⭐️⭐️ | 3.0 | **4.2** | [Chris Camacho](https://github.com/codifies) | -| 100 | [shaders_model_shader](shaders/shaders_model_shader.c) | shaders_model_shader | ⭐️⭐️☆☆ | 1.3 | 3.7 | [Ray](https://github.com/raysan5) | -| 101 | [shaders_shapes_textures](shaders/shaders_shapes_textures.c) | shaders_shapes_textures | ⭐️⭐️☆☆ | 1.7 | 3.7 | [Ray](https://github.com/raysan5) | -| 102 | [shaders_custom_uniform](shaders/shaders_custom_uniform.c) | shaders_custom_uniform | ⭐️⭐️☆☆ | 1.3 | **4.0** | [Ray](https://github.com/raysan5) | -| 103 | [shaders_postprocessing](shaders/shaders_postprocessing.c) | shaders_postprocessing | ⭐️⭐️⭐️☆ | 1.3 | **4.0** | [Ray](https://github.com/raysan5) | -| 104 | [shaders_palette_switch](shaders/shaders_palette_switch.c) | shaders_palette_switch | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Marco Lizza](https://github.com/MarcoLizza) | -| 105 | [shaders_raymarching](shaders/shaders_raymarching.c) | shaders_raymarching | ⭐️⭐️⭐️⭐️ | 2.0 | **4.2** | [Ray](https://github.com/raysan5) | -| 106 | [shaders_texture_drawing](shaders/shaders_texture_drawing.c) | shaders_texture_drawing | ⭐️⭐️☆☆ | 2.0 | 3.7 | [Michał Ciesielski](https://github.com/) | -| 107 | [shaders_texture_outline](shaders/shaders_texture_outline.c) | shaders_texture_outline | ⭐️⭐️⭐️☆ | **4.0** | **4.0** | [Samuel Skiff](https://github.com/GoldenThumbs) | -| 108 | [shaders_texture_waves](shaders/shaders_texture_waves.c) | shaders_texture_waves | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Anata](https://github.com/anatagawa) | -| 109 | [shaders_julia_set](shaders/shaders_julia_set.c) | shaders_julia_set | ⭐️⭐️⭐️☆ | 2.5 | **4.0** | [eggmund](https://github.com/eggmund) | -| 110 | [shaders_eratosthenes](shaders/shaders_eratosthenes.c) | shaders_eratosthenes | ⭐️⭐️⭐️☆ | 2.5 | **4.0** | [ProfJski](https://github.com/ProfJski) | -| 111 | [shaders_fog](shaders/shaders_fog.c) | shaders_fog | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | -| 112 | [shaders_simple_mask](shaders/shaders_simple_mask.c) | shaders_simple_mask | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | -| 113 | [shaders_hot_reloading](shaders/shaders_hot_reloading.c) | shaders_hot_reloading | ⭐️⭐️⭐️☆ | 3.0 | 3.5 | [Ray](https://github.com/raysan5) | -| 114 | [shaders_mesh_instancing](shaders/shaders_mesh_instancing.c) | shaders_mesh_instancing | ⭐️⭐️⭐️⭐️ | 3.7 | **4.2** | [seanpringle](https://github.com/seanpringle) | -| 115 | [shaders_multi_sample2d](shaders/shaders_multi_sample2d.c) | shaders_multi_sample2d | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Ray](https://github.com/raysan5) | -| 116 | [shaders_spotlight](shaders/shaders_spotlight.c) | shaders_spotlight | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | -| 117 | [shaders_deferred_render](shaders/shaders_deferred_render.c) | shaders_deferred_render | ⭐️⭐️⭐️⭐️ | 4.5 | 4.5 | [Justin Andreas Lacoste](https://github.com/27justin) | -| 118 | [shaders_vertex_displacement](shaders/shaders_vertex_displacement.c) | shaders_deferred_render | ⭐️☆☆☆ | 1.5 | 1.5 | [Alex ZH](https://github.com/ZzzhHe) | +| 100 | [shaders_basic_lighting](shaders/shaders_basic_lighting.c) | shaders_basic_lighting | ⭐️⭐️⭐️⭐️ | 3.0 | **4.2** | [Chris Camacho](https://github.com/codifies) | +| 101 | [shaders_model_shader](shaders/shaders_model_shader.c) | shaders_model_shader | ⭐️⭐️☆☆ | 1.3 | 3.7 | [Ray](https://github.com/raysan5) | +| 102 | [shaders_shapes_textures](shaders/shaders_shapes_textures.c) | shaders_shapes_textures | ⭐️⭐️☆☆ | 1.7 | 3.7 | [Ray](https://github.com/raysan5) | +| 103 | [shaders_custom_uniform](shaders/shaders_custom_uniform.c) | shaders_custom_uniform | ⭐️⭐️☆☆ | 1.3 | **4.0** | [Ray](https://github.com/raysan5) | +| 104 | [shaders_postprocessing](shaders/shaders_postprocessing.c) | shaders_postprocessing | ⭐️⭐️⭐️☆ | 1.3 | **4.0** | [Ray](https://github.com/raysan5) | +| 105 | [shaders_palette_switch](shaders/shaders_palette_switch.c) | shaders_palette_switch | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Marco Lizza](https://github.com/MarcoLizza) | +| 106 | [shaders_raymarching](shaders/shaders_raymarching.c) | shaders_raymarching | ⭐️⭐️⭐️⭐️ | 2.0 | **4.2** | [Ray](https://github.com/raysan5) | +| 107 | [shaders_texture_drawing](shaders/shaders_texture_drawing.c) | shaders_texture_drawing | ⭐️⭐️☆☆ | 2.0 | 3.7 | [Michał Ciesielski](https://github.com/) | +| 108 | [shaders_texture_outline](shaders/shaders_texture_outline.c) | shaders_texture_outline | ⭐️⭐️⭐️☆ | **4.0** | **4.0** | [Samuel Skiff](https://github.com/GoldenThumbs) | +| 109 | [shaders_texture_waves](shaders/shaders_texture_waves.c) | shaders_texture_waves | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Anata](https://github.com/anatagawa) | +| 110 | [shaders_julia_set](shaders/shaders_julia_set.c) | shaders_julia_set | ⭐️⭐️⭐️☆ | 2.5 | **4.0** | [eggmund](https://github.com/eggmund) | +| 111 | [shaders_eratosthenes](shaders/shaders_eratosthenes.c) | shaders_eratosthenes | ⭐️⭐️⭐️☆ | 2.5 | **4.0** | [ProfJski](https://github.com/ProfJski) | +| 112 | [shaders_fog](shaders/shaders_fog.c) | shaders_fog | ⭐️⭐️⭐️☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | +| 113 | [shaders_simple_mask](shaders/shaders_simple_mask.c) | shaders_simple_mask | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | +| 114 | [shaders_hot_reloading](shaders/shaders_hot_reloading.c) | shaders_hot_reloading | ⭐️⭐️⭐️☆ | 3.0 | 3.5 | [Ray](https://github.com/raysan5) | +| 115 | [shaders_mesh_instancing](shaders/shaders_mesh_instancing.c) | shaders_mesh_instancing | ⭐️⭐️⭐️⭐️ | 3.7 | **4.2** | [seanpringle](https://github.com/seanpringle) | +| 116 | [shaders_multi_sample2d](shaders/shaders_multi_sample2d.c) | shaders_multi_sample2d | ⭐️⭐️☆☆ | 3.5 | 3.5 | [Ray](https://github.com/raysan5) | +| 117 | [shaders_spotlight](shaders/shaders_spotlight.c) | shaders_spotlight | ⭐️⭐️☆☆ | 2.5 | 3.7 | [Chris Camacho](https://github.com/codifies) | +| 118 | [shaders_deferred_render](shaders/shaders_deferred_render.c) | shaders_deferred_render | ⭐️⭐️⭐️⭐️ | 4.5 | 4.5 | [Justin Andreas Lacoste](https://github.com/27justin) | ### category: audio diff --git a/examples/models/models_point_rendering.c b/examples/models/models_point_rendering.c new file mode 100644 index 000000000..128a70f57 --- /dev/null +++ b/examples/models/models_point_rendering.c @@ -0,0 +1,171 @@ +/******************************************************************************************* +* +* raylib example - point rendering +* +* 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 Reese Gallagher (@satchelfrost) +* +********************************************************************************************/ + +#include "raylib.h" +#include // Required for: rand() +#include // Required for: cos(), sin() + +#define MAX_POINTS 10000000 // 10 million +#define MIN_POINTS 1000 // 1 thousand + +static float RandFloat(); + +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + InitWindow(screenWidth, screenHeight, "raylib [models] example - point rendering"); + SetTargetFPS(60); + + Camera camera = { + .position = {3.0f, 3.0f, 3.0f}, + .target = {0.0f, 0.0f, 0.0f}, + .up = {0.0f, 1.0f, 0.0f}, + .fovy = 45.0f, + .projection = CAMERA_PERSPECTIVE, + }; + + Vector3 position = {0.0f, 0.0f, 0.0f}; + bool useDrawModelPoints = true; + bool numPointsChanged = false; + int numPoints = 1000; + Mesh mesh = GenPoints(numPoints); + Model model = LoadModelFromMesh(mesh); + //-------------------------------------------------------------------------------------- + + // Main game loop + while(!WindowShouldClose()) + { + // Update + //---------------------------------------------------------------------------------- + UpdateCamera(&camera, CAMERA_ORBITAL); + + if (IsKeyPressed(KEY_SPACE)) useDrawModelPoints = !useDrawModelPoints; + if (IsKeyPressed(KEY_UP)) + { + numPoints = (numPoints * 10 > MAX_POINTS) ? MAX_POINTS : numPoints * 10; + numPointsChanged = true; + TraceLog(LOG_INFO, "num points %d", numPoints); + } + if (IsKeyPressed(KEY_DOWN)) + { + numPoints = (numPoints / 10 < MIN_POINTS) ? MIN_POINTS : numPoints / 10; + numPointsChanged = true; + TraceLog(LOG_INFO, "num points %d", numPoints); + } + + // upload a different point cloud size + if (numPointsChanged) + { + UnloadModel(model); + mesh = GenPoints(numPoints); + model = LoadModelFromMesh(mesh); + numPointsChanged = false; + } + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + ClearBackground(BLACK); + BeginMode3D(camera); + + // The new method only uploads the points once to the GPU + if (useDrawModelPoints) + { + DrawModelPoints(model, position, 1.0f, WHITE); + } + // The old method must continually draw the "points" (lines) + else + { + for (int i = 0; i < numPoints; i++) + { + Vector3 pos = { + .x = mesh.vertices[i * 3 + 0], + .y = mesh.vertices[i * 3 + 1], + .z = mesh.vertices[i * 3 + 2], + }; + Color color = { + .r = mesh.colors[i * 4 + 0], + .g = mesh.colors[i * 4 + 1], + .b = mesh.colors[i * 4 + 2], + .a = mesh.colors[i * 4 + 3], + }; + DrawPoint3D(pos, color); + } + } + + // Draw a unit sphere for reference + DrawSphereWires(position, 1.0f, 10, 10, YELLOW); + EndMode3D(); + + // Text formatting + Color color = WHITE; + int fps = GetFPS(); + if ((fps < 30) && (fps >= 15)) color = ORANGE; + else if (fps < 15) color = RED; + DrawText(TextFormat("%2i FPS", fps), 20, 20, 40, color); + DrawText(TextFormat("Point Count: %d", numPoints), 20, screenHeight - 50, 40, WHITE); + DrawText("Up - increase points", 20, 70, 20, WHITE); + DrawText("Down - decrease points", 20, 100, 20, WHITE); + DrawText("Space - drawing function", 20, 130, 20, WHITE); + if (useDrawModelPoints) DrawText("DrawModelPoints()", 20, 160, 20, GREEN); + else DrawText("DrawPoint3D()", 20, 160, 20, RED); + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadModel(model); + CloseWindow(); + //-------------------------------------------------------------------------------------- + return 0; +} + +// Generate a spherical point cloud +Mesh GenPoints(int numPoints) +{ + Mesh mesh = { + .triangleCount = 1, + .vertexCount = numPoints, + .vertices = (float *)MemAlloc(numPoints * 3 * sizeof(float)), + .colors = (unsigned char*)MemAlloc(numPoints * 4 * sizeof(unsigned char)), + }; + + // https://en.wikipedia.org/wiki/Spherical_coordinate_system + for (int i = 0; i < numPoints; i++) + { + float theta = PI * rand() / RAND_MAX; + float phi = 2.0f * PI * rand() / RAND_MAX; + float r = 10.0f * rand() / RAND_MAX; + mesh.vertices[i * 3 + 0] = r * sin(theta) * cos(phi); + mesh.vertices[i * 3 + 1] = r * sin(theta) * sin(phi); + mesh.vertices[i * 3 + 2] = r * cos(theta); + Color 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(&mesh, false); + return mesh; +} diff --git a/examples/models/models_point_rendering.png b/examples/models/models_point_rendering.png new file mode 100644 index 0000000000000000000000000000000000000000..a1fc718e4dd508ba6768abb6f7a0c02a1fabaf9f GIT binary patch literal 130183 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 UrvOg;fdBvi07*qoM6N<$g6mpWr2qf` literal 0 HcmV?d00001 diff --git a/src/raylib.h b/src/raylib.h index 0759c259f..f51a444e1 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1545,6 +1545,8 @@ RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters +RLAPI void DrawModelPoints(Model model, Vector3 position, float scale, Color tint); // Draw a model as points +RLAPI void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model as points with extended parameters RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source diff --git a/src/rmodels.c b/src/rmodels.c index 8afa3df57..966a8665c 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -3631,6 +3631,30 @@ void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rlDisableWireMode(); } +// Draw a model points +void DrawModelPoints(Model model, Vector3 position, float scale, Color tint) +{ + rlEnablePointMode(); + rlDisableBackfaceCulling(); + + DrawModel(model, position, scale, tint); + + rlEnableBackfaceCulling(); + rlDisableWireMode(); +} + +// Draw a model points +void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint) +{ + rlEnablePointMode(); + rlDisableBackfaceCulling(); + + DrawModelEx(model, position, rotationAxis, rotationAngle, scale, tint); + + rlEnableBackfaceCulling(); + rlDisableWireMode(); +} + // Draw a billboard void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint) { From dfd8055270691429076283f407d4176929f56e80 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 24 Aug 2024 16:42:52 +0000 Subject: [PATCH 13/28] Update raylib_api.* by CI --- parser/output/raylib_api.json | 54 ++++++++ parser/output/raylib_api.lua | 24 ++++ parser/output/raylib_api.txt | 238 ++++++++++++++++++---------------- parser/output/raylib_api.xml | 16 ++- 4 files changed, 221 insertions(+), 111 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index c4ce91e87..dfe37e2fe 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -10366,6 +10366,60 @@ } ] }, + { + "name": "DrawModelPoints", + "description": "Draw a model as points", + "returnType": "void", + "params": [ + { + "type": "Model", + "name": "model" + }, + { + "type": "Vector3", + "name": "position" + }, + { + "type": "float", + "name": "scale" + }, + { + "type": "Color", + "name": "tint" + } + ] + }, + { + "name": "DrawModelPointsEx", + "description": "Draw a model as points with extended parameters", + "returnType": "void", + "params": [ + { + "type": "Model", + "name": "model" + }, + { + "type": "Vector3", + "name": "position" + }, + { + "type": "Vector3", + "name": "rotationAxis" + }, + { + "type": "float", + "name": "rotationAngle" + }, + { + "type": "Vector3", + "name": "scale" + }, + { + "type": "Color", + "name": "tint" + } + ] + }, { "name": "DrawBoundingBox", "description": "Draw bounding box (wires)", diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 3a9e2f04f..933b2c704 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -7213,6 +7213,30 @@ return { {type = "Color", name = "tint"} } }, + { + name = "DrawModelPoints", + description = "Draw a model as points", + returnType = "void", + params = { + {type = "Model", name = "model"}, + {type = "Vector3", name = "position"}, + {type = "float", name = "scale"}, + {type = "Color", name = "tint"} + } + }, + { + name = "DrawModelPointsEx", + description = "Draw a model as points with extended parameters", + returnType = "void", + params = { + {type = "Model", name = "model"}, + {type = "Vector3", name = "position"}, + {type = "Vector3", name = "rotationAxis"}, + {type = "float", name = "rotationAngle"}, + {type = "Vector3", name = "scale"}, + {type = "Color", name = "tint"} + } + }, { name = "DrawBoundingBox", description = "Draw bounding box (wires)", diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index afa9b525d..94562d66a 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -984,7 +984,7 @@ Callback 006: AudioCallback() (2 input parameters) Param[1]: bufferData (type: void *) Param[2]: frames (type: unsigned int) -Functions found: 573 +Functions found: 575 Function 001: InitWindow() (3 input parameters) Name: InitWindow @@ -3955,13 +3955,31 @@ Function 464: DrawModelWiresEx() (6 input parameters) Param[4]: rotationAngle (type: float) Param[5]: scale (type: Vector3) Param[6]: tint (type: Color) -Function 465: DrawBoundingBox() (2 input parameters) +Function 465: DrawModelPoints() (4 input parameters) + Name: DrawModelPoints + Return type: void + Description: Draw a model as points + Param[1]: model (type: Model) + Param[2]: position (type: Vector3) + Param[3]: scale (type: float) + Param[4]: tint (type: Color) +Function 466: DrawModelPointsEx() (6 input parameters) + Name: DrawModelPointsEx + Return type: void + Description: Draw a model as points with extended parameters + Param[1]: model (type: Model) + Param[2]: position (type: Vector3) + Param[3]: rotationAxis (type: Vector3) + Param[4]: rotationAngle (type: float) + Param[5]: scale (type: Vector3) + Param[6]: tint (type: Color) +Function 467: DrawBoundingBox() (2 input parameters) Name: DrawBoundingBox Return type: void Description: Draw bounding box (wires) Param[1]: box (type: BoundingBox) Param[2]: color (type: Color) -Function 466: DrawBillboard() (5 input parameters) +Function 468: DrawBillboard() (5 input parameters) Name: DrawBillboard Return type: void Description: Draw a billboard texture @@ -3970,7 +3988,7 @@ Function 466: DrawBillboard() (5 input parameters) Param[3]: position (type: Vector3) Param[4]: scale (type: float) Param[5]: tint (type: Color) -Function 467: DrawBillboardRec() (6 input parameters) +Function 469: DrawBillboardRec() (6 input parameters) Name: DrawBillboardRec Return type: void Description: Draw a billboard texture defined by source @@ -3980,7 +3998,7 @@ Function 467: DrawBillboardRec() (6 input parameters) Param[4]: position (type: Vector3) Param[5]: size (type: Vector2) Param[6]: tint (type: Color) -Function 468: DrawBillboardPro() (9 input parameters) +Function 470: DrawBillboardPro() (9 input parameters) Name: DrawBillboardPro Return type: void Description: Draw a billboard texture defined by source and rotation @@ -3993,13 +4011,13 @@ Function 468: DrawBillboardPro() (9 input parameters) Param[7]: origin (type: Vector2) Param[8]: rotation (type: float) Param[9]: tint (type: Color) -Function 469: UploadMesh() (2 input parameters) +Function 471: UploadMesh() (2 input parameters) Name: UploadMesh Return type: void Description: Upload mesh vertex data in GPU and provide VAO/VBO ids Param[1]: mesh (type: Mesh *) Param[2]: dynamic (type: bool) -Function 470: UpdateMeshBuffer() (5 input parameters) +Function 472: UpdateMeshBuffer() (5 input parameters) Name: UpdateMeshBuffer Return type: void Description: Update mesh vertex data in GPU for a specific buffer index @@ -4008,19 +4026,19 @@ Function 470: UpdateMeshBuffer() (5 input parameters) Param[3]: data (type: const void *) Param[4]: dataSize (type: int) Param[5]: offset (type: int) -Function 471: UnloadMesh() (1 input parameters) +Function 473: UnloadMesh() (1 input parameters) Name: UnloadMesh Return type: void Description: Unload mesh data from CPU and GPU Param[1]: mesh (type: Mesh) -Function 472: DrawMesh() (3 input parameters) +Function 474: DrawMesh() (3 input parameters) Name: DrawMesh Return type: void Description: Draw a 3d mesh with material and transform Param[1]: mesh (type: Mesh) Param[2]: material (type: Material) Param[3]: transform (type: Matrix) -Function 473: DrawMeshInstanced() (4 input parameters) +Function 475: DrawMeshInstanced() (4 input parameters) Name: DrawMeshInstanced Return type: void Description: Draw multiple mesh instances with material and different transforms @@ -4028,35 +4046,35 @@ Function 473: DrawMeshInstanced() (4 input parameters) Param[2]: material (type: Material) Param[3]: transforms (type: const Matrix *) Param[4]: instances (type: int) -Function 474: GetMeshBoundingBox() (1 input parameters) +Function 476: GetMeshBoundingBox() (1 input parameters) Name: GetMeshBoundingBox Return type: BoundingBox Description: Compute mesh bounding box limits Param[1]: mesh (type: Mesh) -Function 475: GenMeshTangents() (1 input parameters) +Function 477: GenMeshTangents() (1 input parameters) Name: GenMeshTangents Return type: void Description: Compute mesh tangents Param[1]: mesh (type: Mesh *) -Function 476: ExportMesh() (2 input parameters) +Function 478: ExportMesh() (2 input parameters) Name: ExportMesh Return type: bool Description: Export mesh data to file, returns true on success Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 477: ExportMeshAsCode() (2 input parameters) +Function 479: ExportMeshAsCode() (2 input parameters) Name: ExportMeshAsCode Return type: bool Description: Export mesh as code file (.h) defining multiple arrays of vertex attributes Param[1]: mesh (type: Mesh) Param[2]: fileName (type: const char *) -Function 478: GenMeshPoly() (2 input parameters) +Function 480: GenMeshPoly() (2 input parameters) Name: GenMeshPoly Return type: Mesh Description: Generate polygonal mesh Param[1]: sides (type: int) Param[2]: radius (type: float) -Function 479: GenMeshPlane() (4 input parameters) +Function 481: GenMeshPlane() (4 input parameters) Name: GenMeshPlane Return type: Mesh Description: Generate plane mesh (with subdivisions) @@ -4064,42 +4082,42 @@ Function 479: GenMeshPlane() (4 input parameters) Param[2]: length (type: float) Param[3]: resX (type: int) Param[4]: resZ (type: int) -Function 480: GenMeshCube() (3 input parameters) +Function 482: GenMeshCube() (3 input parameters) Name: GenMeshCube Return type: Mesh Description: Generate cuboid mesh Param[1]: width (type: float) Param[2]: height (type: float) Param[3]: length (type: float) -Function 481: GenMeshSphere() (3 input parameters) +Function 483: GenMeshSphere() (3 input parameters) Name: GenMeshSphere Return type: Mesh Description: Generate sphere mesh (standard sphere) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 482: GenMeshHemiSphere() (3 input parameters) +Function 484: GenMeshHemiSphere() (3 input parameters) Name: GenMeshHemiSphere Return type: Mesh Description: Generate half-sphere mesh (no bottom cap) Param[1]: radius (type: float) Param[2]: rings (type: int) Param[3]: slices (type: int) -Function 483: GenMeshCylinder() (3 input parameters) +Function 485: GenMeshCylinder() (3 input parameters) Name: GenMeshCylinder Return type: Mesh Description: Generate cylinder mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 484: GenMeshCone() (3 input parameters) +Function 486: GenMeshCone() (3 input parameters) Name: GenMeshCone Return type: Mesh Description: Generate cone/pyramid mesh Param[1]: radius (type: float) Param[2]: height (type: float) Param[3]: slices (type: int) -Function 485: GenMeshTorus() (4 input parameters) +Function 487: GenMeshTorus() (4 input parameters) Name: GenMeshTorus Return type: Mesh Description: Generate torus mesh @@ -4107,7 +4125,7 @@ Function 485: GenMeshTorus() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 486: GenMeshKnot() (4 input parameters) +Function 488: GenMeshKnot() (4 input parameters) Name: GenMeshKnot Return type: Mesh Description: Generate trefoil knot mesh @@ -4115,84 +4133,84 @@ Function 486: GenMeshKnot() (4 input parameters) Param[2]: size (type: float) Param[3]: radSeg (type: int) Param[4]: sides (type: int) -Function 487: GenMeshHeightmap() (2 input parameters) +Function 489: GenMeshHeightmap() (2 input parameters) Name: GenMeshHeightmap Return type: Mesh Description: Generate heightmap mesh from image data Param[1]: heightmap (type: Image) Param[2]: size (type: Vector3) -Function 488: GenMeshCubicmap() (2 input parameters) +Function 490: GenMeshCubicmap() (2 input parameters) Name: GenMeshCubicmap Return type: Mesh Description: Generate cubes-based map mesh from image data Param[1]: cubicmap (type: Image) Param[2]: cubeSize (type: Vector3) -Function 489: LoadMaterials() (2 input parameters) +Function 491: LoadMaterials() (2 input parameters) Name: LoadMaterials Return type: Material * Description: Load materials from model file Param[1]: fileName (type: const char *) Param[2]: materialCount (type: int *) -Function 490: LoadMaterialDefault() (0 input parameters) +Function 492: LoadMaterialDefault() (0 input parameters) Name: LoadMaterialDefault Return type: Material Description: Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) No input parameters -Function 491: IsMaterialReady() (1 input parameters) +Function 493: IsMaterialReady() (1 input parameters) Name: IsMaterialReady Return type: bool Description: Check if a material is ready Param[1]: material (type: Material) -Function 492: UnloadMaterial() (1 input parameters) +Function 494: UnloadMaterial() (1 input parameters) Name: UnloadMaterial Return type: void Description: Unload material from GPU memory (VRAM) Param[1]: material (type: Material) -Function 493: SetMaterialTexture() (3 input parameters) +Function 495: SetMaterialTexture() (3 input parameters) Name: SetMaterialTexture Return type: void Description: Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) Param[1]: material (type: Material *) Param[2]: mapType (type: int) Param[3]: texture (type: Texture2D) -Function 494: SetModelMeshMaterial() (3 input parameters) +Function 496: SetModelMeshMaterial() (3 input parameters) Name: SetModelMeshMaterial Return type: void Description: Set material for a mesh Param[1]: model (type: Model *) Param[2]: meshId (type: int) Param[3]: materialId (type: int) -Function 495: LoadModelAnimations() (2 input parameters) +Function 497: LoadModelAnimations() (2 input parameters) Name: LoadModelAnimations Return type: ModelAnimation * Description: Load model animations from file Param[1]: fileName (type: const char *) Param[2]: animCount (type: int *) -Function 496: UpdateModelAnimation() (3 input parameters) +Function 498: UpdateModelAnimation() (3 input parameters) Name: UpdateModelAnimation Return type: void Description: Update model animation pose Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) Param[3]: frame (type: int) -Function 497: UnloadModelAnimation() (1 input parameters) +Function 499: UnloadModelAnimation() (1 input parameters) Name: UnloadModelAnimation Return type: void Description: Unload animation data Param[1]: anim (type: ModelAnimation) -Function 498: UnloadModelAnimations() (2 input parameters) +Function 500: UnloadModelAnimations() (2 input parameters) Name: UnloadModelAnimations Return type: void Description: Unload animation array data Param[1]: animations (type: ModelAnimation *) Param[2]: animCount (type: int) -Function 499: IsModelAnimationValid() (2 input parameters) +Function 501: IsModelAnimationValid() (2 input parameters) Name: IsModelAnimationValid Return type: bool Description: Check model animation skeleton match Param[1]: model (type: Model) Param[2]: anim (type: ModelAnimation) -Function 500: CheckCollisionSpheres() (4 input parameters) +Function 502: CheckCollisionSpheres() (4 input parameters) Name: CheckCollisionSpheres Return type: bool Description: Check collision between two spheres @@ -4200,40 +4218,40 @@ Function 500: CheckCollisionSpheres() (4 input parameters) Param[2]: radius1 (type: float) Param[3]: center2 (type: Vector3) Param[4]: radius2 (type: float) -Function 501: CheckCollisionBoxes() (2 input parameters) +Function 503: CheckCollisionBoxes() (2 input parameters) Name: CheckCollisionBoxes Return type: bool Description: Check collision between two bounding boxes Param[1]: box1 (type: BoundingBox) Param[2]: box2 (type: BoundingBox) -Function 502: CheckCollisionBoxSphere() (3 input parameters) +Function 504: CheckCollisionBoxSphere() (3 input parameters) Name: CheckCollisionBoxSphere Return type: bool Description: Check collision between box and sphere Param[1]: box (type: BoundingBox) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 503: GetRayCollisionSphere() (3 input parameters) +Function 505: GetRayCollisionSphere() (3 input parameters) Name: GetRayCollisionSphere Return type: RayCollision Description: Get collision info between ray and sphere Param[1]: ray (type: Ray) Param[2]: center (type: Vector3) Param[3]: radius (type: float) -Function 504: GetRayCollisionBox() (2 input parameters) +Function 506: GetRayCollisionBox() (2 input parameters) Name: GetRayCollisionBox Return type: RayCollision Description: Get collision info between ray and box Param[1]: ray (type: Ray) Param[2]: box (type: BoundingBox) -Function 505: GetRayCollisionMesh() (3 input parameters) +Function 507: GetRayCollisionMesh() (3 input parameters) Name: GetRayCollisionMesh Return type: RayCollision Description: Get collision info between ray and mesh Param[1]: ray (type: Ray) Param[2]: mesh (type: Mesh) Param[3]: transform (type: Matrix) -Function 506: GetRayCollisionTriangle() (4 input parameters) +Function 508: GetRayCollisionTriangle() (4 input parameters) Name: GetRayCollisionTriangle Return type: RayCollision Description: Get collision info between ray and triangle @@ -4241,7 +4259,7 @@ Function 506: GetRayCollisionTriangle() (4 input parameters) Param[2]: p1 (type: Vector3) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) -Function 507: GetRayCollisionQuad() (5 input parameters) +Function 509: GetRayCollisionQuad() (5 input parameters) Name: GetRayCollisionQuad Return type: RayCollision Description: Get collision info between ray and quad @@ -4250,158 +4268,158 @@ Function 507: GetRayCollisionQuad() (5 input parameters) Param[3]: p2 (type: Vector3) Param[4]: p3 (type: Vector3) Param[5]: p4 (type: Vector3) -Function 508: InitAudioDevice() (0 input parameters) +Function 510: InitAudioDevice() (0 input parameters) Name: InitAudioDevice Return type: void Description: Initialize audio device and context No input parameters -Function 509: CloseAudioDevice() (0 input parameters) +Function 511: CloseAudioDevice() (0 input parameters) Name: CloseAudioDevice Return type: void Description: Close the audio device and context No input parameters -Function 510: IsAudioDeviceReady() (0 input parameters) +Function 512: IsAudioDeviceReady() (0 input parameters) Name: IsAudioDeviceReady Return type: bool Description: Check if audio device has been initialized successfully No input parameters -Function 511: SetMasterVolume() (1 input parameters) +Function 513: SetMasterVolume() (1 input parameters) Name: SetMasterVolume Return type: void Description: Set master volume (listener) Param[1]: volume (type: float) -Function 512: GetMasterVolume() (0 input parameters) +Function 514: GetMasterVolume() (0 input parameters) Name: GetMasterVolume Return type: float Description: Get master volume (listener) No input parameters -Function 513: LoadWave() (1 input parameters) +Function 515: LoadWave() (1 input parameters) Name: LoadWave Return type: Wave Description: Load wave data from file Param[1]: fileName (type: const char *) -Function 514: LoadWaveFromMemory() (3 input parameters) +Function 516: LoadWaveFromMemory() (3 input parameters) Name: LoadWaveFromMemory Return type: Wave Description: Load wave from memory buffer, fileType refers to extension: i.e. '.wav' Param[1]: fileType (type: const char *) Param[2]: fileData (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 515: IsWaveReady() (1 input parameters) +Function 517: IsWaveReady() (1 input parameters) Name: IsWaveReady Return type: bool Description: Checks if wave data is ready Param[1]: wave (type: Wave) -Function 516: LoadSound() (1 input parameters) +Function 518: LoadSound() (1 input parameters) Name: LoadSound Return type: Sound Description: Load sound from file Param[1]: fileName (type: const char *) -Function 517: LoadSoundFromWave() (1 input parameters) +Function 519: LoadSoundFromWave() (1 input parameters) Name: LoadSoundFromWave Return type: Sound Description: Load sound from wave data Param[1]: wave (type: Wave) -Function 518: LoadSoundAlias() (1 input parameters) +Function 520: LoadSoundAlias() (1 input parameters) Name: LoadSoundAlias Return type: Sound Description: Create a new sound that shares the same sample data as the source sound, does not own the sound data Param[1]: source (type: Sound) -Function 519: IsSoundReady() (1 input parameters) +Function 521: IsSoundReady() (1 input parameters) Name: IsSoundReady Return type: bool Description: Checks if a sound is ready Param[1]: sound (type: Sound) -Function 520: UpdateSound() (3 input parameters) +Function 522: UpdateSound() (3 input parameters) Name: UpdateSound Return type: void Description: Update sound buffer with new data Param[1]: sound (type: Sound) Param[2]: data (type: const void *) Param[3]: sampleCount (type: int) -Function 521: UnloadWave() (1 input parameters) +Function 523: UnloadWave() (1 input parameters) Name: UnloadWave Return type: void Description: Unload wave data Param[1]: wave (type: Wave) -Function 522: UnloadSound() (1 input parameters) +Function 524: UnloadSound() (1 input parameters) Name: UnloadSound Return type: void Description: Unload sound Param[1]: sound (type: Sound) -Function 523: UnloadSoundAlias() (1 input parameters) +Function 525: UnloadSoundAlias() (1 input parameters) Name: UnloadSoundAlias Return type: void Description: Unload a sound alias (does not deallocate sample data) Param[1]: alias (type: Sound) -Function 524: ExportWave() (2 input parameters) +Function 526: ExportWave() (2 input parameters) Name: ExportWave Return type: bool Description: Export wave data to file, returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 525: ExportWaveAsCode() (2 input parameters) +Function 527: ExportWaveAsCode() (2 input parameters) Name: ExportWaveAsCode Return type: bool Description: Export wave sample data to code (.h), returns true on success Param[1]: wave (type: Wave) Param[2]: fileName (type: const char *) -Function 526: PlaySound() (1 input parameters) +Function 528: PlaySound() (1 input parameters) Name: PlaySound Return type: void Description: Play a sound Param[1]: sound (type: Sound) -Function 527: StopSound() (1 input parameters) +Function 529: StopSound() (1 input parameters) Name: StopSound Return type: void Description: Stop playing a sound Param[1]: sound (type: Sound) -Function 528: PauseSound() (1 input parameters) +Function 530: PauseSound() (1 input parameters) Name: PauseSound Return type: void Description: Pause a sound Param[1]: sound (type: Sound) -Function 529: ResumeSound() (1 input parameters) +Function 531: ResumeSound() (1 input parameters) Name: ResumeSound Return type: void Description: Resume a paused sound Param[1]: sound (type: Sound) -Function 530: IsSoundPlaying() (1 input parameters) +Function 532: IsSoundPlaying() (1 input parameters) Name: IsSoundPlaying Return type: bool Description: Check if a sound is currently playing Param[1]: sound (type: Sound) -Function 531: SetSoundVolume() (2 input parameters) +Function 533: SetSoundVolume() (2 input parameters) Name: SetSoundVolume Return type: void Description: Set volume for a sound (1.0 is max level) Param[1]: sound (type: Sound) Param[2]: volume (type: float) -Function 532: SetSoundPitch() (2 input parameters) +Function 534: SetSoundPitch() (2 input parameters) Name: SetSoundPitch Return type: void Description: Set pitch for a sound (1.0 is base level) Param[1]: sound (type: Sound) Param[2]: pitch (type: float) -Function 533: SetSoundPan() (2 input parameters) +Function 535: SetSoundPan() (2 input parameters) Name: SetSoundPan Return type: void Description: Set pan for a sound (0.5 is center) Param[1]: sound (type: Sound) Param[2]: pan (type: float) -Function 534: WaveCopy() (1 input parameters) +Function 536: WaveCopy() (1 input parameters) Name: WaveCopy Return type: Wave Description: Copy a wave to a new wave Param[1]: wave (type: Wave) -Function 535: WaveCrop() (3 input parameters) +Function 537: WaveCrop() (3 input parameters) Name: WaveCrop Return type: void Description: Crop a wave to defined frames range Param[1]: wave (type: Wave *) Param[2]: initFrame (type: int) Param[3]: finalFrame (type: int) -Function 536: WaveFormat() (4 input parameters) +Function 538: WaveFormat() (4 input parameters) Name: WaveFormat Return type: void Description: Convert wave data to desired format @@ -4409,203 +4427,203 @@ Function 536: WaveFormat() (4 input parameters) Param[2]: sampleRate (type: int) Param[3]: sampleSize (type: int) Param[4]: channels (type: int) -Function 537: LoadWaveSamples() (1 input parameters) +Function 539: LoadWaveSamples() (1 input parameters) Name: LoadWaveSamples Return type: float * Description: Load samples data from wave as a 32bit float data array Param[1]: wave (type: Wave) -Function 538: UnloadWaveSamples() (1 input parameters) +Function 540: UnloadWaveSamples() (1 input parameters) Name: UnloadWaveSamples Return type: void Description: Unload samples data loaded with LoadWaveSamples() Param[1]: samples (type: float *) -Function 539: LoadMusicStream() (1 input parameters) +Function 541: LoadMusicStream() (1 input parameters) Name: LoadMusicStream Return type: Music Description: Load music stream from file Param[1]: fileName (type: const char *) -Function 540: LoadMusicStreamFromMemory() (3 input parameters) +Function 542: LoadMusicStreamFromMemory() (3 input parameters) Name: LoadMusicStreamFromMemory Return type: Music Description: Load music stream from data Param[1]: fileType (type: const char *) Param[2]: data (type: const unsigned char *) Param[3]: dataSize (type: int) -Function 541: IsMusicReady() (1 input parameters) +Function 543: IsMusicReady() (1 input parameters) Name: IsMusicReady Return type: bool Description: Checks if a music stream is ready Param[1]: music (type: Music) -Function 542: UnloadMusicStream() (1 input parameters) +Function 544: UnloadMusicStream() (1 input parameters) Name: UnloadMusicStream Return type: void Description: Unload music stream Param[1]: music (type: Music) -Function 543: PlayMusicStream() (1 input parameters) +Function 545: PlayMusicStream() (1 input parameters) Name: PlayMusicStream Return type: void Description: Start music playing Param[1]: music (type: Music) -Function 544: IsMusicStreamPlaying() (1 input parameters) +Function 546: IsMusicStreamPlaying() (1 input parameters) Name: IsMusicStreamPlaying Return type: bool Description: Check if music is playing Param[1]: music (type: Music) -Function 545: UpdateMusicStream() (1 input parameters) +Function 547: UpdateMusicStream() (1 input parameters) Name: UpdateMusicStream Return type: void Description: Updates buffers for music streaming Param[1]: music (type: Music) -Function 546: StopMusicStream() (1 input parameters) +Function 548: StopMusicStream() (1 input parameters) Name: StopMusicStream Return type: void Description: Stop music playing Param[1]: music (type: Music) -Function 547: PauseMusicStream() (1 input parameters) +Function 549: PauseMusicStream() (1 input parameters) Name: PauseMusicStream Return type: void Description: Pause music playing Param[1]: music (type: Music) -Function 548: ResumeMusicStream() (1 input parameters) +Function 550: ResumeMusicStream() (1 input parameters) Name: ResumeMusicStream Return type: void Description: Resume playing paused music Param[1]: music (type: Music) -Function 549: SeekMusicStream() (2 input parameters) +Function 551: SeekMusicStream() (2 input parameters) Name: SeekMusicStream Return type: void Description: Seek music to a position (in seconds) Param[1]: music (type: Music) Param[2]: position (type: float) -Function 550: SetMusicVolume() (2 input parameters) +Function 552: SetMusicVolume() (2 input parameters) Name: SetMusicVolume Return type: void Description: Set volume for music (1.0 is max level) Param[1]: music (type: Music) Param[2]: volume (type: float) -Function 551: SetMusicPitch() (2 input parameters) +Function 553: SetMusicPitch() (2 input parameters) Name: SetMusicPitch Return type: void Description: Set pitch for a music (1.0 is base level) Param[1]: music (type: Music) Param[2]: pitch (type: float) -Function 552: SetMusicPan() (2 input parameters) +Function 554: SetMusicPan() (2 input parameters) Name: SetMusicPan Return type: void Description: Set pan for a music (0.5 is center) Param[1]: music (type: Music) Param[2]: pan (type: float) -Function 553: GetMusicTimeLength() (1 input parameters) +Function 555: GetMusicTimeLength() (1 input parameters) Name: GetMusicTimeLength Return type: float Description: Get music time length (in seconds) Param[1]: music (type: Music) -Function 554: GetMusicTimePlayed() (1 input parameters) +Function 556: GetMusicTimePlayed() (1 input parameters) Name: GetMusicTimePlayed Return type: float Description: Get current music time played (in seconds) Param[1]: music (type: Music) -Function 555: LoadAudioStream() (3 input parameters) +Function 557: LoadAudioStream() (3 input parameters) Name: LoadAudioStream Return type: AudioStream Description: Load audio stream (to stream raw audio pcm data) Param[1]: sampleRate (type: unsigned int) Param[2]: sampleSize (type: unsigned int) Param[3]: channels (type: unsigned int) -Function 556: IsAudioStreamReady() (1 input parameters) +Function 558: IsAudioStreamReady() (1 input parameters) Name: IsAudioStreamReady Return type: bool Description: Checks if an audio stream is ready Param[1]: stream (type: AudioStream) -Function 557: UnloadAudioStream() (1 input parameters) +Function 559: UnloadAudioStream() (1 input parameters) Name: UnloadAudioStream Return type: void Description: Unload audio stream and free memory Param[1]: stream (type: AudioStream) -Function 558: UpdateAudioStream() (3 input parameters) +Function 560: UpdateAudioStream() (3 input parameters) Name: UpdateAudioStream Return type: void Description: Update audio stream buffers with data Param[1]: stream (type: AudioStream) Param[2]: data (type: const void *) Param[3]: frameCount (type: int) -Function 559: IsAudioStreamProcessed() (1 input parameters) +Function 561: IsAudioStreamProcessed() (1 input parameters) Name: IsAudioStreamProcessed Return type: bool Description: Check if any audio stream buffers requires refill Param[1]: stream (type: AudioStream) -Function 560: PlayAudioStream() (1 input parameters) +Function 562: PlayAudioStream() (1 input parameters) Name: PlayAudioStream Return type: void Description: Play audio stream Param[1]: stream (type: AudioStream) -Function 561: PauseAudioStream() (1 input parameters) +Function 563: PauseAudioStream() (1 input parameters) Name: PauseAudioStream Return type: void Description: Pause audio stream Param[1]: stream (type: AudioStream) -Function 562: ResumeAudioStream() (1 input parameters) +Function 564: ResumeAudioStream() (1 input parameters) Name: ResumeAudioStream Return type: void Description: Resume audio stream Param[1]: stream (type: AudioStream) -Function 563: IsAudioStreamPlaying() (1 input parameters) +Function 565: IsAudioStreamPlaying() (1 input parameters) Name: IsAudioStreamPlaying Return type: bool Description: Check if audio stream is playing Param[1]: stream (type: AudioStream) -Function 564: StopAudioStream() (1 input parameters) +Function 566: StopAudioStream() (1 input parameters) Name: StopAudioStream Return type: void Description: Stop audio stream Param[1]: stream (type: AudioStream) -Function 565: SetAudioStreamVolume() (2 input parameters) +Function 567: SetAudioStreamVolume() (2 input parameters) Name: SetAudioStreamVolume Return type: void Description: Set volume for audio stream (1.0 is max level) Param[1]: stream (type: AudioStream) Param[2]: volume (type: float) -Function 566: SetAudioStreamPitch() (2 input parameters) +Function 568: SetAudioStreamPitch() (2 input parameters) Name: SetAudioStreamPitch Return type: void Description: Set pitch for audio stream (1.0 is base level) Param[1]: stream (type: AudioStream) Param[2]: pitch (type: float) -Function 567: SetAudioStreamPan() (2 input parameters) +Function 569: SetAudioStreamPan() (2 input parameters) Name: SetAudioStreamPan Return type: void Description: Set pan for audio stream (0.5 is centered) Param[1]: stream (type: AudioStream) Param[2]: pan (type: float) -Function 568: SetAudioStreamBufferSizeDefault() (1 input parameters) +Function 570: SetAudioStreamBufferSizeDefault() (1 input parameters) Name: SetAudioStreamBufferSizeDefault Return type: void Description: Default size for new audio streams Param[1]: size (type: int) -Function 569: SetAudioStreamCallback() (2 input parameters) +Function 571: SetAudioStreamCallback() (2 input parameters) Name: SetAudioStreamCallback Return type: void Description: Audio thread callback to request new data Param[1]: stream (type: AudioStream) Param[2]: callback (type: AudioCallback) -Function 570: AttachAudioStreamProcessor() (2 input parameters) +Function 572: AttachAudioStreamProcessor() (2 input parameters) Name: AttachAudioStreamProcessor Return type: void Description: Attach audio stream processor to stream, receives the samples as 'float' Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 571: DetachAudioStreamProcessor() (2 input parameters) +Function 573: DetachAudioStreamProcessor() (2 input parameters) Name: DetachAudioStreamProcessor Return type: void Description: Detach audio stream processor from stream Param[1]: stream (type: AudioStream) Param[2]: processor (type: AudioCallback) -Function 572: AttachAudioMixedProcessor() (1 input parameters) +Function 574: AttachAudioMixedProcessor() (1 input parameters) Name: AttachAudioMixedProcessor Return type: void Description: Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' Param[1]: processor (type: AudioCallback) -Function 573: DetachAudioMixedProcessor() (1 input parameters) +Function 575: DetachAudioMixedProcessor() (1 input parameters) Name: DetachAudioMixedProcessor Return type: void Description: Detach audio stream processor from the entire audio pipeline diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 8a6560f48..02cfd02d0 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -670,7 +670,7 @@ - + @@ -2637,6 +2637,20 @@ + + + + + + + + + + + + + + From bc0bd98763bba496836c5cfc1418949256bc6f4d Mon Sep 17 00:00:00 2001 From: Colleague Riley Date: Sat, 24 Aug 2024 12:43:46 -0400 Subject: [PATCH 14/28] (rcore_desktop_rgfw.c) fix errors when compiling with mingw (#4282) * (rcore_desktop_rgfw.c) fix errors when compiling with mingw * define WideCharToMultiByte --- src/external/RGFW.h | 4 +++- src/platforms/rcore_desktop_rgfw.c | 6 +----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/external/RGFW.h b/src/external/RGFW.h index 367a46fa6..35e782d4a 100644 --- a/src/external/RGFW.h +++ b/src/external/RGFW.h @@ -4999,7 +4999,9 @@ static const struct wl_callback_listener wl_surface_frame_listener = { #define WIN32_LEAN_AND_MEAN #define OEMRESOURCE #include - + + __declspec(dllimport) int __stdcall WideCharToMultiByte( UINT CodePage, DWORD dwFlags, const WCHAR* lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar); + #include #include #include diff --git a/src/platforms/rcore_desktop_rgfw.c b/src/platforms/rcore_desktop_rgfw.c index afea0c2e6..64ab432e6 100644 --- a/src/platforms/rcore_desktop_rgfw.c +++ b/src/platforms/rcore_desktop_rgfw.c @@ -76,9 +76,7 @@ void CloseWindow(void); #define Size NSSIZE #endif -#if defined(_MSC_VER) __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar); -#endif #include "../external/RGFW.h" @@ -538,10 +536,8 @@ void *GetWindowHandle(void) { #ifdef RGFW_WEBASM return (void*)platform.window->src.ctx; -#elif !defined(RGFW_WINDOWS) - return (void *)platform.window->src.window; #else - return platform.window->src.hwnd; + return (void*)platform.window->src.window; #endif } From 414133dbe7b063566a338d9c4ef1c9eb0651dec4 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 24 Aug 2024 18:56:06 +0200 Subject: [PATCH 15/28] Update models_point_rendering.c --- examples/models/models_point_rendering.c | 107 +++++++++++++---------- 1 file changed, 59 insertions(+), 48 deletions(-) diff --git a/examples/models/models_point_rendering.c b/examples/models/models_point_rendering.c index 128a70f57..68d498453 100644 --- a/examples/models/models_point_rendering.c +++ b/examples/models/models_point_rendering.c @@ -14,13 +14,15 @@ ********************************************************************************************/ #include "raylib.h" -#include // Required for: rand() -#include // Required for: cos(), sin() -#define MAX_POINTS 10000000 // 10 million -#define MIN_POINTS 1000 // 1 thousand +#include // Required for: rand() +#include // Required for: cos(), sin() -static float RandFloat(); +#define MAX_POINTS 10000000 // 10 million +#define MIN_POINTS 1000 // 1 thousand + +// Generate mesh using points +Mesh GenMeshPoints(int numPoints); //------------------------------------------------------------------------------------ // Program main entry point @@ -31,23 +33,26 @@ int main() //-------------------------------------------------------------------------------------- const int screenWidth = 800; const int screenHeight = 450; + InitWindow(screenWidth, screenHeight, "raylib [models] example - point rendering"); - SetTargetFPS(60); Camera camera = { - .position = {3.0f, 3.0f, 3.0f}, - .target = {0.0f, 0.0f, 0.0f}, - .up = {0.0f, 1.0f, 0.0f}, + .position = { 3.0f, 3.0f, 3.0f }, + .target = { 0.0f, 0.0f, 0.0f }, + .up = { 0.0f, 1.0f, 0.0f }, .fovy = 45.0f, - .projection = CAMERA_PERSPECTIVE, + .projection = CAMERA_PERSPECTIVE }; - Vector3 position = {0.0f, 0.0f, 0.0f}; + Vector3 position = { 0.0f, 0.0f, 0.0f }; bool useDrawModelPoints = true; bool numPointsChanged = false; int numPoints = 1000; - Mesh mesh = GenPoints(numPoints); + + Mesh mesh = GenMeshPoints(numPoints); Model model = LoadModelFromMesh(mesh); + + //SetTargetFPS(60); //-------------------------------------------------------------------------------------- // Main game loop @@ -60,30 +65,30 @@ int main() if (IsKeyPressed(KEY_SPACE)) useDrawModelPoints = !useDrawModelPoints; if (IsKeyPressed(KEY_UP)) { - numPoints = (numPoints * 10 > MAX_POINTS) ? MAX_POINTS : numPoints * 10; + numPoints = (numPoints*10 > MAX_POINTS)? MAX_POINTS : numPoints*10; numPointsChanged = true; - TraceLog(LOG_INFO, "num points %d", numPoints); } if (IsKeyPressed(KEY_DOWN)) { - numPoints = (numPoints / 10 < MIN_POINTS) ? MIN_POINTS : numPoints / 10; + numPoints = (numPoints/10 < MIN_POINTS)? MIN_POINTS : numPoints/10; numPointsChanged = true; - TraceLog(LOG_INFO, "num points %d", numPoints); } - // upload a different point cloud size + // Upload a different point cloud size if (numPointsChanged) { UnloadModel(model); - mesh = GenPoints(numPoints); + mesh = GenMeshPoints(numPoints); model = LoadModelFromMesh(mesh); numPointsChanged = false; } + //---------------------------------------------------------------------------------- // Draw //---------------------------------------------------------------------------------- BeginDrawing(); ClearBackground(BLACK); + BeginMode3D(camera); // The new method only uploads the points once to the GPU @@ -91,42 +96,43 @@ int main() { DrawModelPoints(model, position, 1.0f, WHITE); } - // The old method must continually draw the "points" (lines) else { + // The old method must continually draw the "points" (lines) for (int i = 0; i < numPoints; i++) { Vector3 pos = { - .x = mesh.vertices[i * 3 + 0], - .y = mesh.vertices[i * 3 + 1], - .z = mesh.vertices[i * 3 + 2], + .x = mesh.vertices[i*3 + 0], + .y = mesh.vertices[i*3 + 1], + .z = mesh.vertices[i*3 + 2], }; Color color = { - .r = mesh.colors[i * 4 + 0], - .g = mesh.colors[i * 4 + 1], - .b = mesh.colors[i * 4 + 2], - .a = mesh.colors[i * 4 + 3], + .r = mesh.colors[i*4 + 0], + .g = mesh.colors[i*4 + 1], + .b = mesh.colors[i*4 + 2], + .a = mesh.colors[i*4 + 3], }; + DrawPoint3D(pos, color); } } // Draw a unit sphere for reference DrawSphereWires(position, 1.0f, 10, 10, YELLOW); + EndMode3D(); - // Text formatting - Color color = WHITE; - int fps = GetFPS(); - if ((fps < 30) && (fps >= 15)) color = ORANGE; - else if (fps < 15) color = RED; - DrawText(TextFormat("%2i FPS", fps), 20, 20, 40, color); + // Draw UI text DrawText(TextFormat("Point Count: %d", numPoints), 20, screenHeight - 50, 40, WHITE); DrawText("Up - increase points", 20, 70, 20, WHITE); DrawText("Down - decrease points", 20, 100, 20, WHITE); DrawText("Space - drawing function", 20, 130, 20, WHITE); - if (useDrawModelPoints) DrawText("DrawModelPoints()", 20, 160, 20, GREEN); - else DrawText("DrawPoint3D()", 20, 160, 20, RED); + + if (useDrawModelPoints) DrawText("Using: DrawModelPoints()", 20, 160, 20, GREEN); + else DrawText("Using: DrawPoint3D()", 20, 160, 20, RED); + + DrawFPS(10, 10); + EndDrawing(); //---------------------------------------------------------------------------------- } @@ -134,38 +140,43 @@ int main() // De-Initialization //-------------------------------------------------------------------------------------- UnloadModel(model); + CloseWindow(); //-------------------------------------------------------------------------------------- return 0; } // Generate a spherical point cloud -Mesh GenPoints(int numPoints) +Mesh GenMeshPoints(int numPoints) { Mesh mesh = { .triangleCount = 1, .vertexCount = numPoints, - .vertices = (float *)MemAlloc(numPoints * 3 * sizeof(float)), - .colors = (unsigned char*)MemAlloc(numPoints * 4 * sizeof(unsigned char)), + .vertices = (float *)MemAlloc(numPoints*3*sizeof(float)), + .colors = (unsigned char*)MemAlloc(numPoints*4*sizeof(unsigned char)), }; // https://en.wikipedia.org/wiki/Spherical_coordinate_system for (int i = 0; i < numPoints; i++) { - float theta = PI * rand() / RAND_MAX; - float phi = 2.0f * PI * rand() / RAND_MAX; - float r = 10.0f * rand() / RAND_MAX; - mesh.vertices[i * 3 + 0] = r * sin(theta) * cos(phi); - mesh.vertices[i * 3 + 1] = r * sin(theta) * sin(phi); - mesh.vertices[i * 3 + 2] = r * cos(theta); - Color 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; + float theta = PI*rand()/RAND_MAX; + float phi = 2.0f*PI*rand()/RAND_MAX; + float r = 10.0f*rand()/RAND_MAX; + + mesh.vertices[i*3 + 0] = r*sin(theta)*cos(phi); + mesh.vertices[i*3 + 1] = r*sin(theta)*sin(phi); + mesh.vertices[i*3 + 2] = r*cos(theta); + + Color 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(&mesh, false); + return mesh; } From 78e86b6ea5824699f9fdaf708f532e5215ea9e7f Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 24 Aug 2024 18:58:43 +0200 Subject: [PATCH 16/28] Update rlgl.h --- src/rlgl.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/rlgl.h b/src/rlgl.h index 5fb9497cd..2f8515ee2 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -665,7 +665,7 @@ RLAPI void rlDisableScissorTest(void); // Disable scissor test RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test RLAPI void rlEnableWireMode(void); // Enable wire mode RLAPI void rlEnablePointMode(void); // Enable point mode -RLAPI void rlDisableWireMode(void); // Disable wire mode ( and point ) maybe rename +RLAPI void rlDisableWireMode(void); // Disable wire (and point) mode RLAPI void rlSetLineWidth(float width); // Set the line drawing width RLAPI float rlGetLineWidth(void); // Get the line drawing width RLAPI void rlEnableSmoothLines(void); // Enable line aliasing @@ -1948,6 +1948,7 @@ void rlEnableWireMode(void) #endif } +// Enable point mode void rlEnablePointMode(void) { #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) @@ -1956,6 +1957,7 @@ void rlEnablePointMode(void) glEnable(GL_PROGRAM_POINT_SIZE); #endif } + // Disable wire mode void rlDisableWireMode(void) { @@ -4236,6 +4238,8 @@ void rlSetUniform(int locIndex, const void *value, int uniformType, int count) case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); + + // TODO: Support glUniform1uiv(), glUniform2uiv(), glUniform3uiv(), glUniform4uiv() } #endif } From 4c9282b09025677a90e5e54ffe142824560163b6 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 24 Aug 2024 18:59:24 +0200 Subject: [PATCH 17/28] ADDED: `isGpuReady` flag, allow font loading with no GPU acceleration --- src/rcore.c | 48 ++++++++++++++++++++++++++++-------------------- src/rtext.c | 32 ++++++++++++++++++-------------- 2 files changed, 46 insertions(+), 34 deletions(-) diff --git a/src/rcore.c b/src/rcore.c index e8041ea41..972d58600 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -360,10 +360,15 @@ typedef struct CoreData { //---------------------------------------------------------------------------------- RLAPI const char *raylib_version = RAYLIB_VERSION; // raylib version exported symbol, required for some bindings -CoreData CORE = { 0 }; // Global CORE state context +CoreData CORE = { 0 }; // Global CORE state context + +// Flag to note GPU acceleration is available, +// referenced from other modules to support GPU data loading +// NOTE: Useful to allow Texture, RenderTexture, Font.texture, Mesh.vaoId/vboId, Shader loading +bool isGpuReady = false; #if defined(SUPPORT_SCREEN_CAPTURE) -static int screenshotCounter = 0; // Screenshots counter +static int screenshotCounter = 0; // Screenshots counter #endif #if defined(SUPPORT_GIF_RECORDING) @@ -637,28 +642,31 @@ void InitWindow(int width, int height, const char *title) // Initialize rlgl default data (buffers and shaders) // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); + isGpuReady = true; // Flag to note GPU has been initialized successfully // Setup default viewport SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); -#if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) - // Load default font - // WARNING: External function: Module required: rtext - LoadFontDefault(); - #if defined(SUPPORT_MODULE_RSHAPES) - // Set font white rectangle for shapes drawing, so shapes and text can be batched together - // WARNING: rshapes module is required, if not available, default internal white rectangle is used - Rectangle rec = GetFontDefault().recs[95]; - if (CORE.Window.flags & FLAG_MSAA_4X_HINT) - { - // NOTE: We try to maxime rec padding to avoid pixel bleeding on MSAA filtering - SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 }); - } - else - { - // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding - SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); - } +#if defined(SUPPORT_MODULE_RTEXT) + #if defined(SUPPORT_DEFAULT_FONT) + // Load default font + // WARNING: External function: Module required: rtext + LoadFontDefault(); + #if defined(SUPPORT_MODULE_RSHAPES) + // Set font white rectangle for shapes drawing, so shapes and text can be batched together + // WARNING: rshapes module is required, if not available, default internal white rectangle is used + Rectangle rec = GetFontDefault().recs[95]; + if (CORE.Window.flags & FLAG_MSAA_4X_HINT) + { + // NOTE: We try to maxime rec padding to avoid pixel bleeding on MSAA filtering + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 }); + } + else + { + // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding + SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); + } + #endif #endif #else #if defined(SUPPORT_MODULE_RSHAPES) diff --git a/src/rtext.c b/src/rtext.c index 8c1dc3c5e..4f1be2cd7 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -124,6 +124,7 @@ //---------------------------------------------------------------------------------- // Global variables //---------------------------------------------------------------------------------- +extern bool isGpuReady; #if defined(SUPPORT_DEFAULT_FONT) // Default font provided by raylib // NOTE: Default font is loaded on InitWindow() and disposed on CloseWindow() [module: core] @@ -252,7 +253,7 @@ extern void LoadFontDefault(void) counter++; } - defaultFont.texture = LoadTextureFromImage(imFont); + if (isGpuReady) defaultFont.texture = LoadTextureFromImage(imFont); // Reconstruct charSet using charsWidth[], charsHeight, charsDivisor, glyphCount //------------------------------------------------------------------------------ @@ -308,7 +309,7 @@ extern void LoadFontDefault(void) extern void UnloadFontDefault(void) { for (int i = 0; i < defaultFont.glyphCount; i++) UnloadImage(defaultFont.glyphs[i].image); - UnloadTexture(defaultFont.texture); + if (isGpuReady) UnloadTexture(defaultFont.texture); RL_FREE(defaultFont.glyphs); RL_FREE(defaultFont.recs); } @@ -362,11 +363,14 @@ Font LoadFont(const char *fileName) UnloadImage(image); } - if (font.texture.id == 0) TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName); - else + if (isGpuReady) { - SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default, we set point filter (the best performance) - TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", FONT_TTF_DEFAULT_SIZE, FONT_TTF_DEFAULT_NUMCHARS); + if (font.texture.id == 0) TRACELOG(LOG_WARNING, "FONT: [%s] Failed to load font texture -> Using default font", fileName); + else + { + SetTextureFilter(font.texture, TEXTURE_FILTER_POINT); // By default, we set point filter (the best performance) + TRACELOG(LOG_INFO, "FONT: Data loaded successfully (%i pixel size | %i glyphs)", FONT_TTF_DEFAULT_SIZE, FONT_TTF_DEFAULT_NUMCHARS); + } } return font; @@ -487,7 +491,7 @@ Font LoadFontFromImage(Image image, Color key, int firstChar) }; // Set font with all data parsed from image - font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture + if (isGpuReady) font.texture = LoadTextureFromImage(fontClear); // Convert processed image to OpenGL texture font.glyphCount = index; font.glyphPadding = 0; @@ -556,7 +560,7 @@ Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int font.glyphPadding = FONT_TTF_DEFAULT_CHARS_PADDING; Image atlas = GenImageFontAtlas(font.glyphs, &font.recs, font.glyphCount, font.baseSize, font.glyphPadding, 0); - font.texture = LoadTextureFromImage(atlas); + if (isGpuReady) font.texture = LoadTextureFromImage(atlas); // Update glyphs[i].image to use alpha, required to be used on ImageDrawText() for (int i = 0; i < font.glyphCount; i++) @@ -580,7 +584,7 @@ Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int // Check if a font is ready bool IsFontReady(Font font) { - return ((font.texture.id > 0) && // Validate OpenGL id fot font texture atlas + return ((font.texture.id > 0) && // Validate OpenGL id for font texture atlas (font.baseSize > 0) && // Validate font size (font.glyphCount > 0) && // Validate font contains some glyph (font.recs != NULL) && // Validate font recs defining glyphs on texture atlas @@ -946,7 +950,7 @@ void UnloadFont(Font font) if (font.texture.id != GetFontDefault().texture.id) { UnloadFontData(font.glyphs, font.glyphCount); - UnloadTexture(font.texture); + if (isGpuReady) UnloadTexture(font.texture); RL_FREE(font.recs); TRACELOGD("FONT: Unloaded font data from RAM and VRAM"); @@ -1066,7 +1070,7 @@ bool ExportFontAsCode(Font font, const char *fileName) byteCount += sprintf(txtData + byteCount, " Image imFont = { fontImageData_%s, %i, %i, 1, %i };\n\n", styleName, image.width, image.height, image.format); #endif byteCount += sprintf(txtData + byteCount, " // Load texture from image\n"); - byteCount += sprintf(txtData + byteCount, " font.texture = LoadTextureFromImage(imFont);\n"); + byteCount += sprintf(txtData + byteCount, " if (isGpuReady) font.texture = LoadTextureFromImage(imFont);\n"); #if defined(SUPPORT_COMPRESSED_FONT_ATLAS) byteCount += sprintf(txtData + byteCount, " UnloadImage(imFont); // Uncompressed data can be unloaded from memory\n\n"); #endif @@ -1277,7 +1281,7 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing { Vector2 textSize = { 0 }; - if ((font.texture.id == 0) || (text == NULL)) return textSize; // Security check + if ((isGpuReady && (font.texture.id == 0)) || (text == NULL)) return textSize; // Security check int size = TextLength(text); // Get size in bytes of text int tempByteCounter = 0; // Used to count longer text line num chars @@ -2257,7 +2261,7 @@ static Font LoadBMFont(const char *fileName) RL_FREE(imFonts); - font.texture = LoadTextureFromImage(fullFont); + if (isGpuReady) font.texture = LoadTextureFromImage(fullFont); // Fill font characters info data font.baseSize = fontSize; @@ -2299,7 +2303,7 @@ static Font LoadBMFont(const char *fileName) UnloadImage(fullFont); UnloadFileText(fileText); - if (font.texture.id == 0) + if (isGpuReady && (font.texture.id == 0)) { UnloadFont(font); font = GetFontDefault(); From 0aba21f71c30f3375fa898f8c733089528617639 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Sat, 24 Aug 2024 11:31:28 -0700 Subject: [PATCH 18/28] [RCORE] Update comments on fullscreen and boderless window to describe what they do (#4280) * Update raylib_api.* by CI * update fullscreen and borderless comments to better describe what they do. * Update raylib_api.* by CI --------- Co-authored-by: github-actions[bot] --- parser/output/raylib_api.json | 4 ++-- parser/output/raylib_api.lua | 4 ++-- parser/output/raylib_api.txt | 4 ++-- parser/output/raylib_api.xml | 4 ++-- src/raylib.h | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/parser/output/raylib_api.json b/parser/output/raylib_api.json index dfe37e2fe..03b19325a 100644 --- a/parser/output/raylib_api.json +++ b/parser/output/raylib_api.json @@ -3215,12 +3215,12 @@ }, { "name": "ToggleFullscreen", - "description": "Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP)", + "description": "Toggle window state: fullscreen/windowed [resizes monitor to match window resolution] (only PLATFORM_DESKTOP)", "returnType": "void" }, { "name": "ToggleBorderlessWindowed", - "description": "Toggle window state: borderless windowed (only PLATFORM_DESKTOP)", + "description": "Toggle window state: borderless windowed [resizes window to match monitor resolution] (only PLATFORM_DESKTOP)", "returnType": "void" }, { diff --git a/parser/output/raylib_api.lua b/parser/output/raylib_api.lua index 933b2c704..7d74c9a41 100644 --- a/parser/output/raylib_api.lua +++ b/parser/output/raylib_api.lua @@ -3158,12 +3158,12 @@ return { }, { name = "ToggleFullscreen", - description = "Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP)", + description = "Toggle window state: fullscreen/windowed [resizes monitor to match window resolution] (only PLATFORM_DESKTOP)", returnType = "void" }, { name = "ToggleBorderlessWindowed", - description = "Toggle window state: borderless windowed (only PLATFORM_DESKTOP)", + description = "Toggle window state: borderless windowed [resizes window to match monitor resolution] (only PLATFORM_DESKTOP)", returnType = "void" }, { diff --git a/parser/output/raylib_api.txt b/parser/output/raylib_api.txt index 94562d66a..fb30ebe5f 100644 --- a/parser/output/raylib_api.txt +++ b/parser/output/raylib_api.txt @@ -1056,12 +1056,12 @@ Function 013: ClearWindowState() (1 input parameters) Function 014: ToggleFullscreen() (0 input parameters) Name: ToggleFullscreen Return type: void - Description: Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP) + Description: Toggle window state: fullscreen/windowed [resizes monitor to match window resolution] (only PLATFORM_DESKTOP) No input parameters Function 015: ToggleBorderlessWindowed() (0 input parameters) Name: ToggleBorderlessWindowed Return type: void - Description: Toggle window state: borderless windowed (only PLATFORM_DESKTOP) + Description: Toggle window state: borderless windowed [resizes window to match monitor resolution] (only PLATFORM_DESKTOP) No input parameters Function 016: MaximizeWindow() (0 input parameters) Name: MaximizeWindow diff --git a/parser/output/raylib_api.xml b/parser/output/raylib_api.xml index 02cfd02d0..532b9e3b9 100644 --- a/parser/output/raylib_api.xml +++ b/parser/output/raylib_api.xml @@ -703,9 +703,9 @@ - + - + diff --git a/src/raylib.h b/src/raylib.h index f51a444e1..7e9af7ada 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -968,8 +968,8 @@ RLAPI bool IsWindowResized(void); // Check if wi RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags (only PLATFORM_DESKTOP) RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags -RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP) -RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed (only PLATFORM_DESKTOP) +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed [resizes monitor to match window resolution] (only PLATFORM_DESKTOP) +RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed [resizes window to match monitor resolution] (only PLATFORM_DESKTOP) RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable (only PLATFORM_DESKTOP) RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable (only PLATFORM_DESKTOP) RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized (only PLATFORM_DESKTOP) From 0c06a08e0738a5777d150b7b4562cd8595680271 Mon Sep 17 00:00:00 2001 From: Dave Green <34277803+SoloByte@users.noreply.github.com> Date: Sat, 24 Aug 2024 20:33:21 +0200 Subject: [PATCH 19/28] [rcore][desktop_glfw] Keeping CORE.Window.position properly in sync with glfw window position (#4190) * WindowPosCallback added. CORE.Window.position is now properly kept in sync with the glfw window position. * Update rcore_desktop_glfw.c Comments updated. * Setting CORE.Window.position correctly in InitPlatform() as well. This also fixes not centering the window correctly when the high dpi flag was enabled. * Fixes centering the window in the SetWindowMonitor() function. Here the render size has to be used again in case the high dpi flag is enabled. * Update Window Position Update Window Position right away in ToggleFullscreen() & ToggleBorderlessWindowed() functions --- src/platforms/rcore_desktop_glfw.c | 47 ++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/platforms/rcore_desktop_glfw.c b/src/platforms/rcore_desktop_glfw.c index d64bf60db..c6685846c 100644 --- a/src/platforms/rcore_desktop_glfw.c +++ b/src/platforms/rcore_desktop_glfw.c @@ -109,6 +109,7 @@ static void ErrorCallback(int error, const char *description); // Window callbacks events static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized +static void WindowPosCallback(GLFWwindow* window, int x, int y); // GLFW3 WindowPos Callback, runs when window is moved static void WindowIconifyCallback(GLFWwindow *window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored static void WindowMaximizeCallback(GLFWwindow* window, int maximized); // GLFW3 Window Maximize Callback, runs when window is maximized static void WindowFocusCallback(GLFWwindow *window, int focused); // GLFW3 WindowFocus Callback, runs when window get/lose focus @@ -147,8 +148,8 @@ void ToggleFullscreen(void) if (!CORE.Window.fullscreen) { // Store previous window position (in case we exit fullscreen) - glfwGetWindowPos(platform.handle, &CORE.Window.position.x, &CORE.Window.position.y); - + CORE.Window.previousPosition = CORE.Window.position; + int monitorCount = 0; int monitorIndex = GetCurrentMonitor(); GLFWmonitor **monitors = glfwGetMonitors(&monitorCount); @@ -179,7 +180,11 @@ void ToggleFullscreen(void) CORE.Window.fullscreen = false; CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; - glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.position.x, CORE.Window.position.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + glfwSetWindowMonitor(platform.handle, NULL, CORE.Window.previousPosition.x, CORE.Window.previousPosition.y, CORE.Window.screen.width, CORE.Window.screen.height, GLFW_DONT_CARE); + + // we update the window position right away + CORE.Window.position.x = CORE.Window.previousPosition.x; + CORE.Window.position.y = CORE.Window.previousPosition.y; } // Try to enable GPU V-Sync, so frames are limited to screen refresh rate (60Hz -> 60 FPS) @@ -190,11 +195,11 @@ void ToggleFullscreen(void) // Toggle borderless windowed mode void ToggleBorderlessWindowed(void) { - // Leave fullscreen before attempting to set borderless windowed mode and get screen position from it + // Leave fullscreen before attempting to set borderless windowed mode bool wasOnFullscreen = false; if (CORE.Window.fullscreen) { - CORE.Window.previousPosition = CORE.Window.position; + // fullscreen already saves the previous position so it does not need to be set here again ToggleFullscreen(); wasOnFullscreen = true; } @@ -213,7 +218,7 @@ void ToggleBorderlessWindowed(void) { // Store screen position and size // NOTE: If it was on fullscreen, screen position was already stored, so skip setting it here - if (!wasOnFullscreen) glfwGetWindowPos(platform.handle, &CORE.Window.previousPosition.x, &CORE.Window.previousPosition.y); + if (!wasOnFullscreen) CORE.Window.previousPosition = CORE.Window.position; CORE.Window.previousScreen = CORE.Window.screen; // Set undecorated and topmost modes and flags @@ -255,6 +260,9 @@ void ToggleBorderlessWindowed(void) glfwFocusWindow(platform.handle); CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; + + CORE.Window.position.x = CORE.Window.previousPosition.x; + CORE.Window.position.y = CORE.Window.previousPosition.y; } } else TRACELOG(LOG_WARNING, "GLFW: Failed to find video mode for selected monitor"); @@ -592,6 +600,9 @@ void SetWindowTitle(const char *title) // Set window position on screen (windowed mode) void SetWindowPosition(int x, int y) { + // Update CORE.Window.position as well + CORE.Window.position.x = x; + CORE.Window.position.y = y; glfwSetWindowPos(platform.handle, x, y); } @@ -614,8 +625,9 @@ void SetWindowMonitor(int monitor) { TRACELOG(LOG_INFO, "GLFW: Selected monitor: [%i] %s", monitor, glfwGetMonitorName(monitors[monitor])); - const int screenWidth = CORE.Window.screen.width; - const int screenHeight = CORE.Window.screen.height; + // Here the render width has to be used again in case high dpi flag is enabled + const int screenWidth = CORE.Window.render.width; + const int screenHeight = CORE.Window.render.height; int monitorWorkareaX = 0; int monitorWorkareaY = 0; int monitorWorkareaWidth = 0; @@ -1568,12 +1580,17 @@ int InitPlatform(void) int monitorWidth = 0; int monitorHeight = 0; glfwGetMonitorWorkarea(monitor, &monitorX, &monitorY, &monitorWidth, &monitorHeight); - - int posX = monitorX + (monitorWidth - (int)CORE.Window.screen.width)/2; - int posY = monitorY + (monitorHeight - (int)CORE.Window.screen.height)/2; + + // Here CORE.Window.render.width/height should be used instead of CORE.Window.screen.width/height to center the window correctly when the high dpi flag is enabled. + int posX = monitorX + (monitorWidth - (int)CORE.Window.render.width)/2; + int posY = monitorY + (monitorHeight - (int)CORE.Window.render.height)/2; if (posX < monitorX) posX = monitorX; if (posY < monitorY) posY = monitorY; SetWindowPosition(posX, posY); + + // Update CORE.Window.position here so it is correct from the start + CORE.Window.position.x = posX; + CORE.Window.position.y = posY; } // Load OpenGL extensions @@ -1585,6 +1602,7 @@ int InitPlatform(void) //---------------------------------------------------------------------------- // Set window callback events glfwSetWindowSizeCallback(platform.handle, WindowSizeCallback); // NOTE: Resizing not allowed by default! + glfwSetWindowPosCallback(platform.handle, WindowPosCallback); glfwSetWindowMaximizeCallback(platform.handle, WindowMaximizeCallback); glfwSetWindowIconifyCallback(platform.handle, WindowIconifyCallback); glfwSetWindowFocusCallback(platform.handle, WindowFocusCallback); @@ -1681,7 +1699,12 @@ static void WindowSizeCallback(GLFWwindow *window, int width, int height) // NOTE: Postprocessing texture is not scaled to new size } - +static void WindowPosCallback(GLFWwindow* window, int x, int y) +{ + // Set current window position + CORE.Window.position.x = x; + CORE.Window.position.y = y; +} static void WindowContentScaleCallback(GLFWwindow *window, float scalex, float scaley) { CORE.Window.screenScale = MatrixScale(scalex, scaley, 1.0f); From d314afc451e4b0ed6fe69249dffccb65bd1e0582 Mon Sep 17 00:00:00 2001 From: Tchan0 <61758157+Tchan0@users.noreply.github.com> Date: Sat, 24 Aug 2024 20:35:49 +0200 Subject: [PATCH 20/28] rlgl.h: glint64 did not exist before OpenGL 3.2 (#4284) Compilation breaks on rlgl.h for early OpenGL versions. Glint64 did not exist on those versions (< OpenGL 3.2) --- src/rlgl.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rlgl.h b/src/rlgl.h index 2f8515ee2..3efed736a 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4414,14 +4414,14 @@ void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSi // Get SSBO buffer size unsigned int rlGetShaderBufferSize(unsigned int id) { - GLint64 size = 0; - #if defined(GRAPHICS_API_OPENGL_43) + GLint64 size = 0; glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); glGetBufferParameteri64v(GL_SHADER_STORAGE_BUFFER, GL_BUFFER_SIZE, &size); -#endif - return (size > 0)? (unsigned int)size : 0; +#else + return 0; +#endif } // Read SSBO buffer data (GPU->CPU) From 8dbf3712445c27e86bebf1b82e1de04cf1099a91 Mon Sep 17 00:00:00 2001 From: Tchan0 <61758157+Tchan0@users.noreply.github.com> Date: Sun, 25 Aug 2024 11:23:08 +0200 Subject: [PATCH 21/28] Examples makefiles: align /usr/local with /src Makefile (#4286) * align /usr/local with src Makefile Align /usr/local with the /src Makefile, where it can be overriden. * /usr/local: allow override align /usr/local with the /src Makefile, where it can be overriden --- examples/Makefile | 5 +++-- examples/Makefile.Web | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/Makefile b/examples/Makefile index 904917f7c..bdbb6e351 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -70,8 +70,9 @@ RAYLIB_SRC_PATH ?= ../src # Locations of raylib.h and libraylib.a/libraylib.so # NOTE: Those variables are only used for PLATFORM_OS: LINUX, BSD -RAYLIB_INCLUDE_PATH ?= /usr/local/include -RAYLIB_LIB_PATH ?= /usr/local/lib +DESTDIR ?= /usr/local +RAYLIB_INCLUDE_PATH ?= $(DESTDIR)/include +RAYLIB_LIB_PATH ?= $(DESTDIR)/lib # Library type compilation: STATIC (.a) or SHARED (.so/.dll) RAYLIB_LIBTYPE ?= STATIC diff --git a/examples/Makefile.Web b/examples/Makefile.Web index 7057da1c0..bd48c2a4a 100644 --- a/examples/Makefile.Web +++ b/examples/Makefile.Web @@ -35,8 +35,9 @@ RAYLIB_PATH ?= .. # Locations of raylib.h and libraylib.a/libraylib.so # NOTE: Those variables are only used for PLATFORM_OS: LINUX, BSD -RAYLIB_INCLUDE_PATH ?= /usr/local/include -RAYLIB_LIB_PATH ?= /usr/local/lib +DESTDIR ?= /usr/local +RAYLIB_INCLUDE_PATH ?= $(DESTDIR)/include +RAYLIB_LIB_PATH ?= $(DESTDIR)/lib # Library type compilation: STATIC (.a) or SHARED (.so/.dll) RAYLIB_LIBTYPE ?= STATIC From dec7f12b148ea4383c166d42812e19e0c793bdb0 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 25 Aug 2024 12:51:55 +0200 Subject: [PATCH 22/28] Update raylib.h --- src/raylib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/raylib.h b/src/raylib.h index 7e9af7ada..30ba8bd2a 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1443,7 +1443,7 @@ RLAPI int GetPixelDataSize(int width, int height, int format); // G // Font loading/unloading functions RLAPI Font GetFontDefault(void); // Get the default Font RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) -RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set +RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' RLAPI bool IsFontReady(Font font); // Check if a font is ready From 157c7c21d451e022f8227ee5b756a99689a1d3a3 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 25 Aug 2024 12:52:27 +0200 Subject: [PATCH 23/28] ADDED: more uniform data type options #4137 --- src/rlgl.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/rlgl.h b/src/rlgl.h index 3efed736a..34fabe45e 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -527,6 +527,10 @@ typedef enum { RL_SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) RL_SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) RL_SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + RL_SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int + RL_SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) + RL_SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) + RL_SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) RL_SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d } rlShaderUniformDataType; @@ -4236,6 +4240,10 @@ void rlSetUniform(int locIndex, const void *value, int uniformType, int count) case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); From 5604f6d8a34b94118a9f24e433eb06e771d8378c Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 25 Aug 2024 12:57:05 +0200 Subject: [PATCH 24/28] Update rlgl.h --- src/rlgl.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rlgl.h b/src/rlgl.h index 34fabe45e..9b94d402c 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -4240,10 +4240,12 @@ void rlSetUniform(int locIndex, const void *value, int uniformType, int count) case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + #if !defined(GRAPHICS_API_OPENGL_ES2) case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; + #endif case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); From f5ef3578100ff06dc1ad3752076e5771de4717f9 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 25 Aug 2024 13:55:53 +0200 Subject: [PATCH 25/28] Update rtext.c --- src/rtext.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rtext.c b/src/rtext.c index 4f1be2cd7..34b6fcedb 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -677,6 +677,8 @@ GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSiz { stbtt_GetCodepointHMetrics(&fontInfo, ch, &chars[i].advanceX, NULL); chars[i].advanceX = (int)((float)chars[i].advanceX*scaleFactor); + + if (chh > fontSize) TRACELOG(LOG_WARNING, "FONT: Character [0x%08x] size is bigger than expected font size", ch); // Load characters images chars[i].image.width = chw; From 91a9888baa61e221cb6e68a5fd8fc005fffcbca8 Mon Sep 17 00:00:00 2001 From: Jeffery Myers Date: Sun, 25 Aug 2024 09:49:52 -0700 Subject: [PATCH 26/28] [rModels] Correctly split obj meshes by material (#4285) * Correctly split meshes from tinyobj by material so they can be represented by raylib correctly * PR Feedback --- src/rmodels.c | 304 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 206 insertions(+), 98 deletions(-) diff --git a/src/rmodels.c b/src/rmodels.c index 966a8665c..5fb512403 100644 --- a/src/rmodels.c +++ b/src/rmodels.c @@ -2016,6 +2016,8 @@ static void ProcessMaterialsOBJ(Material *materials, tinyobj_material_t *mats, i // NOTE: Uses default shader, which only supports MATERIAL_MAP_DIFFUSE materials[m] = LoadMaterialDefault(); + if (mats == NULL) continue; + // Get default texture, in case no texture is defined // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 materials[m].maps[MATERIAL_MAP_DIFFUSE].texture = (Texture2D){ rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; @@ -4073,130 +4075,236 @@ static void BuildPoseFromParentJoints(BoneInfo *bones, int boneCount, Transform // - the mesh is automatically triangulated by tinyobj static Model LoadOBJ(const char *fileName) { + tinyobj_attrib_t objAttributes = { 0 }; + tinyobj_shape_t* objShapes = NULL; + unsigned int objShapeCount = 0; + + tinyobj_material_t* objMaterials = NULL; + unsigned int objMaterialCount = 0; + Model model = { 0 }; + model.transform = MatrixIdentity(); - tinyobj_attrib_t attrib = { 0 }; - tinyobj_shape_t *meshes = NULL; - unsigned int meshCount = 0; + char* fileText = LoadFileText(fileName); - tinyobj_material_t *materials = NULL; - unsigned int materialCount = 0; - - char *fileText = LoadFileText(fileName); - - if (fileText != NULL) + if (fileText == NULL) { - unsigned int dataSize = (unsigned int)strlen(fileText); + TRACELOG(LOG_ERROR, "MODEL Unable to read obj file %s", fileName); + return model; + } - char currentDir[1024] = { 0 }; - strcpy(currentDir, GetWorkingDirectory()); // Save current working directory - const char *workingDir = GetDirectoryPath(fileName); // Switch to OBJ directory for material path correctness - if (CHDIR(workingDir) != 0) + char currentDir[1024] = { 0 }; + strcpy(currentDir, GetWorkingDirectory()); // Save current working directory + const char* workingDir = GetDirectoryPath(fileName); // Switch to OBJ directory for material path correctness + if (CHDIR(workingDir) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir); + } + + unsigned int dataSize = (unsigned int)strlen(fileText); + + unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; + int ret = tinyobj_parse_obj(&objAttributes, &objShapes, &objShapeCount, &objMaterials, &objMaterialCount, fileText, dataSize, flags); + + if (ret != TINYOBJ_SUCCESS) + { + TRACELOG(LOG_ERROR, "MODEL Unable to read obj data %s", fileName); + return model; + } + + UnloadFileText(fileText); + + unsigned int faceVertIndex = 0; + unsigned int nextShape = 1; + int lastMaterial = -1; + unsigned int meshIndex = 0; + + // count meshes + unsigned int nextShapeEnd = objAttributes.num_face_num_verts; + + // see how many verts till the next shape + + if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset; + + // walk all the faces + for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) + { + if (faceVertIndex >= nextShapeEnd) { - TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", workingDir); + // try to find the last vert in the next shape + nextShape++; + if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; + else nextShapeEnd = objAttributes.num_face_num_verts; // this is actually the total number of face verts in the file, not faces + meshIndex++; + } + else if (lastMaterial != -1 && objAttributes.material_ids[faceId] != lastMaterial) + { + meshIndex++;// if this is a new material, we need to allocate a new mesh } - unsigned int flags = TINYOBJ_FLAG_TRIANGULATE; - int ret = tinyobj_parse_obj(&attrib, &meshes, &meshCount, &materials, &materialCount, fileText, dataSize, flags); + lastMaterial = objAttributes.material_ids[faceId]; + faceVertIndex += objAttributes.face_num_verts[faceId]; + } - if (ret != TINYOBJ_SUCCESS) TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to load OBJ data", fileName); - else TRACELOG(LOG_INFO, "MODEL: [%s] OBJ data loaded successfully: %i meshes/%i materials", fileName, meshCount, materialCount); + // allocate the base meshes and materials + model.meshCount = meshIndex + 1; + model.meshes = (Mesh*)MemAlloc(sizeof(Mesh) * model.meshCount); - // WARNING: We are not splitting meshes by materials (previous implementation) - // Depending on the provided OBJ that was not the best option and it just crashed - // so, implementation was simplified to prioritize parsed meshes - model.meshCount = meshCount; + if (objMaterialCount > 0) + { + model.materialCount = objMaterialCount; + model.materials = (Material*)MemAlloc(sizeof(Material) * objMaterialCount); + } + else // we must allocate at least one material + { + model.materialCount = 1; + model.materials = (Material*)MemAlloc(sizeof(Material) * 1); + } - // Set number of materials available - // NOTE: There could be more materials available than meshes but it will be resolved at - // model.meshMaterial, just assigning the right material to corresponding mesh - model.materialCount = materialCount; - if (model.materialCount == 0) + model.meshMaterial = (int*)MemAlloc(sizeof(int) * model.meshCount); + + // see how many verts are in each mesh + unsigned int* localMeshVertexCounts = (unsigned int*)MemAlloc(sizeof(unsigned int) * model.meshCount); + + faceVertIndex = 0; + nextShapeEnd = objAttributes.num_face_num_verts; + lastMaterial = -1; + meshIndex = 0; + unsigned int localMeshVertexCount = 0; + + nextShape = 1; + if (objShapeCount > 1) + nextShapeEnd = objShapes[nextShape].face_offset; + + // walk all the faces + for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) + { + bool newMesh = false; // do we need a new mesh? + if (faceVertIndex >= nextShapeEnd) { - model.materialCount = 1; - TRACELOG(LOG_INFO, "MODEL: No materials provided, setting one default material for all meshes"); + // try to find the last vert in the next shape + nextShape++; + if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; + else nextShapeEnd = objAttributes.num_face_num_verts; // this is actually the total number of face verts in the file, not faces + + newMesh = true; } - else if (model.materialCount > 1 && model.meshCount > 1) + else if (lastMaterial != -1 && objAttributes.material_ids[faceId] != lastMaterial) { - // TEMP warning about multiple materials, to be removed when proper splitting code is implemented - // any obj with multiple materials will need to have it's materials assigned by the user in code to work at this time - TRACELOG(LOG_INFO, "MODEL: OBJ has multiple materials, manual material assignment will be required."); + newMesh = true; } - // Init model meshes and materials - model.meshes = (Mesh *)RL_CALLOC(model.meshCount, sizeof(Mesh)); - model.meshMaterial = (int *)RL_CALLOC(model.meshCount, sizeof(int)); // Material index assigned to each mesh - model.materials = (Material *)RL_CALLOC(model.materialCount, sizeof(Material)); + lastMaterial = objAttributes.material_ids[faceId]; - // Process each provided mesh - for (int i = 0; i < model.meshCount; i++) + if (newMesh) { - // WARNING: We need to calculate the mesh triangles manually using meshes[i].face_offset - // because in case of triangulated quads, meshes[i].length actually report quads, - // despite the triangulation that is efectively considered on attrib.num_faces - unsigned int tris = 0; - if (i == model.meshCount - 1) tris = attrib.num_faces - meshes[i].face_offset; - else tris = meshes[i + 1].face_offset; + localMeshVertexCounts[meshIndex] = localMeshVertexCount; - model.meshes[i].vertexCount = tris*3; - model.meshes[i].triangleCount = tris; // Face count (triangulated) - model.meshes[i].vertices = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); - model.meshes[i].texcoords = (float *)RL_CALLOC(model.meshes[i].vertexCount*2, sizeof(float)); - model.meshes[i].normals = (float *)RL_CALLOC(model.meshes[i].vertexCount*3, sizeof(float)); - model.meshMaterial[i] = 0; // By default, assign material 0 to each mesh - - // Process all mesh faces - for (unsigned int face = 0, f = meshes[i].face_offset, v = 0, vt = 0, vn = 0; face < tris; face++, f++, v += 3, vt += 3, vn += 3) - { - // Get indices for the face - tinyobj_vertex_index_t idx0 = attrib.faces[f*3 + 0]; - tinyobj_vertex_index_t idx1 = attrib.faces[f*3 + 1]; - tinyobj_vertex_index_t idx2 = attrib.faces[f*3 + 2]; - - // Fill vertices buffer (float) using vertex index of the face - for (int n = 0; n < 3; n++) { model.meshes[i].vertices[v*3 + n] = attrib.vertices[idx0.v_idx*3 + n]; } - for (int n = 0; n < 3; n++) { model.meshes[i].vertices[(v + 1)*3 + n] = attrib.vertices[idx1.v_idx*3 + n]; } - for (int n = 0; n < 3; n++) { model.meshes[i].vertices[(v + 2)*3 + n] = attrib.vertices[idx2.v_idx*3 + n]; } - - if (attrib.num_texcoords > 0) - { - // Fill texcoords buffer (float) using vertex index of the face - // NOTE: Y-coordinate must be flipped upside-down - model.meshes[i].texcoords[vt*2 + 0] = attrib.texcoords[idx0.vt_idx*2 + 0]; - model.meshes[i].texcoords[vt*2 + 1] = 1.0f - attrib.texcoords[idx0.vt_idx*2 + 1]; - - model.meshes[i].texcoords[(vt + 1)*2 + 0] = attrib.texcoords[idx1.vt_idx*2 + 0]; - model.meshes[i].texcoords[(vt + 1)*2 + 1] = 1.0f - attrib.texcoords[idx1.vt_idx*2 + 1]; - - model.meshes[i].texcoords[(vt + 2)*2 + 0] = attrib.texcoords[idx2.vt_idx*2 + 0]; - model.meshes[i].texcoords[(vt + 2)*2 + 1] = 1.0f - attrib.texcoords[idx2.vt_idx*2 + 1]; - } - - if (attrib.num_normals > 0) - { - // Fill normals buffer (float) using vertex index of the face - for (int n = 0; n < 3; n++) { model.meshes[i].normals[vn*3 + n] = attrib.normals[idx0.vn_idx*3 + n]; } - for (int n = 0; n < 3; n++) { model.meshes[i].normals[(vn + 1)*3 + n] = attrib.normals[idx1.vn_idx*3 + n]; } - for (int n = 0; n < 3; n++) { model.meshes[i].normals[(vn + 2)*3 + n] = attrib.normals[idx2.vn_idx*3 + n]; } - } - } + localMeshVertexCount = 0; + meshIndex++; } - // Init model materials - if (materialCount > 0) ProcessMaterialsOBJ(model.materials, materials, materialCount); - else model.materials[0] = LoadMaterialDefault(); // Set default material for the mesh + faceVertIndex += objAttributes.face_num_verts[faceId]; + localMeshVertexCount += objAttributes.face_num_verts[faceId]; + } + localMeshVertexCounts[meshIndex] = localMeshVertexCount; - tinyobj_attrib_free(&attrib); - tinyobj_shapes_free(meshes, model.meshCount); - tinyobj_materials_free(materials, materialCount); + for (int i = 0; i < model.meshCount; i++) + { + // allocate the buffers for each mesh + unsigned int vertexCount = localMeshVertexCounts[i]; - UnloadFileText(fileText); + model.meshes[i].vertexCount = vertexCount; + model.meshes[i].triangleCount = vertexCount / 3; - // Restore current working directory - if (CHDIR(currentDir) != 0) + model.meshes[i].vertices = (float*)MemAlloc(sizeof(float) * vertexCount * 3); + model.meshes[i].normals = (float*)MemAlloc(sizeof(float) * vertexCount * 3); + model.meshes[i].texcoords = (float*)MemAlloc(sizeof(float) * vertexCount * 2); + model.meshes[i].colors = (unsigned char*)MemAlloc(sizeof(unsigned char) * vertexCount * 4); + } + + MemFree(localMeshVertexCounts); + localMeshVertexCounts = NULL; + + // fill meshes + faceVertIndex = 0; + + nextShapeEnd = objAttributes.num_face_num_verts; + + // see how many verts till the next shape + nextShape = 1; + if (objShapeCount > 1) nextShapeEnd = objShapes[nextShape].face_offset; + lastMaterial = -1; + meshIndex = 0; + localMeshVertexCount = 0; + + // walk all the faces + for (unsigned int faceId = 0; faceId < objAttributes.num_faces; faceId++) + { + bool newMesh = false; // do we need a new mesh? + if (faceVertIndex >= nextShapeEnd) { - TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir); + // try to find the last vert in the next shape + nextShape++; + if (nextShape < objShapeCount) nextShapeEnd = objShapes[nextShape].face_offset; + else nextShapeEnd = objAttributes.num_face_num_verts; // this is actually the total number of face verts in the file, not faces + newMesh = true; } + // if this is a new material, we need to allocate a new mesh + if (lastMaterial != -1 && objAttributes.material_ids[faceId] != lastMaterial) newMesh = true; + lastMaterial = objAttributes.material_ids[faceId];; + + if (newMesh) + { + localMeshVertexCount = 0; + meshIndex++; + } + + int matId = 0; + if (lastMaterial >= 0 && lastMaterial < (int)objMaterialCount) + matId = lastMaterial; + + model.meshMaterial[meshIndex] = matId; + + for (int f = 0; f < objAttributes.face_num_verts[faceId]; f++) + { + int vertIndex = objAttributes.faces[faceVertIndex].v_idx; + int normalIndex = objAttributes.faces[faceVertIndex].vn_idx; + int texcordIndex = objAttributes.faces[faceVertIndex].vt_idx; + + for (int i = 0; i < 3; i++) + model.meshes[meshIndex].vertices[localMeshVertexCount * 3 + i] = objAttributes.vertices[vertIndex * 3 + i]; + + for (int i = 0; i < 3; i++) + model.meshes[meshIndex].normals[localMeshVertexCount * 3 + i] = objAttributes.normals[normalIndex * 3 + i]; + + for (int i = 0; i < 2; i++) + model.meshes[meshIndex].texcoords[localMeshVertexCount * 2 + i] = objAttributes.texcoords[texcordIndex * 2 + i]; + + model.meshes[meshIndex].texcoords[localMeshVertexCount * 2 + 1] = 1.0f - model.meshes[meshIndex].texcoords[localMeshVertexCount * 2 + 1]; + + for (int i = 0; i < 4; i++) + model.meshes[meshIndex].colors[localMeshVertexCount * 4 + i] = 255; + + faceVertIndex++; + localMeshVertexCount++; + } + } + + if (objMaterialCount > 0) ProcessMaterialsOBJ(model.materials, objMaterials, objMaterialCount); + else model.materials[0] = LoadMaterialDefault(); // Set default material for the mesh + + tinyobj_attrib_free(&objAttributes); + tinyobj_shapes_free(objShapes, objShapeCount); + tinyobj_materials_free(objMaterials, objMaterialCount); + + for (int i = 0; i < model.meshCount; i++) + UploadMesh(model.meshes + i, true); + + // Restore current working directory + if (CHDIR(currentDir) != 0) + { + TRACELOG(LOG_WARNING, "MODEL: [%s] Failed to change working directory", currentDir); } return model; From 8ea5db3ec43d1fa702b29df9066307d0d337b229 Mon Sep 17 00:00:00 2001 From: Hesham Abourgheba Date: Sun, 25 Aug 2024 19:51:08 +0300 Subject: [PATCH 27/28] fix(rcore/android): Allow main() to return it its caller on configuration changes. (#4288) --- src/platforms/rcore_android.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/platforms/rcore_android.c b/src/platforms/rcore_android.c index 7a2162025..55706c591 100644 --- a/src/platforms/rcore_android.c +++ b/src/platforms/rcore_android.c @@ -694,11 +694,12 @@ void PollInputEvents(void) // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); - // NOTE: Never close window, native activity is controlled by the system! + // NOTE: Allow closing the window in case a configuration change happened. + // The android_main function should be allowed to return to its caller in order for the + // Android OS to relaunch the activity. if (platform.app->destroyRequested != 0) { - //CORE.Window.shouldClose = true; - //ANativeActivity_finish(platform.app->activity); + CORE.Window.shouldClose = true; } } } @@ -781,7 +782,7 @@ int InitPlatform(void) // Process this event if (platform.source != NULL) platform.source->process(platform.app, platform.source); - // NOTE: Never close window, native activity is controlled by the system! + // NOTE: It's highly likely destroyRequested will never be non-zero at the start of the activity lifecycle. //if (platform.app->destroyRequested != 0) CORE.Window.shouldClose = true; } } @@ -812,6 +813,12 @@ void ClosePlatform(void) eglTerminate(platform.device); platform.device = EGL_NO_DISPLAY; } + + // NOTE: Reset global state in case the activity is being relaunched. + if (platform.app->destroyRequested != 0) { + CORE = (CoreData){0}; + platform = (PlatformData){0}; + } } // Initialize display device and framebuffer From 5f49ec3d6448502d798d4d404e08e354ec48012a Mon Sep 17 00:00:00 2001 From: hanaxars <156359933+hanaxars@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:35:35 +0300 Subject: [PATCH 28/28] Fix missing equal sign (#4294) I just noticed there is a missing equal sign. This PR fixes this. --- src/platforms/rcore_drm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platforms/rcore_drm.c b/src/platforms/rcore_drm.c index 291fd93c7..43dc19c71 100644 --- a/src/platforms/rcore_drm.c +++ b/src/platforms/rcore_drm.c @@ -206,7 +206,7 @@ static const short linuxToRaylibMap[KEYMAP_SIZE] = { [BTN_TL] = GAMEPAD_BUTTON_LEFT_TRIGGER_1, [BTN_TL2] = GAMEPAD_BUTTON_LEFT_TRIGGER_2, [BTN_TR] = GAMEPAD_BUTTON_RIGHT_TRIGGER_1, - [BTN_TR2] GAMEPAD_BUTTON_RIGHT_TRIGGER_2, + [BTN_TR2] = GAMEPAD_BUTTON_RIGHT_TRIGGER_2, [BTN_SELECT] = GAMEPAD_BUTTON_MIDDLE_LEFT, [BTN_MODE] = GAMEPAD_BUTTON_MIDDLE, [BTN_START] = GAMEPAD_BUTTON_MIDDLE_RIGHT,