diff --git a/BINDINGS.md b/BINDINGS.md index 4cd22013d..c66c12808 100644 --- a/BINDINGS.md +++ b/BINDINGS.md @@ -7,6 +7,7 @@ Some people ported raylib to other languages in form of bindings or wrappers to | name | raylib version | language | license | repo | |:------------------:|:---------------:|:---------:|:----------:|-----------------------------------------------------------| | raylib | **5.0** | [C/C++](https://en.wikipedia.org/wiki/C_(programming_language)) | Zlib | https://github.com/raysan5/raylib | +| raylib-beef | **5.0** | [Beef](https://www.beeflang.org/) | MIT | https://github.com/Starpelly/raylib-beef | | raylib-boo | 3.7 | [Boo](http://boo-language.github.io/)| MIT | https://github.com/Rabios/raylib-boo | | Raylib-cs | **4.5** | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | Zlib | https://github.com/ChrisDill/Raylib-cs | | Raylib-CsLo | 4.2 | [C#](https://en.wikipedia.org/wiki/C_Sharp_(programming_language)) | MPL-2.0 | https://github.com/NotNotTech/Raylib-CsLo | diff --git a/CMakeLists.txt b/CMakeLists.txt index 9619768c8..ce2de5788 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.5) project(raylib) # Avoid excessive expansion of variables in conditionals. In particular, if diff --git a/examples/audio/audio_stream_effects.c b/examples/audio/audio_stream_effects.c index 219305057..2262d41f9 100644 --- a/examples/audio/audio_stream_effects.c +++ b/examples/audio/audio_stream_effects.c @@ -2,7 +2,7 @@ * * raylib [audio] example - Music stream processing effects * -* Example originally created with raylib 4.2, last time updated with raylib 4.2 +* Example originally created with raylib 4.2, last time updated with raylib 5.0 * * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, * BSD-like license that allows static linking with closed source software @@ -13,7 +13,7 @@ #include "raylib.h" -#include // Required for: NULL +#include // Required for: NULL // Required delay effect variables static float *delayBuffer = NULL; @@ -149,13 +149,17 @@ static void AudioProcessEffectLPF(void *buffer, unsigned int frames) static const float cutoff = 70.0f / 44100.0f; // 70 Hz lowpass filter const float k = cutoff / (cutoff + 0.1591549431f); // RC filter formula + // Converts the buffer data before using it + float *bufferData = (float *)buffer; for (unsigned int i = 0; i < frames*2; i += 2) { - float l = ((float *)buffer)[i], r = ((float *)buffer)[i + 1]; + const float l = bufferData[i]; + const float r = bufferData[i + 1]; + low[0] += k * (l - low[0]); low[1] += k * (r - low[1]); - ((float *)buffer)[i] = low[0]; - ((float *)buffer)[i + 1] = low[1]; + bufferData[i] = low[0]; + bufferData[i + 1] = low[1]; } } @@ -176,4 +180,4 @@ static void AudioProcessEffectDelay(void *buffer, unsigned int frames) delayBuffer[delayWriteIndex++] = ((float *)buffer)[i + 1]; if (delayWriteIndex == delayBufferSize) delayWriteIndex = 0; } -} \ No newline at end of file +} diff --git a/examples/core/core_vr_simulator.c b/examples/core/core_vr_simulator.c index fc2dee6b9..9acea1972 100644 --- a/examples/core/core_vr_simulator.c +++ b/examples/core/core_vr_simulator.c @@ -39,7 +39,6 @@ int main(void) .vResolution = 1200, // Vertical resolution in pixels .hScreenSize = 0.133793f, // Horizontal size in meters .vScreenSize = 0.0669f, // Vertical size in meters - .vScreenCenter = 0.04678f, // Screen center in meters .eyeToScreenDistance = 0.041f, // Distance between eye and display in meters .lensSeparationDistance = 0.07f, // Lens separation distance in meters .interpupillaryDistance = 0.07f, // IPD (distance between pupils) in meters diff --git a/examples/shaders/resources/models/old_car_new.glb b/examples/shaders/resources/models/old_car_new.glb new file mode 100644 index 000000000..119995ca7 Binary files /dev/null and b/examples/shaders/resources/models/old_car_new.glb differ diff --git a/examples/shaders/resources/models/plane.glb b/examples/shaders/resources/models/plane.glb new file mode 100644 index 000000000..452e1c5bf Binary files /dev/null and b/examples/shaders/resources/models/plane.glb differ diff --git a/examples/shaders/resources/old_car_d.png b/examples/shaders/resources/old_car_d.png new file mode 100644 index 000000000..d8b3c833d Binary files /dev/null and b/examples/shaders/resources/old_car_d.png differ diff --git a/examples/shaders/resources/old_car_e.png b/examples/shaders/resources/old_car_e.png new file mode 100644 index 000000000..23f01c0fe Binary files /dev/null and b/examples/shaders/resources/old_car_e.png differ diff --git a/examples/shaders/resources/old_car_mra.png b/examples/shaders/resources/old_car_mra.png new file mode 100644 index 000000000..0fb46b336 Binary files /dev/null and b/examples/shaders/resources/old_car_mra.png differ diff --git a/examples/shaders/resources/old_car_n.png b/examples/shaders/resources/old_car_n.png new file mode 100644 index 000000000..11f689fe2 Binary files /dev/null and b/examples/shaders/resources/old_car_n.png differ diff --git a/examples/shaders/resources/road_a.png b/examples/shaders/resources/road_a.png new file mode 100644 index 000000000..103777398 Binary files /dev/null and b/examples/shaders/resources/road_a.png differ diff --git a/examples/shaders/resources/road_mra.png b/examples/shaders/resources/road_mra.png new file mode 100644 index 000000000..988c839cc Binary files /dev/null and b/examples/shaders/resources/road_mra.png differ diff --git a/examples/shaders/resources/road_n.png b/examples/shaders/resources/road_n.png new file mode 100644 index 000000000..a5f3548c1 Binary files /dev/null and b/examples/shaders/resources/road_n.png differ diff --git a/examples/shaders/resources/shaders/glsl100/pbr.fs b/examples/shaders/resources/shaders/glsl100/pbr.fs new file mode 100644 index 000000000..1ada83317 --- /dev/null +++ b/examples/shaders/resources/shaders/glsl100/pbr.fs @@ -0,0 +1,156 @@ +#version 100 + +precision mediump float; + +#define MAX_LIGHTS 4 +#define LIGHT_DIRECTIONAL 0 +#define LIGHT_POINT 1 +#define PI 3.14159265358979323846 + +struct Light { + int enabled; + int type; + vec3 position; + vec3 target; + vec4 color; + float intensity; +}; + +// Input vertex attributes (from vertex shader) +varying in vec3 fragPosition; +varying in vec2 fragTexCoord; +varying in vec4 fragColor; +varying in vec3 fragNormal; +varying in vec4 shadowPos; +varying in mat3 TBN; + + +// Input uniform values +uniform int numOfLights; +uniform sampler2D albedoMap; +uniform sampler2D mraMap; +uniform sampler2D normalMap; +uniform sampler2D emissiveMap; // r: Hight g:emissive + +uniform vec2 tiling; +uniform vec2 offset; + +uniform int useTexAlbedo; +uniform int useTexNormal; +uniform int useTexMRA; +uniform int useTexEmissive; + +uniform vec4 albedoColor; +uniform vec4 emissiveColor; +uniform float normalValue; +uniform float metallicValue; +uniform float roughnessValue; +uniform float aoValue; +uniform float emissivePower; + +// Input lighting values +uniform Light lights[MAX_LIGHTS]; +uniform vec3 viewPos; + +uniform vec3 ambientColor; +uniform float ambient; + +// refl in range 0 to 1 +// returns base reflectivity to 1 +// incrase reflectivity when surface view at larger angle +vec3 schlickFresnel(float hDotV,vec3 refl) +{ + return refl + (1.0 - refl) * pow(1.0 - hDotV,5.0); +} + +float ggxDistribution(float nDotH,float roughness) +{ + float a = roughness * roughness * roughness * roughness; + float d = nDotH * nDotH * (a - 1.0) + 1.0; + d = PI * d * d; + return a / max(d,0.0000001); +} + +float geomSmith(float nDotV,float nDotL,float roughness) +{ + float r = roughness + 1.0; + float k = r * r / 8.0; + float ik = 1.0 - k; + float ggx1 = nDotV / (nDotV * ik + k); + float ggx2 = nDotL / (nDotL * ik + k); + return ggx1 * ggx2; +} + +vec3 pbr(){ + vec3 albedo = texture2D(albedoMap,vec2(fragTexCoord.x*tiling.x+offset.x,fragTexCoord.y*tiling.y+offset.y)).rgb; + albedo = vec3(albedoColor.x*albedo.x,albedoColor.y*albedo.y,albedoColor.z*albedo.z); + float metallic = clamp(metallicValue,0.0,1.0); + float roughness = clamp(roughnessValue,0.0,1.0); + float ao = clamp(aoValue,0.0,1.0); + if(useTexMRA == 1) { + vec4 mra = texture2D(mraMap, vec2(fragTexCoord.x * tiling.x + offset.x, fragTexCoord.y * tiling.y + offset.y)); + metallic = clamp(mra.r+metallicValue,0.04,1.0); + roughness = clamp(mra.g+roughnessValue,0.04,1.0); + ao = (mra.b+aoValue)*0.5; + } + + + + vec3 N = normalize(fragNormal); + if(useTexNormal == 1) { + N = texture2D(normalMap, vec2(fragTexCoord.x * tiling.x + offset.y, fragTexCoord.y * tiling.y + offset.y)).rgb; + N = normalize(N * 2.0 - 1.0); + N = normalize(N * TBN); + } + + vec3 V = normalize(viewPos - fragPosition); + + vec3 e = vec3(0); + e = (texture2D(emissiveMap, vec2(fragTexCoord.x*tiling.x+offset.x, fragTexCoord.y*tiling.y+offset.y)).rgb).g * emissiveColor.rgb*emissivePower * float(useTexEmissive); + + //return N;//vec3(metallic,metallic,metallic); + //if dia-electric use base reflectivity of 0.04 otherwise ut is a metal use albedo as base reflectivity + vec3 baseRefl = mix(vec3(0.04),albedo.rgb,metallic); + vec3 Lo = vec3(0.0); // acumulate lighting lum + + for(int i=0;ipbrShader = pbrShader; + + pbrMat->texAlbedo = (Texture2D){0}; + pbrMat->texNormal = (Texture2D){0}; + pbrMat->texMRA = (Texture2D){0}; + pbrMat->texEmissive = (Texture2D){0}; + + //PBRParam + pbrMat->albedo[0] = 1.0; + pbrMat->albedo[1] = 1.0; + pbrMat->albedo[2] = 1.0; + pbrMat->albedo[3] = 1.0; + pbrMat->metallic = 0; + pbrMat->roughness = 0; + pbrMat->ao = 1.0; + pbrMat->normal = 1; + pbrMat->emissive[0] = 0; + pbrMat->emissive[1] = 0; + pbrMat->emissive[2] = 0; + pbrMat->emissive[3] = 0; + + pbrMat->texTiling[0] = 1.0; + pbrMat->texTiling[1] = 1.0; + pbrMat->texOffset[0] = 0.0; + pbrMat->texOffset[1] = 0.0; + pbrMat->emissivePower = 1.0; + // Set up PBR shader material locations + + pbrMat->albedoLoc = GetShaderLocation(pbrMat->pbrShader, "albedoColor"); + pbrMat->normalLoc = GetShaderLocation(pbrMat->pbrShader, "normalValue"); + pbrMat->metallicLoc = GetShaderLocation(pbrMat->pbrShader, "metallicValue"); + pbrMat->roughnessLoc = GetShaderLocation(pbrMat->pbrShader, "roughnessValue"); + pbrMat->aoLoc = GetShaderLocation(pbrMat->pbrShader, "aoValue"); + pbrMat->emissiveColorLoc = GetShaderLocation(pbrMat->pbrShader, "emissiveColor"); + pbrMat->emissivePowerLoc = GetShaderLocation(pbrMat->pbrShader, "emissivePower"); + + pbrMat->texTilingLoc = GetShaderLocation(pbrMat->pbrShader, "tiling"); + pbrMat->texOffsetLoc = GetShaderLocation(pbrMat->pbrShader, "offset"); + + pbrMat->useTexAlbedoLoc = GetShaderLocation(pbrMat->pbrShader, "useTexAlbedo"); + pbrMat->useTexNormalLoc = GetShaderLocation(pbrMat->pbrShader, "useTexNormal"); + pbrMat->useTexMRAELoc = GetShaderLocation(pbrMat->pbrShader, "useTexMRA"); + pbrMat->useTexEmissiveLoc = GetShaderLocation(pbrMat->pbrShader, "useTexEmissive"); + + SetShaderValue(pbrMat->pbrShader,pbrMat->albedoLoc,pbrMat->albedo,SHADER_UNIFORM_VEC4); + SetShaderValue(pbrMat->pbrShader, pbrMat->emissiveColorLoc, pbrMat->emissive, SHADER_UNIFORM_VEC4); + SetShaderValue(pbrMat->pbrShader, pbrMat->emissivePowerLoc, &pbrMat->emissivePower, SHADER_UNIFORM_FLOAT); + SetShaderValue(pbrMat->pbrShader,pbrMat->metallicLoc,&pbrMat->metallic,SHADER_UNIFORM_FLOAT); + SetShaderValue(pbrMat->pbrShader,pbrMat->roughnessLoc,&pbrMat->roughness,SHADER_UNIFORM_FLOAT); + SetShaderValue(pbrMat->pbrShader,pbrMat->aoLoc,&pbrMat->ao,SHADER_UNIFORM_FLOAT); + SetShaderValue(pbrMat->pbrShader,pbrMat->normalLoc,&pbrMat->normal,SHADER_UNIFORM_FLOAT); + SetShaderValue(pbrMat->pbrShader,pbrMat->texTilingLoc,pbrMat->texTiling,SHADER_UNIFORM_VEC2); + SetShaderValue(pbrMat->pbrShader,pbrMat->texOffsetLoc,pbrMat->texOffset,SHADER_UNIFORM_VEC2); +} + +void PBRLoadTextures(PBRMaterial *pbrMat,PBRTexType pbrTexType,const char *fileName){ + if(pbrMat == NULL) return; + switch(pbrTexType){ + case PBR_TEXTURE_ALBEDO: + pbrMat->texAlbedo = LoadTexture(fileName); + pbrMat->useTexAlbedo = 1; + break; + case PBR_TEXTURE_MRA: + pbrMat->texMRA = LoadTexture(fileName); + pbrMat->useTexMRA = 1; + break; + case PBR_TEXTURE_NORMAL: + pbrMat->texNormal = LoadTexture(fileName); + pbrMat->useTexNormal = 1; + break; + case PBR_TEXTURE_EMISSIVE: + pbrMat->texEmissive = LoadTexture(fileName); + pbrMat->useTexEmissive = 1; + break; + } +} + +void UnloadPBRMaterial(PBRMaterial pbrMat){ + if(pbrMat.useTexAlbedo == 1) UnloadTexture(pbrMat.texAlbedo); + if(pbrMat.useTexNormal == 1) UnloadTexture(pbrMat.texNormal); + if(pbrMat.useTexMRA == 1) UnloadTexture(pbrMat.texMRA); + if(pbrMat.useTexEmissive == 1) UnloadTexture(pbrMat.texEmissive); +} + +void PBRSetColor(PBRMaterial *pbrMat,PBRColorType pbrColorType,Color color){ + if(pbrMat == NULL) return; + switch(pbrColorType){ + case PBR_COLOR_ALBEDO: + pbrMat->albedo[0] = (float) color.r / 255; + pbrMat->albedo[1] = (float) color.g / 255; + pbrMat->albedo[2] = (float) color.b / 255; + pbrMat->albedo[3] = (float) color.a / 255; + SetShaderValue(pbrMat->pbrShader,pbrMat->albedoLoc,pbrMat->albedo,SHADER_UNIFORM_VEC4); + break; + case PBR_COLOR_EMISSIVE: + pbrMat->emissive[0] = (float) color.r / 255; + pbrMat->emissive[1] = (float) color.g / 255; + pbrMat->emissive[2] = (float) color.b / 255; + pbrMat->emissive[3] = (float) color.a / 255; + SetShaderValue(pbrMat->pbrShader, pbrMat->emissiveColorLoc, pbrMat->emissive, SHADER_UNIFORM_VEC4); + break; + } +} + +void PBRSetFloat(PBRMaterial *pbrMat, PBRFloatType pbrParamType, float value){ + if(pbrMat == NULL) return; + switch(pbrParamType){ + case PBR_PARAM_METALLIC: + pbrMat->metallic = value; + SetShaderValue(pbrMat->pbrShader,pbrMat->metallicLoc,&pbrMat->metallic,SHADER_UNIFORM_FLOAT); + break; + case PBR_PARAM_ROUGHNESS: + pbrMat->roughness = value; + SetShaderValue(pbrMat->pbrShader,pbrMat->roughnessLoc,&pbrMat->roughness,SHADER_UNIFORM_FLOAT); + break; + case PBR_PARAM_NORMAL: + pbrMat->normal = value; + SetShaderValue(pbrMat->pbrShader,pbrMat->normalLoc,&pbrMat->normal,SHADER_UNIFORM_FLOAT); + break; + case PBR_PARAM_AO: + pbrMat->ao = value; + SetShaderValue(pbrMat->pbrShader,pbrMat->aoLoc,&pbrMat->ao,SHADER_UNIFORM_FLOAT); + break; + case PBR_PARAM_EMISSIVE: + pbrMat->emissivePower = value; + SetShaderValue(pbrMat->pbrShader,pbrMat->emissivePowerLoc,&pbrMat->emissivePower,SHADER_UNIFORM_FLOAT); + break; + } +} + + +void PBRSetVec2(PBRMaterial *pbrMat,PBRVec2Type type,Vector2 value){ + switch(type){ + case PBR_VEC2_TILING: + pbrMat->texTiling[0] = value.x; + pbrMat->texTiling[1] = value.y; + SetShaderValue(pbrMat->pbrShader,pbrMat->texTilingLoc,&pbrMat->texTiling,SHADER_UNIFORM_VEC2); + break; + case PBR_VEC2_OFFSET: + pbrMat->texOffset[0] = value.x; + pbrMat->texOffset[1] = value.y; + SetShaderValue(pbrMat->pbrShader,pbrMat->texOffsetLoc,&pbrMat->texOffset,SHADER_UNIFORM_VEC2); + break; + } +} + +void PBRSetMaterial(PBRModel* model,PBRMaterial* pbrMat,int matIndex){ + + + model->pbrMat = *pbrMat; + model->model.materials[matIndex].shader = model->pbrMat.pbrShader; + pbrMat->pbrShader.locs[SHADER_LOC_MAP_MRA] = GetShaderLocation(pbrMat->pbrShader, "mraMap"); + pbrMat->pbrShader.locs[SHADER_LOC_MAP_EMISSIVE] = GetShaderLocation(pbrMat->pbrShader, "emissiveMap"); + pbrMat->pbrShader.locs[SHADER_LOC_MAP_NORMAL] = GetShaderLocation(pbrMat->pbrShader, "normalMap"); + + if(pbrMat->useTexAlbedo) { + model->model.materials[matIndex].maps[MATERIAL_MAP_ALBEDO].texture = pbrMat->texAlbedo; + } + if(pbrMat->useTexMRA) { + model->model.materials[matIndex].maps[MATERIAL_MAP_MRA].texture = pbrMat->texMRA; + } + if(pbrMat->useTexNormal) { + model->model.materials[matIndex].maps[MATERIAL_MAP_NORMAL].texture = pbrMat->texNormal; + } + if(pbrMat->useTexEmissive) { + model->model.materials[matIndex].maps[MATERIAL_MAP_EMISSIVE].texture = pbrMat->texEmissive; + } + + SetShaderValue(pbrMat->pbrShader,pbrMat->useTexAlbedoLoc,&pbrMat->useTexAlbedo,SHADER_UNIFORM_INT); + SetShaderValue(pbrMat->pbrShader,pbrMat->useTexNormalLoc,&pbrMat->useTexNormal,SHADER_UNIFORM_INT); + SetShaderValue(pbrMat->pbrShader, pbrMat->useTexMRAELoc, &pbrMat->useTexMRA, SHADER_UNIFORM_INT); + SetShaderValue(pbrMat->pbrShader, pbrMat->useTexEmissiveLoc, &pbrMat->useTexEmissive, SHADER_UNIFORM_INT); +} + +void PBRDrawModel(PBRModel pbrModel, Vector3 position, float scale){ + PBRMaterial *pbrMat = &pbrModel.pbrMat; + SetShaderValue(pbrMat->pbrShader,pbrMat->albedoLoc,pbrMat->albedo,SHADER_UNIFORM_VEC4); + SetShaderValue(pbrMat->pbrShader, pbrMat->emissiveColorLoc, pbrMat->emissive, SHADER_UNIFORM_VEC4); + SetShaderValue(pbrMat->pbrShader,pbrMat->metallicLoc,&pbrMat->metallic,SHADER_UNIFORM_FLOAT); + SetShaderValue(pbrMat->pbrShader,pbrMat->roughnessLoc,&pbrMat->roughness,SHADER_UNIFORM_FLOAT); + SetShaderValue(pbrMat->pbrShader,pbrMat->aoLoc,&pbrMat->ao,SHADER_UNIFORM_FLOAT); + SetShaderValue(pbrMat->pbrShader,pbrMat->normalLoc,&pbrMat->normal,SHADER_UNIFORM_FLOAT); + SetShaderValue(pbrMat->pbrShader,pbrMat->texTilingLoc,pbrMat->texTiling,SHADER_UNIFORM_VEC2); + SetShaderValue(pbrMat->pbrShader,pbrMat->texOffsetLoc,pbrMat->texOffset,SHADER_UNIFORM_VEC2); + + SetShaderValue(pbrMat->pbrShader,pbrMat->useTexAlbedoLoc,&pbrMat->useTexAlbedo,SHADER_UNIFORM_INT); + SetShaderValue(pbrMat->pbrShader,pbrMat->useTexNormalLoc,&pbrMat->useTexNormal,SHADER_UNIFORM_INT); + SetShaderValue(pbrMat->pbrShader, pbrMat->useTexMRAELoc, &pbrMat->useTexMRA, SHADER_UNIFORM_INT); + SetShaderValue(pbrMat->pbrShader, pbrMat->useTexEmissiveLoc, &pbrMat->useTexEmissive, SHADER_UNIFORM_INT); + + DrawModel(pbrModel.model,position,scale,WHITE); +} + +PBRModel PBRModelLoad(const char *fileName){ + PBRModel pbrModel = (PBRModel){0}; + pbrModel.model = LoadModel(fileName); + return pbrModel; +} + +PBRModel PBRModelLoadFromMesh(Mesh mesh){ + PBRModel pbrModel = (PBRModel){0}; + pbrModel.model = LoadModelFromMesh(mesh); + return pbrModel; +} +#endif // RPBR_IMPLEMENTATION diff --git a/examples/shaders/shaders_basic_pbr.c b/examples/shaders/shaders_basic_pbr.c new file mode 100644 index 000000000..92a888811 --- /dev/null +++ b/examples/shaders/shaders_basic_pbr.c @@ -0,0 +1,171 @@ +/******************************************************************************************* +* +* raylib [core] example - Model Defuse Normal Shader (adapted for HTML5 platform) +* +* This example is prepared to compile for PLATFORM_WEB and PLATFORM_DESKTOP +* As you will notice, code structure is slightly different to the other examples... +* To compile it for PLATFORM_WEB just uncomment #define PLATFORM_WEB at beginning +* +* This example has been created using raylib 5.0 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Copyright (c) 2023-2024 Afan OLOVCIC (@_DevDad) 2015 Ramon Santamaria (@raysan5) +* Model: "Old Rusty Car" (https://skfb.ly/LxRy) by Renafox is licensed under Creative Commons Attribution-NonCommercial (http://creativecommons.org/licenses/by-nc/4.0/). +********************************************************************************************/ + +#include "raylib.h" + +#if defined(PLATFORM_WEB) +#include +#endif + +#define RPBR_IMPLEMENTATION +#include "rpbr.h" + +#if defined(PLATFORM_DESKTOP) +#define GLSL_VERSION 330 +#else // PLATFORM_ANDROID, PLATFORM_WEB +#define GLSL_VERSION 120 +#endif + + +//---------------------------------------------------------------------------------- +// Main Entry Point +//---------------------------------------------------------------------------------- +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + SetConfigFlags(FLAG_MSAA_4X_HINT); + InitWindow(screenWidth, screenHeight, "raylib [shaders] example - basic pbr"); + + // Define the camera to look into our 3d world + Camera camera = { 0 }; + camera.position = (Vector3){ 2.0f, 2.0f, 6.0f }; // Camera position + camera.target = (Vector3){ 0.0f, 0.5f, 0.0f }; // Camera looking at point + camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Camera up vector (rotation towards target) + camera.fovy = 45.0f; // Camera field-of-view Y + camera.projection = CAMERA_PERSPECTIVE; // Camera projection type + + + Shader shader = LoadShader(TextFormat("resources/shaders/glsl%i/pbr.vs",GLSL_VERSION), + TextFormat("resources/shaders/glsl%i/pbr.fs",GLSL_VERSION)); + + + + PBRModel model = PBRModelLoad("resources/models/old_car_new.glb"); + //if we use obj file formator if model doesn't have tangents we have to calculate MeshTangents + //by using raylib function GenMeshTangents(mesh) for example: obj file doesn't support tangents + //GenMeshTangents(&model.model.meshes[0]); + + PBRMaterial model_mat = (PBRMaterial){0}; + PBRMaterialSetup(&model_mat, shader, NULL); //environment = NULL for now + PBRLoadTextures(&model_mat, PBR_TEXTURE_ALBEDO, "resources/old_car_d.png"); + PBRLoadTextures(&model_mat, PBR_TEXTURE_MRA, "resources/old_car_mra.png"); + PBRLoadTextures(&model_mat, PBR_TEXTURE_NORMAL, "resources/old_car_n.png"); + PBRLoadTextures(&model_mat, PBR_TEXTURE_EMISSIVE, "resources/old_car_e.png"); + PBRSetColor(&model_mat,PBR_COLOR_EMISSIVE, (Color){255,162,0,255}); + PBRSetVec2(&model_mat, PBR_VEC2_TILING,(Vector2){0.5,0.5}); + PBRSetMaterial(&model,&model_mat,0); + + PBRModel floor = PBRModelLoad("resources/models/plane.glb"); + + PBRMaterial floor_mat = (PBRMaterial){0}; + PBRMaterialSetup(&floor_mat, shader, NULL); + PBRLoadTextures(&floor_mat, PBR_TEXTURE_ALBEDO, "resources/road_a.png"); + PBRLoadTextures(&floor_mat, PBR_TEXTURE_MRA, "resources/road_mra.png"); + PBRLoadTextures(&floor_mat, PBR_TEXTURE_NORMAL, "resources/road_n.png"); + PBRSetVec2(&floor_mat, PBR_VEC2_TILING,(Vector2){0.5,0.5}); + PBRSetMaterial(&floor,&floor_mat,0); + + shader.locs[SHADER_LOC_VECTOR_VIEW] = GetShaderLocation(shader, "viewPos"); + int numOfLightsLoc = GetShaderLocation(shader, "numOfLights"); + int numOfLights = 4; + SetShaderValue(shader, numOfLightsLoc, &numOfLights, SHADER_UNIFORM_INT); + + Color ambCol = (Color){26,32,135,255}; + float ambIntens = 0.02; + + int albedoLoc = GetShaderLocation(shader, "albedo"); + PBRSetAmbient(shader,ambCol,ambIntens); + + // Create lights + PBRLight lights[MAX_LIGHTS] = { 0 }; + lights[0] = PBRLightCreate(LIGHT_POINT, (Vector3){ -1, 1, -2 }, (Vector3){0,0,0}, YELLOW,4, shader); + lights[1] = PBRLightCreate(LIGHT_POINT, (Vector3){ 2, 1, 1 }, (Vector3){0,0,0}, GREEN,3.3, shader); + lights[2] = PBRLightCreate(LIGHT_POINT, (Vector3){ -2, 1, 1 }, (Vector3){0,0,0}, RED,8.3, shader); + lights[3] = PBRLightCreate(LIGHT_POINT, (Vector3){ 1, 1, -2 }, (Vector3){0,0,0}, BLUE,2, shader); + SetShaderValueV(shader, GetShaderLocation(shader, "lights"), lights, SHADER_UNIFORM_FLOAT, numOfLights); + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second------------------------------------------------------------- + + int emissiveCnt = 0; + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + UpdateCamera(&camera, CAMERA_ORBITAL); + + // Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f }) + float cameraPos[3] = {camera.position.x, camera.position.y, camera.position.z}; + SetShaderValue(shader, shader.locs[SHADER_LOC_VECTOR_VIEW], cameraPos, SHADER_UNIFORM_VEC3); + + // Check key inputs to enable/disable lights + if (IsKeyPressed(KEY_Y)) { lights[0].enabled = !lights[0].enabled; } + if (IsKeyPressed(KEY_G)) { lights[1].enabled = !lights[1].enabled; } + if (IsKeyPressed(KEY_R)) { lights[2].enabled = !lights[2].enabled; } + if (IsKeyPressed(KEY_B)) { lights[3].enabled = !lights[3].enabled; } + + // Update light values (actually, only enable/disable them) + for (int i = 0; i < MAX_LIGHTS; i++) PBRLightUpdate(shader, lights[i]); + emissiveCnt--; + if(emissiveCnt<=0){ + emissiveCnt = GetRandomValue(0,20); + PBRSetFloat(&model_mat,PBR_PARAM_EMISSIVE,(float)GetRandomValue(0,100)/100); + } + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + ClearBackground(BLACK); + BeginMode3D(camera); + + PBRDrawModel(floor, (Vector3){0,0,0}, 5.0f); + PBRDrawModel(model, (Vector3) {0, 0.0, 0}, 0.005); + + // Draw spheres to show where the lights are + for (int i = 0; i < MAX_LIGHTS; i++) { + Color col = (Color) {lights[i].color[0] * 255, lights[i].color[1] * 255, lights[i].color[2] * 255, + lights[i].color[3] * 255}; + if (lights[i].enabled) DrawSphereEx(lights[i].position, 0.2f, 8, 8, col); + else DrawSphereWires(lights[i].position, 0.2f, 8, 8, ColorAlpha(col, 0.3f)); + } + EndMode3D(); + + DrawText("(c) Old Rusty Car model by Renafox (https://skfb.ly/LxRy)", screenWidth - 320, screenHeight - 20, 10, GRAY); + DrawFPS(10, 10); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + //-------------------------------------------------------------------------------------- + // De-Initialization + //-------------------------------------------------------------------------------------- + + UnloadModel(floor.model); // Unload model + UnloadModel(model.model); // Unload model + UnloadShader(shader); // Unload Shader + UnloadPBRMaterial(floor_mat); // Unload PBRMaterial + UnloadPBRMaterial(model_mat); // Unload PBRMaterial + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; + } diff --git a/src/platforms/rcore_web.c b/src/platforms/rcore_web.c index 4d7d3b9b4..4bc863eaf 100644 --- a/src/platforms/rcore_web.c +++ b/src/platforms/rcore_web.c @@ -73,6 +73,7 @@ //---------------------------------------------------------------------------------- typedef struct { GLFWwindow *handle; // GLFW window handle (graphic device) + bool ourFullscreen; // Internal var to filter our handling of fullscreen vs the user handling of fullscreen } PlatformData; //---------------------------------------------------------------------------------- @@ -140,6 +141,37 @@ bool WindowShouldClose(void) // Toggle fullscreen mode void ToggleFullscreen(void) { + platform.ourFullscreen = true; + bool enterFullscreen = false; + + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (wasFullscreen) + { + EM_ASM(document.exitFullscreen();); + + if (CORE.Window.flags & FLAG_FULLSCREEN_MODE) enterFullscreen = false; + else enterFullscreen = true; + + CORE.Window.fullscreen = false; + CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; + } + else enterFullscreen = true; + + if (enterFullscreen) + { + // NOTE: The setTimeouts handle the browser mode change delay + EM_ASM( + setTimeout(function() + { + Module.requestFullscreen(false, false); + }, 100); + ); + CORE.Window.fullscreen = true; + CORE.Window.flags |= FLAG_FULLSCREEN_MODE; + } + + // NOTE: Old notes below: /* EM_ASM ( @@ -204,40 +236,44 @@ void ToggleFullscreen(void) CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; } */ - - CORE.Window.fullscreen = !CORE.Window.fullscreen; // Toggle fullscreen flag } // Toggle borderless windowed mode void ToggleBorderlessWindowed(void) { + platform.ourFullscreen = true; + bool enterBorderless = false; + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); if (wasFullscreen) { EM_ASM(document.exitFullscreen();); + if (CORE.Window.flags & FLAG_BORDERLESS_WINDOWED_MODE) enterBorderless = false; + else enterBorderless = true; + CORE.Window.fullscreen = false; CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; } + else enterBorderless = true; - if (!IsWindowState(FLAG_BORDERLESS_WINDOWED_MODE)) + if (enterBorderless) { // NOTE: 1. The setTimeouts handle the browser mode change delay - // 2. The style unset handles the possibility of a width="100%" like on the default shell.html file + // 2. The style unset handles the possibility of a width="value%" like on the default shell.html file EM_ASM( setTimeout(function() { - Module.requestFullscreen(true, true); + Module.requestFullscreen(false, true); setTimeout(function() { canvas.style.width="unset"; }, 100); }, 100); ); - CORE.Window.flags |= FLAG_BORDERLESS_WINDOWED_MODE; } - else CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; } // Set window state: maximized, if resizable @@ -277,9 +313,9 @@ void SetWindowState(unsigned int flags) } // State change: FLAG_FULLSCREEN_MODE - if ((flags & FLAG_FULLSCREEN_MODE) > 0) + if ((CORE.Window.flags & FLAG_FULLSCREEN_MODE) != (flags & FLAG_FULLSCREEN_MODE)) { - TRACELOG(LOG_WARNING, "SetWindowState(FLAG_FULLSCREEN_MODE) not available yet on target platform"); + ToggleFullscreen(); // NOTE: Window state flag updated inside function } // State change: FLAG_WINDOW_RESIZABLE @@ -384,9 +420,9 @@ void ClearWindowState(unsigned int flags) } // State change: FLAG_FULLSCREEN_MODE - if ((flags & FLAG_FULLSCREEN_MODE) > 0) + if (((CORE.Window.flags & FLAG_FULLSCREEN_MODE) > 0) && ((flags & FLAG_FULLSCREEN_MODE) > 0)) { - TRACELOG(LOG_WARNING, "ClearWindowState(FLAG_FULLSCREEN_MODE) not available yet on target platform"); + ToggleFullscreen(); // NOTE: Window state flag updated inside function } // State change: FLAG_WINDOW_RESIZABLE @@ -572,15 +608,19 @@ Vector2 GetMonitorPosition(int monitor) // Get selected monitor width (currently used by monitor) int GetMonitorWidth(int monitor) { - TRACELOG(LOG_WARNING, "GetMonitorWidth() not implemented on target platform"); - return 0; + // NOTE: Returned value is limited to the current monitor where the browser window is located + int width = 0; + width = EM_ASM_INT( { return screen.width; }, 0); + return width; } // Get selected monitor height (currently used by monitor) int GetMonitorHeight(int monitor) { - TRACELOG(LOG_WARNING, "GetMonitorHeight() not implemented on target platform"); - return 0; + // NOTE: Returned value is limited to the current monitor where the browser window is located + int height = 0; + height = EM_ASM_INT( { return screen.height; }, 0); + return height; } // Get selected monitor physical width in millimetres @@ -614,8 +654,11 @@ const char *GetMonitorName(int monitor) // Get window position XY on monitor Vector2 GetWindowPosition(void) { - TRACELOG(LOG_WARNING, "GetWindowPosition() not implemented on target platform"); - return (Vector2){ 0, 0 }; + // NOTE: Returned position is relative to the current monitor where the browser window is located + Vector2 position = { 0, 0 }; + position.x = (float)EM_ASM_INT( { return window.screenX; }, 0); + position.y = (float)EM_ASM_INT( { return window.screenY; }, 0); + return position; } // Get window scale DPI factor for current monitor @@ -944,8 +987,13 @@ int InitPlatform(void) if ((CORE.Window.flags & FLAG_WINDOW_TOPMOST) > 0) glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); else glfwWindowHint(GLFW_FLOATING, GLFW_FALSE); - // NOTE: Some GLFW flags are not supported on HTML5 - // e.g.: GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_SCALE_TO_MONITOR, GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_MOUSE_PASSTHROUGH + // NOTE: Some GLFW flags are not supported on HTML5 + // e.g.: GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_MOUSE_PASSTHROUGH + + // Scale content area based on the monitor content scale where window is placed on + // NOTE: This feature requires emscripten 3.1.51 + //if ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0) glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_TRUE); + //else glfwWindowHint(GLFW_SCALE_TO_MONITOR, GLFW_FALSE); if (CORE.Window.flags & FLAG_MSAA_4X_HINT) { @@ -1003,6 +1051,9 @@ int InitPlatform(void) CORE.Window.display.width = CORE.Window.screen.width; CORE.Window.display.height = CORE.Window.screen.height; + // Init fullscreen toggle required var: + platform.ourFullscreen = false; + if (CORE.Window.fullscreen) { // remember center for switchinging from fullscreen to window @@ -1136,7 +1187,7 @@ int InitPlatform(void) // Initialize input events callbacks //---------------------------------------------------------------------------- // Setup callback functions for the DOM events - emscripten_set_fullscreenchange_callback("#canvas", NULL, 1, EmscriptenFullscreenChangeCallback); + emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenFullscreenChangeCallback); // WARNING: Below resize code was breaking fullscreen mode for sample games and examples, it needs review // Check fullscreen change events(note this is done on the window since most browsers don't support this on #canvas) @@ -1400,7 +1451,19 @@ static void CursorEnterCallback(GLFWwindow *window, int enter) // Register fullscreen change events static EM_BOOL EmscriptenFullscreenChangeCallback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) { - // TODO: Implement EmscriptenFullscreenChangeCallback()? + // NOTE: 1. Reset the fullscreen flags if the user left fullscreen manually by pressing the Escape key + // 2. Which is a necessary safeguard because that case will bypass the toggles CORE.Window.flags resets + if (platform.ourFullscreen) platform.ourFullscreen = false; + else + { + const bool wasFullscreen = EM_ASM_INT( { if (document.fullscreenElement) return 1; }, 0); + if (!wasFullscreen) + { + CORE.Window.fullscreen = false; + CORE.Window.flags &= ~FLAG_FULLSCREEN_MODE; + CORE.Window.flags &= ~FLAG_BORDERLESS_WINDOWED_MODE; + } + } return 1; // The event was consumed by the callback handler } diff --git a/src/raylib.h b/src/raylib.h index fd870c188..206b26573 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -484,7 +484,6 @@ typedef struct VrDeviceInfo { int vResolution; // Vertical resolution in pixels float hScreenSize; // Horizontal size in meters float vScreenSize; // Vertical size in meters - float vScreenCenter; // Screen center in meters float eyeToScreenDistance; // Distance between eye and display in meters float lensSeparationDistance; // Lens separation distance in meters float interpupillaryDistance; // IPD (distance between pupils) in meters @@ -1481,6 +1480,7 @@ RLAPI const char *TextToUpper(const char *text); // Get upp RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) +RLAPI float TextToFloat(const char *text); // Get float value from text (negative values not supported) //------------------------------------------------------------------------------------ // Basic 3d Shapes Drawing Functions (Module: models) diff --git a/src/rcore.c b/src/rcore.c index 336b8a2a6..dce5b8dc9 100644 --- a/src/rcore.c +++ b/src/rcore.c @@ -1194,8 +1194,8 @@ VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device) // NOTE: Camera movement might seem more natural if we model the head. // Our axis of rotation is the base of our head, so we might want to add // some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions. - config.viewOffset[0] = MatrixTranslate(-device.interpupillaryDistance*0.5f, 0.075f, 0.045f); - config.viewOffset[1] = MatrixTranslate(device.interpupillaryDistance*0.5f, 0.075f, 0.045f); + config.viewOffset[0] = MatrixTranslate(device.interpupillaryDistance*0.5f, 0.075f, 0.045f); + config.viewOffset[1] = MatrixTranslate(-device.interpupillaryDistance*0.5f, 0.075f, 0.045f); // Compute eyes Viewports /* diff --git a/src/rshapes.c b/src/rshapes.c index 4b3f2cfd0..b8c17e702 100644 --- a/src/rshapes.c +++ b/src/rshapes.c @@ -1546,6 +1546,92 @@ void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, fl // Draw spline: linear, minimum 2 points void DrawSplineLinear(Vector2 *points, int pointCount, float thick, Color color) { + if (pointCount < 2) return; + +#if defined(SUPPORT_SPLINE_MITERS) + Vector2 prevNormal = (Vector2){-(points[1].y - points[0].y), (points[1].x - points[0].x)}; + float prevLength = sqrtf(prevNormal.x*prevNormal.x + prevNormal.y*prevNormal.y); + + if (prevLength > 0.0f) + { + prevNormal.x /= prevLength; + prevNormal.y /= prevLength; + } + else + { + prevNormal.x = 0.0f; + prevNormal.y = 0.0f; + } + + Vector2 prevRadius = { 0.5f*thick*prevNormal.x, 0.5f*thick*prevNormal.y }; + + for (int i = 0; i < pointCount - 1; i++) + { + Vector2 normal = { 0 }; + + if (i < pointCount - 2) + { + normal = (Vector2){-(points[i + 2].y - points[i + 1].y), (points[i + 2].x - points[i + 1].x)}; + float normalLength = sqrtf(normal.x*normal.x + normal.y*normal.y); + + if (normalLength > 0.0f) + { + normal.x /= normalLength; + normal.y /= normalLength; + } + else + { + normal.x = 0.0f; + normal.y = 0.0f; + } + } + else + { + normal = prevNormal; + } + + Vector2 radius = { prevNormal.x + normal.x, prevNormal.y + normal.y }; + float radiusLength = sqrtf(radius.x*radius.x + radius.y*radius.y); + + if (radiusLength > 0.0f) + { + radius.x /= radiusLength; + radius.y /= radiusLength; + } + else + { + radius.x = 0.0f; + radius.y = 0.0f; + } + + float cosTheta = radius.x*normal.x + radius.y*normal.y; + + if (cosTheta != 0.0f) + { + radius.x *= (thick*0.5f/cosTheta); + radius.y *= (thick*0.5f/cosTheta); + } + else + { + radius.x = 0.0f; + radius.y = 0.0f; + } + + Vector2 strip[4] = { + { points[i].x - prevRadius.x, points[i].y - prevRadius.y }, + { points[i].x + prevRadius.x, points[i].y + prevRadius.y }, + { points[i + 1].x - radius.x, points[i + 1].y - radius.y }, + { points[i + 1].x + radius.x, points[i + 1].y + radius.y } + }; + + DrawTriangleStrip(strip, 4, color); + + prevRadius = radius; + prevNormal = normal; + } + +#else // !SUPPORT_SPLINE_MITTERS + Vector2 delta = { 0 }; float length = 0.0f; float scale = 0.0f; @@ -1567,8 +1653,10 @@ void DrawSplineLinear(Vector2 *points, int pointCount, float thick, Color color) DrawTriangleStrip(strip, 4, color); } +#endif + #if defined(SUPPORT_SPLINE_SEGMENT_CAPS) - + // TODO: Add spline segment rounded caps at the begin/end of the spline #endif } diff --git a/src/rtext.c b/src/rtext.c index 1ab45277a..2a6aa1273 100644 --- a/src/rtext.c +++ b/src/rtext.c @@ -1421,6 +1421,28 @@ int TextToInteger(const char *text) return value*sign; } +float TextToFloat(const char *text) +{ + float value = 0.0f; + float sign = 1.0f; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1; + text++; + } + int i = 0; + for (; ((text[i] >= '0') && (text[i] <= '9')); ++i) value = value*10.0f + (float)(text[i] - '0'); + if (text[i++] != '.') return value*sign; + float divisor = 10.0f; + for (; ((text[i] >= '0') && (text[i] <= '9')); ++i) + { + value += ((float)(text[i] - '0'))/divisor; + divisor = divisor*10.0f; + } + return value; +} + #if defined(SUPPORT_TEXT_MANIPULATION) // Copy one string to another, returns bytes copied int TextCopy(char *dst, const char *src) @@ -2029,7 +2051,8 @@ static int GetLine(const char *origin, char *buffer, int maxLength) // REQUIRES: strstr(), sscanf(), strrchr(), memcpy() static Font LoadBMFont(const char *fileName) { - #define MAX_BUFFER_SIZE 256 + #define MAX_BUFFER_SIZE 256 + #define MAX_FONT_IMAGE_PAGES 8 Font font = { 0 }; @@ -2041,7 +2064,8 @@ static Font LoadBMFont(const char *fileName) int imWidth = 0; int imHeight = 0; - char imFileName[129] = { 0 }; + int pageCount = 1; + char imFileName[MAX_FONT_IMAGE_PAGES][129] = { 0 }; int base = 0; // Useless data int readBytes = 0; // Data bytes read @@ -2060,17 +2084,26 @@ static Font LoadBMFont(const char *fileName) // Read line data readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); searchPoint = strstr(buffer, "lineHeight"); - readVars = sscanf(searchPoint, "lineHeight=%i base=%i scaleW=%i scaleH=%i", &fontSize, &base, &imWidth, &imHeight); + readVars = sscanf(searchPoint, "lineHeight=%i base=%i scaleW=%i scaleH=%i pages=%i", &fontSize, &base, &imWidth, &imHeight, &pageCount); fileTextPtr += (readBytes + 1); if (readVars < 4) { UnloadFileText(fileText); return font; } // Some data not available, file malformed + + if (pageCount > MAX_FONT_IMAGE_PAGES) + { + TRACELOG(LOG_WARNING, "FONT: [%s] Font defines more pages than supported: %i/%i", fileName, pageCount, MAX_FONT_IMAGE_PAGES); + pageCount = MAX_FONT_IMAGE_PAGES; + } - readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); - searchPoint = strstr(buffer, "file"); - readVars = sscanf(searchPoint, "file=\"%128[^\"]\"", imFileName); - fileTextPtr += (readBytes + 1); + for (int i = 0; i < pageCount; i++) + { + readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); + searchPoint = strstr(buffer, "file"); + readVars = sscanf(searchPoint, "file=\"%128[^\"]\"", imFileName[i]); + fileTextPtr += (readBytes + 1); - if (readVars < 1) { UnloadFileText(fileText); return font; } // No fileName read + if (readVars < 1) { UnloadFileText(fileText); return font; } // No fileName read + } readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); searchPoint = strstr(buffer, "count"); @@ -2079,50 +2112,56 @@ static Font LoadBMFont(const char *fileName) if (readVars < 1) { UnloadFileText(fileText); return font; } // No glyphCount read - // Compose correct path using route of .fnt file (fileName) and imFileName - char *imPath = NULL; - char *lastSlash = NULL; + // Load all required images for further compose + Image *imFonts = (Image *)RL_CALLOC(pageCount, sizeof(Image)); // Font atlases, multiple images - lastSlash = strrchr(fileName, '/'); - if (lastSlash == NULL) lastSlash = strrchr(fileName, '\\'); - - if (lastSlash != NULL) + for (int i = 0; i < pageCount; i++) { - // NOTE: We need some extra space to avoid memory corruption on next allocations! - imPath = (char *)RL_CALLOC(TextLength(fileName) - TextLength(lastSlash) + TextLength(imFileName) + 4, 1); - memcpy(imPath, fileName, TextLength(fileName) - TextLength(lastSlash) + 1); - memcpy(imPath + TextLength(fileName) - TextLength(lastSlash) + 1, imFileName, TextLength(imFileName)); - } - else imPath = imFileName; + imFonts[i] = LoadImage(TextFormat("%s/%s", GetDirectoryPath(fileName), imFileName[i])); - TRACELOGD(" > Image loading path: %s", imPath); - - Image imFont = LoadImage(imPath); - - if (imFont.format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) - { - // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel - Image imFontAlpha = { - .data = RL_CALLOC(imFont.width*imFont.height, 2), - .width = imFont.width, - .height = imFont.height, - .mipmaps = 1, - .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA - }; - - for (int p = 0, i = 0; p < (imFont.width*imFont.height*2); p += 2, i++) + if (imFonts[i].format == PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) { - ((unsigned char *)(imFontAlpha.data))[p] = 0xff; - ((unsigned char *)(imFontAlpha.data))[p + 1] = ((unsigned char *)imFont.data)[i]; - } + // Convert image to GRAYSCALE + ALPHA, using the mask as the alpha channel + Image imFontAlpha = { + .data = RL_CALLOC(imFonts[i].width*imFonts[i].height, 2), + .width = imFonts[i].width, + .height = imFonts[i].height, + .mipmaps = 1, + .format = PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA + }; - UnloadImage(imFont); - imFont = imFontAlpha; + for (int p = 0, pi = 0; p < (imFonts[i].width*imFonts[i].height*2); p += 2, pi++) + { + ((unsigned char *)(imFontAlpha.data))[p] = 0xff; + ((unsigned char *)(imFontAlpha.data))[p + 1] = ((unsigned char *)imFonts[i].data)[pi]; + } + + UnloadImage(imFonts[i]); + imFonts[i] = imFontAlpha; + } } - font.texture = LoadTextureFromImage(imFont); + Image fullFont = imFonts[0]; + for (int i = 1; i < pageCount; i++) UnloadImage(imFonts[i]); - if (lastSlash != NULL) RL_FREE(imPath); + // If multiple atlas, then merge atlas + // NOTE: WARNING: This process could be really slow! + if (pageCount > 1) + { + // Resize font atlas to draw additional images + ImageResizeCanvas(&fullFont, imWidth, imHeight*pageCount, 0, 0, BLACK); + + for (int i = 1; i < pageCount; i++) + { + Rectangle srcRec = { 0.0f, 0.0f, (float)imWidth, (float)imHeight }; + Rectangle destRec = { 0.0f, (float)imHeight*(float)i, (float)imWidth, (float)imHeight }; + ImageDraw(&fullFont, imFonts[i], srcRec, destRec, WHITE); + } + } + + RL_FREE(imFonts); + + font.texture = LoadTextureFromImage(fullFont); // Fill font characters info data font.baseSize = fontSize; @@ -2131,19 +2170,19 @@ static Font LoadBMFont(const char *fileName) font.glyphs = (GlyphInfo *)RL_MALLOC(glyphCount*sizeof(GlyphInfo)); font.recs = (Rectangle *)RL_MALLOC(glyphCount*sizeof(Rectangle)); - int charId, charX, charY, charWidth, charHeight, charOffsetX, charOffsetY, charAdvanceX; + int charId, charX, charY, charWidth, charHeight, charOffsetX, charOffsetY, charAdvanceX, pageID; for (int i = 0; i < glyphCount; i++) { readBytes = GetLine(fileTextPtr, buffer, MAX_BUFFER_SIZE); - readVars = sscanf(buffer, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i", - &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX); + readVars = sscanf(buffer, "char id=%i x=%i y=%i width=%i height=%i xoffset=%i yoffset=%i xadvance=%i page=%i", + &charId, &charX, &charY, &charWidth, &charHeight, &charOffsetX, &charOffsetY, &charAdvanceX, &pageID); fileTextPtr += (readBytes + 1); - if (readVars == 8) // Make sure all char data has been properly read + if (readVars == 9) // Make sure all char data has been properly read { // Get character rectangle in the font atlas texture - font.recs[i] = (Rectangle){ (float)charX, (float)charY, (float)charWidth, (float)charHeight }; + font.recs[i] = (Rectangle){ (float)charX, (float)charY + (float)imHeight*pageID, (float)charWidth, (float)charHeight }; // Save data properly in sprite font font.glyphs[i].value = charId; @@ -2151,13 +2190,13 @@ static Font LoadBMFont(const char *fileName) font.glyphs[i].offsetY = charOffsetY; font.glyphs[i].advanceX = charAdvanceX; - // Fill character image data from imFont data - font.glyphs[i].image = ImageFromImage(imFont, font.recs[i]); + // Fill character image data from full font data + font.glyphs[i].image = ImageFromImage(fullFont, font.recs[i]); } else TRACELOG(LOG_WARNING, "FONT: [%s] Some characters data not correctly provided", fileName); } - UnloadImage(imFont); + UnloadImage(fullFont); UnloadFileText(fileText); if (font.texture.id == 0) @@ -2170,6 +2209,7 @@ static Font LoadBMFont(const char *fileName) return font; } + #endif #endif // SUPPORT_MODULE_RTEXT diff --git a/src/rtextures.c b/src/rtextures.c index c78b0ebf4..d42304c7d 100644 --- a/src/rtextures.c +++ b/src/rtextures.c @@ -213,14 +213,14 @@ #define STBIR_MALLOC(size,c) ((void)(c), RL_MALLOC(size)) #define STBIR_FREE(ptr,c) ((void)(c), RL_FREE(ptr)) #define STB_IMAGE_RESIZE_IMPLEMENTATION -#include "external/stb_image_resize2.h" // Required for: stbir_resize_uint8_linear() [ImageResize()] +#include "external/stb_image_resize2.h" // Required for: stbir_resize_uint8_linear() [ImageResize()] #if defined(SUPPORT_FILEFORMAT_SVG) - #define NANOSVG_IMPLEMENTATION // Expands implementation - #include "external/nanosvg.h" + #define NANOSVG_IMPLEMENTATION // Expands implementation + #include "external/nanosvg.h" - #define NANOSVGRAST_IMPLEMENTATION - #include "external/nanosvgrast.h" + #define NANOSVGRAST_IMPLEMENTATION + #include "external/nanosvgrast.h" #endif //---------------------------------------------------------------------------------- @@ -1959,29 +1959,24 @@ void ImageBlurGaussian(Image *image, int blurSize) { float avgG = 0.0f; float avgB = 0.0f; float avgAlpha = 0.0f; - int convolutionSize = blurSize+1; + int convolutionSize = blurSize; - for (int i = 0; i < blurSize+1; i++) + for (int i = 0; i < blurSize; i++) { avgR += pixelsCopy1[row*image->width + i].x; avgG += pixelsCopy1[row*image->width + i].y; avgB += pixelsCopy1[row*image->width + i].z; avgAlpha += pixelsCopy1[row*image->width + i].w; - } + } - pixelsCopy2[row*image->width].x = avgR/convolutionSize; - pixelsCopy2[row*image->width].y = avgG/convolutionSize; - pixelsCopy2[row*image->width].z = avgB/convolutionSize; - pixelsCopy2[row*image->width].w = avgAlpha/convolutionSize; - - for (int x = 1; x < image->width; x++) + for (int x = 0; x < image->width; x++) { - if (x-blurSize >= 0) + if (x-blurSize-1 >= 0) { - avgR -= pixelsCopy1[row*image->width + x-blurSize].x; - avgG -= pixelsCopy1[row*image->width + x-blurSize].y; - avgB -= pixelsCopy1[row*image->width + x-blurSize].z; - avgAlpha -= pixelsCopy1[row*image->width + x-blurSize].w; + avgR -= pixelsCopy1[row*image->width + x-blurSize-1].x; + avgG -= pixelsCopy1[row*image->width + x-blurSize-1].y; + avgB -= pixelsCopy1[row*image->width + x-blurSize-1].z; + avgAlpha -= pixelsCopy1[row*image->width + x-blurSize-1].w; convolutionSize--; } @@ -1999,7 +1994,7 @@ void ImageBlurGaussian(Image *image, int blurSize) { pixelsCopy2[row*image->width + x].z = avgB/convolutionSize; pixelsCopy2[row*image->width + x].w = avgAlpha/convolutionSize; } - } + } // Vertical motion blur for (int col = 0; col < image->width; col++) @@ -2008,9 +2003,9 @@ void ImageBlurGaussian(Image *image, int blurSize) { float avgG = 0.0f; float avgB = 0.0f; float avgAlpha = 0.0f; - int convolutionSize = blurSize+1; + int convolutionSize = blurSize; - for (int i = 0; i < blurSize+1; i++) + for (int i = 0; i < blurSize; i++) { avgR += pixelsCopy2[i*image->width + col].x; avgG += pixelsCopy2[i*image->width + col].y; @@ -2018,19 +2013,14 @@ void ImageBlurGaussian(Image *image, int blurSize) { avgAlpha += pixelsCopy2[i*image->width + col].w; } - pixelsCopy1[col].x = (unsigned char) (avgR/convolutionSize); - pixelsCopy1[col].y = (unsigned char) (avgG/convolutionSize); - pixelsCopy1[col].z = (unsigned char) (avgB/convolutionSize); - pixelsCopy1[col].w = (unsigned char) (avgAlpha/convolutionSize); - - for (int y = 1; y < image->height; y++) + for (int y = 0; y < image->height; y++) { - if (y-blurSize >= 0) + if (y-blurSize-1 >= 0) { - avgR -= pixelsCopy2[(y-blurSize)*image->width + col].x; - avgG -= pixelsCopy2[(y-blurSize)*image->width + col].y; - avgB -= pixelsCopy2[(y-blurSize)*image->width + col].z; - avgAlpha -= pixelsCopy2[(y-blurSize)*image->width + col].w; + avgR -= pixelsCopy2[(y-blurSize-1)*image->width + col].x; + avgG -= pixelsCopy2[(y-blurSize-1)*image->width + col].y; + avgB -= pixelsCopy2[(y-blurSize-1)*image->width + col].z; + avgAlpha -= pixelsCopy2[(y-blurSize-1)*image->width + col].w; convolutionSize--; } if (y+blurSize < image->height)