commit
44c2df3c12
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -49,6 +49,7 @@ ipch/
|
|||
# Ignore compiled binaries
|
||||
*.o
|
||||
*.exe
|
||||
*.a
|
||||
!raylib.rc.o
|
||||
|
||||
# Ignore all examples files
|
||||
|
|
|
|||
63
src/core.c
63
src/core.c
|
|
@ -707,7 +707,7 @@ bool WindowShouldClose(void)
|
|||
{
|
||||
#if defined(PLATFORM_WEB)
|
||||
// Emterpreter-Async required to run sync code
|
||||
// https://github.com/kripken/emscripten/wiki/Emterpreter#emterpreter-async-run-synchronous-code
|
||||
// https://github.com/emscripten-core/emscripten/wiki/Emterpreter#emterpreter-async-run-synchronous-code
|
||||
// By default, this function is never called on a web-ready raylib example because we encapsulate
|
||||
// frame code in a UpdateDrawFrame() function, to allow browser manage execution asynchronously
|
||||
// but now emscripten allows sync code to be executed in an interpreted way, using emterpreter!
|
||||
|
|
@ -871,7 +871,7 @@ void *GetWindowHandle(void)
|
|||
{
|
||||
#if defined(_WIN32)
|
||||
// NOTE: Returned handle is: void *HWND (windows.h)
|
||||
return glfwGetWin32Window(window);
|
||||
return glfwGetWin32Window(window);
|
||||
#elif defined(__linux__)
|
||||
// NOTE: Returned handle is: unsigned long Window (X.h)
|
||||
// typedef unsigned long XID;
|
||||
|
|
@ -1228,7 +1228,7 @@ void BeginTextureMode(RenderTexture2D target)
|
|||
rlLoadIdentity(); // Reset current matrix (MODELVIEW)
|
||||
|
||||
//rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?)
|
||||
|
||||
|
||||
// Setup current width/height for proper aspect ratio
|
||||
// calculation when using BeginMode3D()
|
||||
currentWidth = target.texture.width;
|
||||
|
|
@ -1254,6 +1254,10 @@ void EndTextureMode(void)
|
|||
|
||||
rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix
|
||||
rlLoadIdentity(); // Reset current matrix (MODELVIEW)
|
||||
|
||||
// Reset current screen size
|
||||
currentWidth = GetScreenWidth();
|
||||
currentHeight = GetScreenHeight();
|
||||
}
|
||||
|
||||
// Returns a ray trace from mouse position
|
||||
|
|
@ -1426,11 +1430,11 @@ Vector3 ColorToHSV(Color color)
|
|||
Vector3 hsv = { 0.0f, 0.0f, 0.0f };
|
||||
float min, max, delta;
|
||||
|
||||
min = rgb.x < rgb.y ? rgb.x : rgb.y;
|
||||
min = min < rgb.z ? min : rgb.z;
|
||||
min = rgb.x < rgb.y? rgb.x : rgb.y;
|
||||
min = min < rgb.z? min : rgb.z;
|
||||
|
||||
max = rgb.x > rgb.y ? rgb.x : rgb.y;
|
||||
max = max > rgb.z ? max : rgb.z;
|
||||
max = rgb.x > rgb.y? rgb.x : rgb.y;
|
||||
max = max > rgb.z? max : rgb.z;
|
||||
|
||||
hsv.z = max; // Value
|
||||
delta = max - min;
|
||||
|
|
@ -1481,25 +1485,25 @@ Color ColorFromHSV(Vector3 hsv)
|
|||
// Red channel
|
||||
float k = fmod((5.0f + h/60.0f), 6);
|
||||
float t = 4.0f - k;
|
||||
k = (t < k) ? t : k;
|
||||
k = (k < 1) ? k : 1;
|
||||
k = (k > 0) ? k : 0;
|
||||
k = (t < k)? t : k;
|
||||
k = (k < 1)? k : 1;
|
||||
k = (k > 0)? k : 0;
|
||||
color.r = (v - v*s*k)*255;
|
||||
|
||||
// Green channel
|
||||
k = fmod((3.0f + h/60.0f), 6);
|
||||
t = 4.0f - k;
|
||||
k = (t < k) ? t : k;
|
||||
k = (k < 1) ? k : 1;
|
||||
k = (k > 0) ? k : 0;
|
||||
k = (t < k)? t : k;
|
||||
k = (k < 1)? k : 1;
|
||||
k = (k > 0)? k : 0;
|
||||
color.g = (v - v*s*k)*255;
|
||||
|
||||
// Blue channel
|
||||
k = fmod((1.0f + h/60.0f), 6);
|
||||
t = 4.0f - k;
|
||||
k = (t < k) ? t : k;
|
||||
k = (k < 1) ? k : 1;
|
||||
k = (k > 0) ? k : 0;
|
||||
k = (t < k)? t : k;
|
||||
k = (k < 1)? k : 1;
|
||||
k = (k > 0)? k : 0;
|
||||
color.b = (v - v*s*k)*255;
|
||||
|
||||
return color;
|
||||
|
|
@ -1673,7 +1677,7 @@ const char *GetFileNameWithoutExt(const char *filePath)
|
|||
|
||||
// NOTE: strrchr() returns a pointer to the last occurrence of character
|
||||
lastDot = strrchr(result, nameDot);
|
||||
lastSep = (pathSep == 0) ? NULL : strrchr(result, pathSep);
|
||||
lastSep = (pathSep == 0)? NULL : strrchr(result, pathSep);
|
||||
|
||||
if (lastDot != NULL) // Check if it has an extension separator...
|
||||
{
|
||||
|
|
@ -1913,14 +1917,13 @@ void OpenURL(const char *url)
|
|||
char *cmd = (char *)calloc(strlen(url) + 10, sizeof(char));
|
||||
|
||||
#if defined(_WIN32)
|
||||
sprintf(cmd, "explorer '%s'", url);
|
||||
sprintf(cmd, "explorer %s", url);
|
||||
#elif defined(__linux__)
|
||||
sprintf(cmd, "xdg-open '%s'", url); // Alternatives: firefox, x-www-browser
|
||||
#elif defined(__APPLE__)
|
||||
sprintf(cmd, "open '%s'", url);
|
||||
#endif
|
||||
system(cmd);
|
||||
|
||||
free(cmd);
|
||||
}
|
||||
}
|
||||
|
|
@ -2210,7 +2213,7 @@ void SetMouseOffset(int offsetX, int offsetY)
|
|||
// NOTE: Useful when rendering to different size targets
|
||||
void SetMouseScale(float scaleX, float scaleY)
|
||||
{
|
||||
mouseScale = (Vector2){ scaleX, scaleY };
|
||||
mouseScale = (Vector2){ scaleX, scaleY };
|
||||
}
|
||||
|
||||
// Returns mouse wheel movement Y
|
||||
|
|
@ -3188,7 +3191,7 @@ static void PollInputEvents(void)
|
|||
|
||||
// Poll Events (registered events)
|
||||
// NOTE: Activity is paused if not enabled (appEnabled)
|
||||
while ((ident = ALooper_pollAll(appEnabled ? 0 : -1, NULL, &events,(void**)&source)) >= 0)
|
||||
while ((ident = ALooper_pollAll(appEnabled? 0 : -1, NULL, &events,(void**)&source)) >= 0)
|
||||
{
|
||||
// Process this event
|
||||
if (source != NULL) source->process(androidApp, source);
|
||||
|
|
@ -3274,9 +3277,9 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i
|
|||
char path[512] = { 0 };
|
||||
#if defined(PLATFORM_ANDROID)
|
||||
strcpy(path, internalDataPath);
|
||||
strcat(path, TextFormat("/screenrec%03i.gif", screenshotCounter));
|
||||
strcat(path, TextFormat("./screenrec%03i.gif", screenshotCounter));
|
||||
#else
|
||||
strcpy(path, TextFormat("/screenrec%03i.gif", screenshotCounter));
|
||||
strcpy(path, TextFormat("./screenrec%03i.gif", screenshotCounter));
|
||||
#endif
|
||||
|
||||
// NOTE: delay represents the time between frames in the gif, if we capture a gif frame every
|
||||
|
|
@ -3768,7 +3771,7 @@ static EM_BOOL EmscriptenTouchCallback(int eventType, const EmscriptenTouchEvent
|
|||
}
|
||||
|
||||
printf("%s, numTouches: %d %s%s%s%s\n", emscripten_event_type_to_string(eventType), event->numTouches,
|
||||
event->ctrlKey ? " CTRL" : "", event->shiftKey ? " SHIFT" : "", event->altKey ? " ALT" : "", event->metaKey ? " META" : "");
|
||||
event->ctrlKey? " CTRL" : "", event->shiftKey? " SHIFT" : "", event->altKey? " ALT" : "", event->metaKey? " META" : "");
|
||||
|
||||
for (int i = 0; i < event->numTouches; ++i)
|
||||
{
|
||||
|
|
@ -3822,7 +3825,7 @@ static EM_BOOL EmscriptenGamepadCallback(int eventType, const EmscriptenGamepadE
|
|||
{
|
||||
/*
|
||||
printf("%s: timeStamp: %g, connected: %d, index: %ld, numAxes: %d, numButtons: %d, id: \"%s\", mapping: \"%s\"\n",
|
||||
eventType != 0 ? emscripten_event_type_to_string(eventType) : "Gamepad state",
|
||||
eventType != 0? emscripten_event_type_to_string(eventType) : "Gamepad state",
|
||||
gamepadEvent->timestamp, gamepadEvent->connected, gamepadEvent->index, gamepadEvent->numAxes, gamepadEvent->numButtons, gamepadEvent->id, gamepadEvent->mapping);
|
||||
|
||||
for(int i = 0; i < gamepadEvent->numAxes; ++i) printf("Axis %d: %g\n", i, gamepadEvent->axis[i]);
|
||||
|
|
@ -4186,11 +4189,11 @@ static void EventThreadSpawn(char *device)
|
|||
{
|
||||
// Looks like a interesting device
|
||||
TraceLog(LOG_INFO, "Opening input device [%s] (%s%s%s%s%s)", device,
|
||||
worker->isMouse ? "mouse " : "",
|
||||
worker->isMultitouch ? "multitouch " : "",
|
||||
worker->isTouch ? "touchscreen " : "",
|
||||
worker->isGamepad ? "gamepad " : "",
|
||||
worker->isKeyboard ? "keyboard " : "");
|
||||
worker->isMouse? "mouse " : "",
|
||||
worker->isMultitouch? "multitouch " : "",
|
||||
worker->isTouch? "touchscreen " : "",
|
||||
worker->isGamepad? "gamepad " : "",
|
||||
worker->isKeyboard? "keyboard " : "");
|
||||
|
||||
// Create a thread for this device
|
||||
int error = pthread_create(&worker->threadId, NULL, &EventThread, (void *)worker);
|
||||
|
|
|
|||
51
src/external/cgltf.h
vendored
51
src/external/cgltf.h
vendored
|
|
@ -1,6 +1,49 @@
|
|||
/**
|
||||
* cgltf - a single-file glTF 2.0 parser written in C99.
|
||||
*
|
||||
* Version: 1.0
|
||||
*
|
||||
* Website: https://github.com/jkuhlmann/cgltf
|
||||
*
|
||||
* Distributed under the MIT License, see notice at the end of this file.
|
||||
*
|
||||
* Building:
|
||||
* Include this file where you need the struct and function
|
||||
* declarations. Have exactly one source file where you define
|
||||
* `CGLTF_IMPLEMENTATION` before including this file to get the
|
||||
* function definitions.
|
||||
*
|
||||
* Reference:
|
||||
* `cgltf_result cgltf_parse(const cgltf_options*, const void*,
|
||||
* cgltf_size, cgltf_data**)` parses both glTF and GLB data. If
|
||||
* this function returns `cgltf_result_success`, you have to call
|
||||
* `cgltf_free()` on the created `cgltf_data*` variable.
|
||||
* Note that contents of external files for buffers and images are not
|
||||
* automatically loaded. You'll need to read these files yourself using
|
||||
* URIs in the `cgltf_data` structure.
|
||||
*
|
||||
* `cgltf_options` is the struct passed to `cgltf_parse()` to control
|
||||
* parts of the parsing process. You can use it to force the file type
|
||||
* and provide memory allocation callbacks. Should be zero-initialized
|
||||
* to trigger default behavior.
|
||||
*
|
||||
* `cgltf_data` is the struct allocated and filled by `cgltf_parse()`.
|
||||
* It generally mirrors the glTF format as described by the spec (see
|
||||
* https://github.com/KhronosGroup/glTF/tree/master/specification/2.0).
|
||||
*
|
||||
* `void cgltf_free(cgltf_data*)` frees the allocated `cgltf_data`
|
||||
* variable.
|
||||
*
|
||||
* `cgltf_result cgltf_load_buffers(const cgltf_options*, cgltf_data*,
|
||||
* const char*)` can be optionally called to open and read buffer
|
||||
* files using the `FILE*` APIs.
|
||||
*
|
||||
* `cgltf_result cgltf_parse_file(const cgltf_options* options, const
|
||||
* char* path, cgltf_data** out_data)` can be used to open the given
|
||||
* file using `FILE*` APIs and parse the data using `cgltf_parse()`.
|
||||
*
|
||||
* `cgltf_result cgltf_validate(cgltf_data*)` can be used to do additional
|
||||
* checks to make sure the parsed glTF data is valid.
|
||||
*/
|
||||
#ifndef CGLTF_H_INCLUDED__
|
||||
#define CGLTF_H_INCLUDED__
|
||||
|
|
@ -462,6 +505,10 @@ void cgltf_free(cgltf_data* data);
|
|||
void cgltf_node_transform_local(const cgltf_node* node, cgltf_float* out_matrix);
|
||||
void cgltf_node_transform_world(const cgltf_node* node, cgltf_float* out_matrix);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* #ifndef CGLTF_H_INCLUDED__ */
|
||||
|
||||
/*
|
||||
|
|
@ -4266,10 +4313,6 @@ static void jsmn_init(jsmn_parser *parser) {
|
|||
|
||||
#endif /* #ifdef CGLTF_IMPLEMENTATION */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/* cgltf is distributed under MIT license:
|
||||
*
|
||||
* Copyright (c) 2018 Johannes Kuhlmann
|
||||
|
|
|
|||
BIN
src/libraylib.a
BIN
src/libraylib.a
Binary file not shown.
234
src/models.c
234
src/models.c
|
|
@ -116,7 +116,7 @@ void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color)
|
|||
void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color)
|
||||
{
|
||||
if (rlCheckBufferLimit(2*36)) rlglDraw();
|
||||
|
||||
|
||||
rlPushMatrix();
|
||||
rlTranslatef(center.x, center.y, center.z);
|
||||
rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
|
||||
|
|
@ -140,7 +140,7 @@ void DrawCube(Vector3 position, float width, float height, float length, Color c
|
|||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float z = 0.0f;
|
||||
|
||||
|
||||
if (rlCheckBufferLimit(36)) rlglDraw();
|
||||
|
||||
rlPushMatrix();
|
||||
|
|
@ -221,7 +221,7 @@ void DrawCubeWires(Vector3 position, float width, float height, float length, Co
|
|||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float z = 0.0f;
|
||||
|
||||
|
||||
if (rlCheckBufferLimit(36)) rlglDraw();
|
||||
|
||||
rlPushMatrix();
|
||||
|
|
@ -626,7 +626,7 @@ Model LoadModel(const char *fileName)
|
|||
Model LoadModelFromMesh(Mesh mesh)
|
||||
{
|
||||
Model model = { 0 };
|
||||
|
||||
|
||||
model.mesh = mesh;
|
||||
model.transform = MatrixIdentity();
|
||||
model.material = LoadMaterialDefault();
|
||||
|
|
@ -655,12 +655,16 @@ Mesh LoadMesh(const char *fileName)
|
|||
TraceLog(LOG_WARNING, "[%s] Mesh fileformat not supported, it can't be loaded", fileName);
|
||||
#endif
|
||||
|
||||
#if defined(SUPPORT_MESH_GENERATION)
|
||||
if (mesh.vertexCount == 0)
|
||||
{
|
||||
TraceLog(LOG_WARNING, "Mesh could not be loaded! Let's load a cube to replace it!");
|
||||
mesh = GenMeshCube(1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
else rlLoadMesh(&mesh, false); // Upload vertex data to GPU (static mesh)
|
||||
#else
|
||||
rlLoadMesh(&mesh, false); // Upload vertex data to GPU (static mesh)
|
||||
#endif
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
|
@ -675,11 +679,11 @@ void UnloadMesh(Mesh *mesh)
|
|||
void ExportMesh(Mesh mesh, const char *fileName)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
|
||||
if (IsFileExtension(fileName, ".obj"))
|
||||
{
|
||||
FILE *objFile = fopen(fileName, "wt");
|
||||
|
||||
|
||||
fprintf(objFile, "# //////////////////////////////////////////////////////////////////////////////////\n");
|
||||
fprintf(objFile, "# // //\n");
|
||||
fprintf(objFile, "# // rMeshOBJ exporter v1.0 - Mesh exported as triangle faces and not optimized //\n");
|
||||
|
|
@ -692,33 +696,33 @@ void ExportMesh(Mesh mesh, const char *fileName)
|
|||
fprintf(objFile, "# //////////////////////////////////////////////////////////////////////////////////\n\n");
|
||||
fprintf(objFile, "# Vertex Count: %i\n", mesh.vertexCount);
|
||||
fprintf(objFile, "# Triangle Count: %i\n\n", mesh.triangleCount);
|
||||
|
||||
|
||||
fprintf(objFile, "g mesh\n");
|
||||
|
||||
|
||||
for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3)
|
||||
{
|
||||
fprintf(objFile, "v %.2f %.2f %.2f\n", mesh.vertices[v], mesh.vertices[v + 1], mesh.vertices[v + 2]);
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 2)
|
||||
{
|
||||
fprintf(objFile, "vt %.2f %.2f\n", mesh.texcoords[v], mesh.texcoords[v + 1]);
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0, v = 0; i < mesh.vertexCount; i++, v += 3)
|
||||
{
|
||||
fprintf(objFile, "vn %.2f %.2f %.2f\n", mesh.normals[v], mesh.normals[v + 1], mesh.normals[v + 2]);
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < mesh.triangleCount; i += 3)
|
||||
{
|
||||
fprintf(objFile, "f %i/%i/%i %i/%i/%i %i/%i/%i\n", i, i, i, i + 1, i + 1, i + 1, i + 2, i + 2, i + 2);
|
||||
}
|
||||
|
||||
|
||||
fprintf(objFile, "\n");
|
||||
|
||||
|
||||
fclose(objFile);
|
||||
|
||||
|
||||
success = true;
|
||||
}
|
||||
else if (IsFileExtension(fileName, ".raw")) { } // TODO: Support additional file formats to export mesh vertex data
|
||||
|
|
@ -733,7 +737,7 @@ Mesh GenMeshPoly(int sides, float radius)
|
|||
{
|
||||
Mesh mesh = { 0 };
|
||||
int vertexCount = sides*3;
|
||||
|
||||
|
||||
// Vertices definition
|
||||
Vector3 *vertices = (Vector3 *)malloc(vertexCount*sizeof(Vector3));
|
||||
for (int i = 0, v = 0; i < 360; i += 360/sides, v += 3)
|
||||
|
|
@ -741,13 +745,13 @@ Mesh GenMeshPoly(int sides, float radius)
|
|||
vertices[v] = (Vector3){ 0.0f, 0.0f, 0.0f };
|
||||
vertices[v + 1] = (Vector3){ sinf(DEG2RAD*i)*radius, 0.0f, cosf(DEG2RAD*i)*radius };
|
||||
vertices[v + 2] = (Vector3){ sinf(DEG2RAD*(i + 360/sides))*radius, 0.0f, cosf(DEG2RAD*(i + 360/sides))*radius };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Normals definition
|
||||
Vector3 *normals = (Vector3 *)malloc(vertexCount*sizeof(Vector3));
|
||||
for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up;
|
||||
|
||||
// TexCoords definition
|
||||
// TexCoords definition
|
||||
Vector2 *texcoords = (Vector2 *)malloc(vertexCount*sizeof(Vector2));
|
||||
for (int n = 0; n < vertexCount; n++) texcoords[n] = (Vector2){ 0.0f, 0.0f };
|
||||
|
||||
|
|
@ -756,7 +760,7 @@ Mesh GenMeshPoly(int sides, float radius)
|
|||
mesh.vertices = (float *)malloc(mesh.vertexCount*3*sizeof(float));
|
||||
mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float));
|
||||
mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float));
|
||||
|
||||
|
||||
// Mesh vertices position array
|
||||
for (int i = 0; i < mesh.vertexCount; i++)
|
||||
{
|
||||
|
|
@ -764,14 +768,14 @@ Mesh GenMeshPoly(int sides, float radius)
|
|||
mesh.vertices[3*i + 1] = vertices[i].y;
|
||||
mesh.vertices[3*i + 2] = vertices[i].z;
|
||||
}
|
||||
|
||||
|
||||
// Mesh texcoords array
|
||||
for (int i = 0; i < mesh.vertexCount; i++)
|
||||
{
|
||||
mesh.texcoords[2*i] = texcoords[i].x;
|
||||
mesh.texcoords[2*i + 1] = texcoords[i].y;
|
||||
}
|
||||
|
||||
|
||||
// Mesh normals array
|
||||
for (int i = 0; i < mesh.vertexCount; i++)
|
||||
{
|
||||
|
|
@ -779,14 +783,14 @@ Mesh GenMeshPoly(int sides, float radius)
|
|||
mesh.normals[3*i + 1] = normals[i].y;
|
||||
mesh.normals[3*i + 2] = normals[i].z;
|
||||
}
|
||||
|
||||
|
||||
free(vertices);
|
||||
free(normals);
|
||||
free(texcoords);
|
||||
|
||||
// Upload vertex data to GPU (static mesh)
|
||||
rlLoadMesh(&mesh, false);
|
||||
|
||||
rlLoadMesh(&mesh, false);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
|
@ -799,7 +803,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
|
|||
#if defined(CUSTOM_MESH_GEN_PLANE)
|
||||
resX++;
|
||||
resZ++;
|
||||
|
||||
|
||||
// Vertices definition
|
||||
int vertexCount = resX*resZ; // vertices get reused for the faces
|
||||
|
||||
|
|
@ -820,7 +824,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
|
|||
Vector3 *normals = (Vector3 *)malloc(vertexCount*sizeof(Vector3));
|
||||
for (int n = 0; n < vertexCount; n++) normals[n] = (Vector3){ 0.0f, 1.0f, 0.0f }; // Vector3.up;
|
||||
|
||||
// TexCoords definition
|
||||
// TexCoords definition
|
||||
Vector2 *texcoords = (Vector2 *)malloc(vertexCount*sizeof(Vector2));
|
||||
for (int v = 0; v < resZ; v++)
|
||||
{
|
||||
|
|
@ -843,7 +847,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
|
|||
triangles[t++] = i + 1;
|
||||
triangles[t++] = i;
|
||||
|
||||
triangles[t++] = i + resX;
|
||||
triangles[t++] = i + resX;
|
||||
triangles[t++] = i + resX + 1;
|
||||
triangles[t++] = i + 1;
|
||||
}
|
||||
|
|
@ -854,7 +858,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
|
|||
mesh.texcoords = (float *)malloc(mesh.vertexCount*2*sizeof(float));
|
||||
mesh.normals = (float *)malloc(mesh.vertexCount*3*sizeof(float));
|
||||
mesh.indices = (unsigned short *)malloc(mesh.triangleCount*3*sizeof(unsigned short));
|
||||
|
||||
|
||||
// Mesh vertices position array
|
||||
for (int i = 0; i < mesh.vertexCount; i++)
|
||||
{
|
||||
|
|
@ -862,14 +866,14 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
|
|||
mesh.vertices[3*i + 1] = vertices[i].y;
|
||||
mesh.vertices[3*i + 2] = vertices[i].z;
|
||||
}
|
||||
|
||||
|
||||
// Mesh texcoords array
|
||||
for (int i = 0; i < mesh.vertexCount; i++)
|
||||
{
|
||||
mesh.texcoords[2*i] = texcoords[i].x;
|
||||
mesh.texcoords[2*i + 1] = texcoords[i].y;
|
||||
}
|
||||
|
||||
|
||||
// Mesh normals array
|
||||
for (int i = 0; i < mesh.vertexCount; i++)
|
||||
{
|
||||
|
|
@ -877,22 +881,22 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
|
|||
mesh.normals[3*i + 1] = normals[i].y;
|
||||
mesh.normals[3*i + 2] = normals[i].z;
|
||||
}
|
||||
|
||||
|
||||
// Mesh indices array initialization
|
||||
for (int i = 0; i < mesh.triangleCount*3; i++) mesh.indices[i] = triangles[i];
|
||||
|
||||
|
||||
free(vertices);
|
||||
free(normals);
|
||||
free(texcoords);
|
||||
free(triangles);
|
||||
|
||||
|
||||
#else // Use par_shapes library to generate plane mesh
|
||||
|
||||
par_shapes_mesh *plane = par_shapes_create_plane(resX, resZ); // No normals/texcoords generated!!!
|
||||
par_shapes_scale(plane, width, length, 1.0f);
|
||||
par_shapes_rotate(plane, -PI/2.0f, (float[]){ 1, 0, 0 });
|
||||
par_shapes_translate(plane, -width/2, 0.0f, length/2);
|
||||
|
||||
|
||||
mesh.vertices = (float *)malloc(plane->ntriangles*3*3*sizeof(float));
|
||||
mesh.texcoords = (float *)malloc(plane->ntriangles*3*2*sizeof(float));
|
||||
mesh.normals = (float *)malloc(plane->ntriangles*3*3*sizeof(float));
|
||||
|
|
@ -905,11 +909,11 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
|
|||
mesh.vertices[k*3] = plane->points[plane->triangles[k]*3];
|
||||
mesh.vertices[k*3 + 1] = plane->points[plane->triangles[k]*3 + 1];
|
||||
mesh.vertices[k*3 + 2] = plane->points[plane->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.normals[k*3] = plane->normals[plane->triangles[k]*3];
|
||||
mesh.normals[k*3 + 1] = plane->normals[plane->triangles[k]*3 + 1];
|
||||
mesh.normals[k*3 + 2] = plane->normals[plane->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.texcoords[k*2] = plane->tcoords[plane->triangles[k]*2];
|
||||
mesh.texcoords[k*2 + 1] = plane->tcoords[plane->triangles[k]*2 + 1];
|
||||
}
|
||||
|
|
@ -918,7 +922,7 @@ Mesh GenMeshPlane(float width, float length, int resX, int resZ)
|
|||
#endif
|
||||
|
||||
// Upload vertex data to GPU (static mesh)
|
||||
rlLoadMesh(&mesh, false);
|
||||
rlLoadMesh(&mesh, false);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
|
@ -956,7 +960,7 @@ Mesh GenMeshCube(float width, float height, float length)
|
|||
-width/2, height/2, length/2,
|
||||
-width/2, height/2, -length/2
|
||||
};
|
||||
|
||||
|
||||
float texcoords[] = {
|
||||
0.0f, 0.0f,
|
||||
1.0f, 0.0f,
|
||||
|
|
@ -983,7 +987,7 @@ Mesh GenMeshCube(float width, float height, float length)
|
|||
1.0f, 1.0f,
|
||||
0.0f, 1.0f
|
||||
};
|
||||
|
||||
|
||||
float normals[] = {
|
||||
0.0f, 0.0f, 1.0f,
|
||||
0.0f, 0.0f, 1.0f,
|
||||
|
|
@ -1013,15 +1017,15 @@ Mesh GenMeshCube(float width, float height, float length)
|
|||
|
||||
mesh.vertices = (float *)malloc(24*3*sizeof(float));
|
||||
memcpy(mesh.vertices, vertices, 24*3*sizeof(float));
|
||||
|
||||
|
||||
mesh.texcoords = (float *)malloc(24*2*sizeof(float));
|
||||
memcpy(mesh.texcoords, texcoords, 24*2*sizeof(float));
|
||||
|
||||
|
||||
mesh.normals = (float *)malloc(24*3*sizeof(float));
|
||||
memcpy(mesh.normals, normals, 24*3*sizeof(float));
|
||||
|
||||
|
||||
mesh.indices = (unsigned short *)malloc(36*sizeof(unsigned short));
|
||||
|
||||
|
||||
int k = 0;
|
||||
|
||||
// Indices can be initialized right now
|
||||
|
|
@ -1036,10 +1040,10 @@ Mesh GenMeshCube(float width, float height, float length)
|
|||
|
||||
k++;
|
||||
}
|
||||
|
||||
|
||||
mesh.vertexCount = 24;
|
||||
mesh.triangleCount = 12;
|
||||
|
||||
|
||||
#else // Use par_shapes library to generate cube mesh
|
||||
/*
|
||||
// Platonic solids:
|
||||
|
|
@ -1053,11 +1057,11 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron
|
|||
// NOTE: No normals/texcoords generated by default
|
||||
par_shapes_mesh *cube = par_shapes_create_cube();
|
||||
cube->tcoords = PAR_MALLOC(float, 2*cube->npoints);
|
||||
for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f;
|
||||
for (int i = 0; i < 2*cube->npoints; i++) cube->tcoords[i] = 0.0f;
|
||||
par_shapes_scale(cube, width, height, length);
|
||||
par_shapes_translate(cube, -width/2, 0.0f, -length/2);
|
||||
par_shapes_compute_normals(cube);
|
||||
|
||||
|
||||
mesh.vertices = (float *)malloc(cube->ntriangles*3*3*sizeof(float));
|
||||
mesh.texcoords = (float *)malloc(cube->ntriangles*3*2*sizeof(float));
|
||||
mesh.normals = (float *)malloc(cube->ntriangles*3*3*sizeof(float));
|
||||
|
|
@ -1070,11 +1074,11 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron
|
|||
mesh.vertices[k*3] = cube->points[cube->triangles[k]*3];
|
||||
mesh.vertices[k*3 + 1] = cube->points[cube->triangles[k]*3 + 1];
|
||||
mesh.vertices[k*3 + 2] = cube->points[cube->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.normals[k*3] = cube->normals[cube->triangles[k]*3];
|
||||
mesh.normals[k*3 + 1] = cube->normals[cube->triangles[k]*3 + 1];
|
||||
mesh.normals[k*3 + 2] = cube->normals[cube->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.texcoords[k*2] = cube->tcoords[cube->triangles[k]*2];
|
||||
mesh.texcoords[k*2 + 1] = cube->tcoords[cube->triangles[k]*2 + 1];
|
||||
}
|
||||
|
|
@ -1083,7 +1087,7 @@ par_shapes_mesh* par_shapes_create_icosahedron(); // 20 sides polyhedron
|
|||
#endif
|
||||
|
||||
// Upload vertex data to GPU (static mesh)
|
||||
rlLoadMesh(&mesh, false);
|
||||
rlLoadMesh(&mesh, false);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
|
@ -1095,8 +1099,8 @@ RLAPI Mesh GenMeshSphere(float radius, int rings, int slices)
|
|||
|
||||
par_shapes_mesh *sphere = par_shapes_create_parametric_sphere(slices, rings);
|
||||
par_shapes_scale(sphere, radius, radius, radius);
|
||||
// NOTE: Soft normals are computed internally
|
||||
|
||||
// NOTE: Soft normals are computed internally
|
||||
|
||||
mesh.vertices = (float *)malloc(sphere->ntriangles*3*3*sizeof(float));
|
||||
mesh.texcoords = (float *)malloc(sphere->ntriangles*3*2*sizeof(float));
|
||||
mesh.normals = (float *)malloc(sphere->ntriangles*3*3*sizeof(float));
|
||||
|
|
@ -1109,19 +1113,19 @@ RLAPI Mesh GenMeshSphere(float radius, int rings, int slices)
|
|||
mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3];
|
||||
mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1];
|
||||
mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3];
|
||||
mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1];
|
||||
mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2];
|
||||
mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1];
|
||||
}
|
||||
|
||||
par_shapes_free_mesh(sphere);
|
||||
|
||||
|
||||
// Upload vertex data to GPU (static mesh)
|
||||
rlLoadMesh(&mesh, false);
|
||||
rlLoadMesh(&mesh, false);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
|
@ -1133,8 +1137,8 @@ RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices)
|
|||
|
||||
par_shapes_mesh *sphere = par_shapes_create_hemisphere(slices, rings);
|
||||
par_shapes_scale(sphere, radius, radius, radius);
|
||||
// NOTE: Soft normals are computed internally
|
||||
|
||||
// NOTE: Soft normals are computed internally
|
||||
|
||||
mesh.vertices = (float *)malloc(sphere->ntriangles*3*3*sizeof(float));
|
||||
mesh.texcoords = (float *)malloc(sphere->ntriangles*3*2*sizeof(float));
|
||||
mesh.normals = (float *)malloc(sphere->ntriangles*3*3*sizeof(float));
|
||||
|
|
@ -1147,19 +1151,19 @@ RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices)
|
|||
mesh.vertices[k*3] = sphere->points[sphere->triangles[k]*3];
|
||||
mesh.vertices[k*3 + 1] = sphere->points[sphere->triangles[k]*3 + 1];
|
||||
mesh.vertices[k*3 + 2] = sphere->points[sphere->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.normals[k*3] = sphere->normals[sphere->triangles[k]*3];
|
||||
mesh.normals[k*3 + 1] = sphere->normals[sphere->triangles[k]*3 + 1];
|
||||
mesh.normals[k*3 + 2] = sphere->normals[sphere->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.texcoords[k*2] = sphere->tcoords[sphere->triangles[k]*2];
|
||||
mesh.texcoords[k*2 + 1] = sphere->tcoords[sphere->triangles[k]*2 + 1];
|
||||
}
|
||||
|
||||
par_shapes_free_mesh(sphere);
|
||||
|
||||
|
||||
// Upload vertex data to GPU (static mesh)
|
||||
rlLoadMesh(&mesh, false);
|
||||
rlLoadMesh(&mesh, false);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
|
@ -1171,7 +1175,7 @@ Mesh GenMeshCylinder(float radius, float height, int slices)
|
|||
|
||||
// Instance a cylinder that sits on the Z=0 plane using the given tessellation
|
||||
// levels across the UV domain. Think of "slices" like a number of pizza
|
||||
// slices, and "stacks" like a number of stacked rings.
|
||||
// slices, and "stacks" like a number of stacked rings.
|
||||
// Height and radius are both 1.0, but they can easily be changed with par_shapes_scale
|
||||
par_shapes_mesh *cylinder = par_shapes_create_cylinder(slices, 8);
|
||||
par_shapes_scale(cylinder, radius, radius, height);
|
||||
|
|
@ -1183,16 +1187,16 @@ Mesh GenMeshCylinder(float radius, float height, int slices)
|
|||
for (int i = 0; i < 2*capTop->npoints; i++) capTop->tcoords[i] = 0.0f;
|
||||
par_shapes_rotate(capTop, -PI/2.0f, (float[]){ 1, 0, 0 });
|
||||
par_shapes_translate(capTop, 0, height, 0);
|
||||
|
||||
|
||||
// Generate an orientable disk shape (bottom cap)
|
||||
par_shapes_mesh *capBottom = par_shapes_create_disk(radius, slices, (float[]){ 0, 0, 0 }, (float[]){ 0, 0, -1 });
|
||||
capBottom->tcoords = PAR_MALLOC(float, 2*capBottom->npoints);
|
||||
for (int i = 0; i < 2*capBottom->npoints; i++) capBottom->tcoords[i] = 0.95f;
|
||||
par_shapes_rotate(capBottom, PI/2.0f, (float[]){ 1, 0, 0 });
|
||||
|
||||
|
||||
par_shapes_merge_and_free(cylinder, capTop);
|
||||
par_shapes_merge_and_free(cylinder, capBottom);
|
||||
|
||||
|
||||
mesh.vertices = (float *)malloc(cylinder->ntriangles*3*3*sizeof(float));
|
||||
mesh.texcoords = (float *)malloc(cylinder->ntriangles*3*2*sizeof(float));
|
||||
mesh.normals = (float *)malloc(cylinder->ntriangles*3*3*sizeof(float));
|
||||
|
|
@ -1205,19 +1209,19 @@ Mesh GenMeshCylinder(float radius, float height, int slices)
|
|||
mesh.vertices[k*3] = cylinder->points[cylinder->triangles[k]*3];
|
||||
mesh.vertices[k*3 + 1] = cylinder->points[cylinder->triangles[k]*3 + 1];
|
||||
mesh.vertices[k*3 + 2] = cylinder->points[cylinder->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.normals[k*3] = cylinder->normals[cylinder->triangles[k]*3];
|
||||
mesh.normals[k*3 + 1] = cylinder->normals[cylinder->triangles[k]*3 + 1];
|
||||
mesh.normals[k*3 + 2] = cylinder->normals[cylinder->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.texcoords[k*2] = cylinder->tcoords[cylinder->triangles[k]*2];
|
||||
mesh.texcoords[k*2 + 1] = cylinder->tcoords[cylinder->triangles[k]*2 + 1];
|
||||
}
|
||||
|
||||
par_shapes_free_mesh(cylinder);
|
||||
|
||||
|
||||
// Upload vertex data to GPU (static mesh)
|
||||
rlLoadMesh(&mesh, false);
|
||||
rlLoadMesh(&mesh, false);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
|
@ -1229,7 +1233,7 @@ Mesh GenMeshTorus(float radius, float size, int radSeg, int sides)
|
|||
|
||||
if (radius > 1.0f) radius = 1.0f;
|
||||
else if (radius < 0.1f) radius = 0.1f;
|
||||
|
||||
|
||||
// Create a donut that sits on the Z=0 plane with the specified inner radius
|
||||
// The outer radius can be controlled with par_shapes_scale
|
||||
par_shapes_mesh *torus = par_shapes_create_torus(radSeg, sides, radius);
|
||||
|
|
@ -1247,19 +1251,19 @@ Mesh GenMeshTorus(float radius, float size, int radSeg, int sides)
|
|||
mesh.vertices[k*3] = torus->points[torus->triangles[k]*3];
|
||||
mesh.vertices[k*3 + 1] = torus->points[torus->triangles[k]*3 + 1];
|
||||
mesh.vertices[k*3 + 2] = torus->points[torus->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.normals[k*3] = torus->normals[torus->triangles[k]*3];
|
||||
mesh.normals[k*3 + 1] = torus->normals[torus->triangles[k]*3 + 1];
|
||||
mesh.normals[k*3 + 2] = torus->normals[torus->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.texcoords[k*2] = torus->tcoords[torus->triangles[k]*2];
|
||||
mesh.texcoords[k*2 + 1] = torus->tcoords[torus->triangles[k]*2 + 1];
|
||||
}
|
||||
|
||||
par_shapes_free_mesh(torus);
|
||||
|
||||
|
||||
// Upload vertex data to GPU (static mesh)
|
||||
rlLoadMesh(&mesh, false);
|
||||
rlLoadMesh(&mesh, false);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
|
@ -1268,7 +1272,7 @@ Mesh GenMeshTorus(float radius, float size, int radSeg, int sides)
|
|||
Mesh GenMeshKnot(float radius, float size, int radSeg, int sides)
|
||||
{
|
||||
Mesh mesh = { 0 };
|
||||
|
||||
|
||||
if (radius > 3.0f) radius = 3.0f;
|
||||
else if (radius < 0.5f) radius = 0.5f;
|
||||
|
||||
|
|
@ -1287,19 +1291,19 @@ Mesh GenMeshKnot(float radius, float size, int radSeg, int sides)
|
|||
mesh.vertices[k*3] = knot->points[knot->triangles[k]*3];
|
||||
mesh.vertices[k*3 + 1] = knot->points[knot->triangles[k]*3 + 1];
|
||||
mesh.vertices[k*3 + 2] = knot->points[knot->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.normals[k*3] = knot->normals[knot->triangles[k]*3];
|
||||
mesh.normals[k*3 + 1] = knot->normals[knot->triangles[k]*3 + 1];
|
||||
mesh.normals[k*3 + 2] = knot->normals[knot->triangles[k]*3 + 2];
|
||||
|
||||
|
||||
mesh.texcoords[k*2] = knot->tcoords[knot->triangles[k]*2];
|
||||
mesh.texcoords[k*2 + 1] = knot->tcoords[knot->triangles[k]*2 + 1];
|
||||
}
|
||||
|
||||
par_shapes_free_mesh(knot);
|
||||
|
||||
|
||||
// Upload vertex data to GPU (static mesh)
|
||||
rlLoadMesh(&mesh, false);
|
||||
rlLoadMesh(&mesh, false);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
|
@ -1407,7 +1411,7 @@ Mesh GenMeshHeightmap(Image heightmap, Vector3 size)
|
|||
}
|
||||
|
||||
free(pixels);
|
||||
|
||||
|
||||
// Upload vertex data to GPU (static mesh)
|
||||
rlLoadMesh(&mesh, false);
|
||||
|
||||
|
|
@ -1767,9 +1771,9 @@ Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize)
|
|||
free(mapTexcoords);
|
||||
|
||||
free(cubicmapPixels); // Free image pixel data
|
||||
|
||||
|
||||
// Upload vertex data to GPU (static mesh)
|
||||
rlLoadMesh(&mesh, false);
|
||||
rlLoadMesh(&mesh, false);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
|
@ -1817,7 +1821,7 @@ void UnloadMaterial(Material material)
|
|||
// Unload loaded texture maps (avoid unloading default texture, managed by raylib)
|
||||
for (int i = 0; i < MAX_MATERIAL_MAPS; i++)
|
||||
{
|
||||
if (material.maps[i].texture.id != GetTextureDefault().id) rlDeleteTextures(material.maps[i].texture.id);
|
||||
if (material.maps[i].texture.id != GetTextureDefault().id) rlDeleteTextures(material.maps[i].texture.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1838,7 +1842,7 @@ void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rota
|
|||
Matrix matScale = MatrixScale(scale.x, scale.y, scale.z);
|
||||
Matrix matRotation = MatrixRotate(rotationAxis, rotationAngle*DEG2RAD);
|
||||
Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z);
|
||||
|
||||
|
||||
Matrix matTransform = MatrixMultiply(MatrixMultiply(matScale, matRotation), matTranslation);
|
||||
|
||||
// Combine model transformation matrix (model.transform) with matrix generated by function parameters (matTransform)
|
||||
|
|
@ -2033,7 +2037,7 @@ bool CheckCollisionRaySphereEx(Ray ray, Vector3 spherePosition, float sphereRadi
|
|||
|
||||
if (distance < sphereRadius) collisionDistance = vector + sqrtf(d);
|
||||
else collisionDistance = vector - sqrtf(d);
|
||||
|
||||
|
||||
// Calculate collision point
|
||||
Vector3 cPoint = Vector3Add(ray.position, Vector3Scale(ray.direction, collisionDistance));
|
||||
|
||||
|
|
@ -2093,7 +2097,7 @@ RayHitInfo GetCollisionRayModel(Ray ray, Model *model)
|
|||
b = vertdata[i*3 + 1];
|
||||
c = vertdata[i*3 + 2];
|
||||
}
|
||||
|
||||
|
||||
a = Vector3Transform(a, model->transform);
|
||||
b = Vector3Transform(b, model->transform);
|
||||
c = Vector3Transform(c, model->transform);
|
||||
|
|
@ -2227,7 +2231,7 @@ void MeshTangents(Mesh *mesh)
|
|||
{
|
||||
if (mesh->tangents == NULL) mesh->tangents = (float *)malloc(mesh->vertexCount*4*sizeof(float));
|
||||
else TraceLog(LOG_WARNING, "Mesh tangents already exist");
|
||||
|
||||
|
||||
Vector3 *tan1 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3));
|
||||
Vector3 *tan2 = (Vector3 *)malloc(mesh->vertexCount*sizeof(Vector3));
|
||||
|
||||
|
|
@ -2256,11 +2260,11 @@ void MeshTangents(Mesh *mesh)
|
|||
float t2 = uv3.y - uv1.y;
|
||||
|
||||
float div = s1*t2 - s2*t1;
|
||||
float r = (div == 0.0f) ? 0.0f : 1.0f/div;
|
||||
float r = (div == 0.0f)? 0.0f : 1.0f/div;
|
||||
|
||||
Vector3 sdir = { (t2*x1 - t1*x2)*r, (t2*y1 - t1*y2)*r, (t2*z1 - t1*z2)*r };
|
||||
Vector3 tdir = { (s1*x2 - s2*x1)*r, (s1*y2 - s2*y1)*r, (s1*z2 - s2*z1)*r };
|
||||
|
||||
|
||||
tan1[i + 0] = sdir;
|
||||
tan1[i + 1] = sdir;
|
||||
tan1[i + 2] = sdir;
|
||||
|
|
@ -2289,13 +2293,13 @@ void MeshTangents(Mesh *mesh)
|
|||
mesh->tangents[i*4 + 0] = tangent.x;
|
||||
mesh->tangents[i*4 + 1] = tangent.y;
|
||||
mesh->tangents[i*4 + 2] = tangent.z;
|
||||
mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f) ? -1.0f : 1.0f;
|
||||
mesh->tangents[i*4 + 3] = (Vector3DotProduct(Vector3CrossProduct(normal, tangent), tan2[i]) < 0.0f)? -1.0f : 1.0f;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
free(tan1);
|
||||
free(tan2);
|
||||
|
||||
|
||||
TraceLog(LOG_INFO, "Tangents computed for mesh");
|
||||
}
|
||||
|
||||
|
|
@ -2307,8 +2311,8 @@ void MeshBinormals(Mesh *mesh)
|
|||
Vector3 normal = { mesh->normals[i*3 + 0], mesh->normals[i*3 + 1], mesh->normals[i*3 + 2] };
|
||||
Vector3 tangent = { mesh->tangents[i*4 + 0], mesh->tangents[i*4 + 1], mesh->tangents[i*4 + 2] };
|
||||
float tangentW = mesh->tangents[i*4 + 3];
|
||||
|
||||
// TODO: Register computed binormal in mesh->binormal ?
|
||||
|
||||
// TODO: Register computed binormal in mesh->binormal?
|
||||
// Vector3 binormal = Vector3Multiply(Vector3CrossProduct(normal, tangent), tangentW);
|
||||
}
|
||||
}
|
||||
|
|
@ -2635,7 +2639,7 @@ static Material LoadMTL(const char *fileName)
|
|||
} break;
|
||||
case 'e': // Ke float float float Emmisive color (RGB)
|
||||
{
|
||||
// TODO: Support Ke ?
|
||||
// TODO: Support Ke?
|
||||
} break;
|
||||
default: break;
|
||||
}
|
||||
|
|
@ -2736,25 +2740,25 @@ static Material LoadMTL(const char *fileName)
|
|||
static Mesh LoadIQM(const char *fileName)
|
||||
{
|
||||
Mesh mesh = { 0 };
|
||||
|
||||
|
||||
// TODO: Load IQM file
|
||||
|
||||
|
||||
return mesh;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(SUPPORT_FILEFORMAT_GLTF)
|
||||
// Load GLTF mesh data
|
||||
// Load glTF mesh data
|
||||
static Mesh LoadGLTF(const char *fileName)
|
||||
{
|
||||
Mesh mesh = { 0 };
|
||||
|
||||
// GLTF file loading
|
||||
|
||||
// glTF file loading
|
||||
FILE *gltfFile = fopen(fileName, "rb");
|
||||
|
||||
|
||||
if (gltfFile == NULL)
|
||||
{
|
||||
TraceLog(LOG_WARNING, "[%s] GLTF file could not be opened", fileName);
|
||||
TraceLog(LOG_WARNING, "[%s] glTF file could not be opened", fileName);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
|
@ -2764,25 +2768,31 @@ static Mesh LoadGLTF(const char *fileName)
|
|||
|
||||
void *buffer = malloc(size);
|
||||
fread(buffer, size, 1, gltfFile);
|
||||
|
||||
|
||||
fclose(gltfFile);
|
||||
|
||||
// GLTF data loading
|
||||
// glTF data loading
|
||||
cgltf_options options = {0};
|
||||
cgltf_data data;
|
||||
cgltf_result result = cgltf_parse(&options, buffer, size, &data);
|
||||
|
||||
free(buffer);
|
||||
|
||||
if (result == cgltf_result_success)
|
||||
{
|
||||
printf("Type: %u\n", data.file_type);
|
||||
printf("Version: %d\n", data.version);
|
||||
printf("Meshes: %lu\n", data.meshes_count);
|
||||
}
|
||||
else TraceLog(LOG_WARNING, "[%s] GLTF data could not be loaded", fileName);
|
||||
|
||||
free(buffer);
|
||||
cgltf_free(&data);
|
||||
|
||||
return mesh;
|
||||
// TODO: Process glTF data and map to mesh
|
||||
|
||||
// NOTE: data.buffers[] and data.images[] should be loaded
|
||||
// using buffers[n].uri and images[n].uri... or use cgltf_load_buffers(&options, data, fileName);
|
||||
|
||||
cgltf_free(&data);
|
||||
}
|
||||
else TraceLog(LOG_WARNING, "[%s] glTF data could not be loaded", fileName);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
70
src/raudio.c
70
src/raudio.c
|
|
@ -712,11 +712,13 @@ void SetAudioBufferPitch(AudioBuffer *audioBuffer, float pitch)
|
|||
return;
|
||||
}
|
||||
|
||||
audioBuffer->pitch = pitch;
|
||||
float pitchMul = pitch / audioBuffer->pitch;
|
||||
|
||||
// Pitching is just an adjustment of the sample rate. Note that this changes the duration of the sound - higher pitches
|
||||
// will make the sound faster; lower pitches make it slower.
|
||||
mal_uint32 newOutputSampleRate = (mal_uint32)((((float)audioBuffer->dsp.src.config.sampleRateOut / (float)audioBuffer->dsp.src.config.sampleRateIn) / pitch) * audioBuffer->dsp.src.config.sampleRateIn);
|
||||
mal_uint32 newOutputSampleRate = (mal_uint32)((float)audioBuffer->dsp.src.config.sampleRateOut / pitchMul);
|
||||
audioBuffer->pitch *= (float)audioBuffer->dsp.src.config.sampleRateOut / newOutputSampleRate;
|
||||
|
||||
mal_dsp_set_output_sample_rate(&audioBuffer->dsp, newOutputSampleRate);
|
||||
}
|
||||
|
||||
|
|
@ -767,7 +769,11 @@ Wave LoadWave(const char *fileName)
|
|||
{
|
||||
Wave wave = { 0 };
|
||||
|
||||
#if defined(SUPPORT_FILEFORMAT_WAV)
|
||||
if (IsFileExtension(fileName, ".wav")) wave = LoadWAV(fileName);
|
||||
#else
|
||||
if (false) {}
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_OGG)
|
||||
else if (IsFileExtension(fileName, ".ogg")) wave = LoadOGG(fileName);
|
||||
#endif
|
||||
|
|
@ -830,7 +836,7 @@ Sound LoadSoundFromWave(Wave wave)
|
|||
//
|
||||
// I have decided on the first option because it offloads work required for the format conversion to the to the loading stage.
|
||||
// The downside to this is that it uses more memory if the original sound is u8 or s16.
|
||||
mal_format formatIn = ((wave.sampleSize == 8) ? mal_format_u8 : ((wave.sampleSize == 16) ? mal_format_s16 : mal_format_f32));
|
||||
mal_format formatIn = ((wave.sampleSize == 8)? mal_format_u8 : ((wave.sampleSize == 16)? mal_format_s16 : mal_format_f32));
|
||||
mal_uint32 frameCountIn = wave.sampleCount/wave.channels;
|
||||
|
||||
mal_uint32 frameCount = (mal_uint32)mal_convert_frames(NULL, DEVICE_FORMAT, DEVICE_CHANNELS, DEVICE_SAMPLE_RATE, NULL, formatIn, wave.channels, wave.sampleRate, frameCountIn);
|
||||
|
|
@ -887,7 +893,11 @@ void ExportWave(Wave wave, const char *fileName)
|
|||
{
|
||||
bool success = false;
|
||||
|
||||
#if defined(SUPPORT_FILEFORMAT_WAV)
|
||||
if (IsFileExtension(fileName, ".wav")) success = SaveWAV(wave, fileName);
|
||||
#else
|
||||
if (false) {}
|
||||
#endif
|
||||
else if (IsFileExtension(fileName, ".raw"))
|
||||
{
|
||||
// Export raw sample data (without header)
|
||||
|
|
@ -938,7 +948,7 @@ void ExportWaveAsCode(Wave wave, const char *fileName)
|
|||
|
||||
// Write byte data as hexadecimal text
|
||||
fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize);
|
||||
for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0) ? "0x%x,\n" : "0x%x, "), ((unsigned char *)wave.data)[i]);
|
||||
for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)wave.data)[i]);
|
||||
fprintf(txtFile, "0x%x };\n", ((unsigned char *)wave.data)[dataSize - 1]);
|
||||
|
||||
fclose(txtFile);
|
||||
|
|
@ -989,8 +999,8 @@ void SetSoundPitch(Sound sound, float pitch)
|
|||
// Convert wave data to desired format
|
||||
void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels)
|
||||
{
|
||||
mal_format formatIn = ((wave->sampleSize == 8) ? mal_format_u8 : ((wave->sampleSize == 16) ? mal_format_s16 : mal_format_f32));
|
||||
mal_format formatOut = (( sampleSize == 8) ? mal_format_u8 : (( sampleSize == 16) ? mal_format_s16 : mal_format_f32));
|
||||
mal_format formatIn = ((wave->sampleSize == 8)? mal_format_u8 : ((wave->sampleSize == 16)? mal_format_s16 : mal_format_f32));
|
||||
mal_format formatOut = (( sampleSize == 8)? mal_format_u8 : (( sampleSize == 16)? mal_format_s16 : mal_format_f32));
|
||||
|
||||
mal_uint32 frameCountIn = wave->sampleCount; // Is wave->sampleCount actually the frame count? That terminology needs to change, if so.
|
||||
|
||||
|
|
@ -1087,6 +1097,7 @@ Music LoadMusicStream(const char *fileName)
|
|||
Music music = (MusicData *)malloc(sizeof(MusicData));
|
||||
bool musicLoaded = true;
|
||||
|
||||
#if defined(SUPPORT_FILEFORMAT_OGG)
|
||||
if (IsFileExtension(fileName, ".ogg"))
|
||||
{
|
||||
// Open ogg audio stream
|
||||
|
|
@ -1110,6 +1121,9 @@ Music LoadMusicStream(const char *fileName)
|
|||
TraceLog(LOG_DEBUG, "[%s] OGG memory required: %i", fileName, info.temp_memory_required);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (false) {}
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_FLAC)
|
||||
else if (IsFileExtension(fileName, ".flac"))
|
||||
{
|
||||
|
|
@ -1202,7 +1216,11 @@ Music LoadMusicStream(const char *fileName)
|
|||
|
||||
if (!musicLoaded)
|
||||
{
|
||||
#if defined(SUPPORT_FILEFORMAT_OGG)
|
||||
if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close(music->ctxOgg);
|
||||
#else
|
||||
if (false) {}
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_FLAC)
|
||||
else if (music->ctxType == MUSIC_AUDIO_FLAC) drflac_free(music->ctxFlac);
|
||||
#endif
|
||||
|
|
@ -1229,10 +1247,14 @@ Music LoadMusicStream(const char *fileName)
|
|||
void UnloadMusicStream(Music music)
|
||||
{
|
||||
if (music == NULL) return;
|
||||
|
||||
|
||||
CloseAudioStream(music->stream);
|
||||
|
||||
#if defined(SUPPORT_FILEFORMAT_OGG)
|
||||
if (music->ctxType == MUSIC_AUDIO_OGG) stb_vorbis_close(music->ctxOgg);
|
||||
#else
|
||||
if (false) {}
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_FLAC)
|
||||
else if (music->ctxType == MUSIC_AUDIO_FLAC) drflac_free(music->ctxFlac);
|
||||
#endif
|
||||
|
|
@ -1291,13 +1313,15 @@ void ResumeMusicStream(Music music)
|
|||
void StopMusicStream(Music music)
|
||||
{
|
||||
if (music == NULL) return;
|
||||
|
||||
|
||||
StopAudioStream(music->stream);
|
||||
|
||||
// Restart music context
|
||||
switch (music->ctxType)
|
||||
{
|
||||
#if defined(SUPPORT_FILEFORMAT_OGG)
|
||||
case MUSIC_AUDIO_OGG: stb_vorbis_seek_start(music->ctxOgg); break;
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_FLAC)
|
||||
case MUSIC_AUDIO_FLAC: /* TODO: Restart FLAC context */ break;
|
||||
#endif
|
||||
|
|
@ -1321,7 +1345,7 @@ void StopMusicStream(Music music)
|
|||
void UpdateMusicStream(Music music)
|
||||
{
|
||||
if (music == NULL) return;
|
||||
|
||||
|
||||
bool streamEnding = false;
|
||||
|
||||
unsigned int subBufferSizeInFrames = ((AudioBuffer *)music->stream.audioBuffer)->bufferSizeInFrames/2;
|
||||
|
|
@ -1339,12 +1363,14 @@ void UpdateMusicStream(Music music)
|
|||
// TODO: Really don't like ctxType thingy...
|
||||
switch (music->ctxType)
|
||||
{
|
||||
#if defined(SUPPORT_FILEFORMAT_OGG)
|
||||
case MUSIC_AUDIO_OGG:
|
||||
{
|
||||
// NOTE: Returns the number of samples to process (be careful! we ask for number of shorts!)
|
||||
stb_vorbis_get_samples_short_interleaved(music->ctxOgg, music->stream.channels, (short *)pcm, samplesCount);
|
||||
|
||||
} break;
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_FLAC)
|
||||
case MUSIC_AUDIO_FLAC:
|
||||
{
|
||||
|
|
@ -1369,21 +1395,21 @@ void UpdateMusicStream(Music music)
|
|||
} break;
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_MOD)
|
||||
case MUSIC_MODULE_MOD:
|
||||
case MUSIC_MODULE_MOD:
|
||||
{
|
||||
// NOTE: 3rd parameter (nbsample) specify the number of stereo 16bits samples you want, so sampleCount/2
|
||||
jar_mod_fillbuffer(&music->ctxMod, (short *)pcm, samplesCount/2, 0);
|
||||
jar_mod_fillbuffer(&music->ctxMod, (short *)pcm, samplesCount/2, 0);
|
||||
} break;
|
||||
#endif
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
UpdateAudioStream(music->stream, pcm, samplesCount);
|
||||
if ((music->ctxType == MUSIC_MODULE_XM) || (music->ctxType == MUSIC_MODULE_MOD))
|
||||
{
|
||||
if (samplesCount > 1) music->samplesLeft -= samplesCount/2;
|
||||
else music->samplesLeft -= samplesCount;
|
||||
if (samplesCount > 1) music->samplesLeft -= samplesCount/2;
|
||||
else music->samplesLeft -= samplesCount;
|
||||
}
|
||||
else music->samplesLeft -= samplesCount;
|
||||
|
||||
|
|
@ -1451,7 +1477,7 @@ void SetMusicLoopCount(Music music, int count)
|
|||
float GetMusicTimeLength(Music music)
|
||||
{
|
||||
float totalSeconds = 0.0f;
|
||||
|
||||
|
||||
if (music != NULL) totalSeconds = (float)music->totalSamples/(music->stream.sampleRate*music->stream.channels);
|
||||
|
||||
return totalSeconds;
|
||||
|
|
@ -1487,7 +1513,7 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un
|
|||
stream.channels = 1; // Fallback to mono channel
|
||||
}
|
||||
|
||||
mal_format formatIn = ((stream.sampleSize == 8) ? mal_format_u8 : ((stream.sampleSize == 16) ? mal_format_s16 : mal_format_f32));
|
||||
mal_format formatIn = ((stream.sampleSize == 8)? mal_format_u8 : ((stream.sampleSize == 16)? mal_format_s16 : mal_format_f32));
|
||||
|
||||
// The size of a streaming buffer must be at least double the size of a period.
|
||||
unsigned int periodSize = device.bufferSizeInFrames/device.periods;
|
||||
|
|
@ -1504,7 +1530,7 @@ AudioStream InitAudioStream(unsigned int sampleRate, unsigned int sampleSize, un
|
|||
audioBuffer->looping = true; // Always loop for streaming buffers.
|
||||
stream.audioBuffer = audioBuffer;
|
||||
|
||||
TraceLog(LOG_INFO, "[AUD ID %i] Audio stream loaded successfully (%i Hz, %i bit, %s)", stream.source, stream.sampleRate, stream.sampleSize, (stream.channels == 1) ? "Mono" : "Stereo");
|
||||
TraceLog(LOG_INFO, "[AUD ID %i] Audio stream loaded successfully (%i Hz, %i bit, %s)", stream.source, stream.sampleRate, stream.sampleSize, (stream.channels == 1)? "Mono" : "Stereo");
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
|
@ -1542,7 +1568,7 @@ void UpdateAudioStream(AudioStream stream, const void *data, int samplesCount)
|
|||
else
|
||||
{
|
||||
// Just update whichever sub-buffer is processed.
|
||||
subBufferToUpdate = (audioBuffer->isSubBufferProcessed[0]) ? 0 : 1;
|
||||
subBufferToUpdate = (audioBuffer->isSubBufferProcessed[0])? 0 : 1;
|
||||
}
|
||||
|
||||
mal_uint32 subBufferSizeInFrames = audioBuffer->bufferSizeInFrames/2;
|
||||
|
|
@ -1745,7 +1771,7 @@ static Wave LoadWAV(const char *fileName)
|
|||
// NOTE: subChunkSize comes in bytes, we need to translate it to number of samples
|
||||
wave.sampleCount = (wavData.subChunkSize/(wave.sampleSize/8))/wave.channels;
|
||||
|
||||
TraceLog(LOG_INFO, "[%s] WAV file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo");
|
||||
TraceLog(LOG_INFO, "[%s] WAV file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1866,7 +1892,7 @@ static Wave LoadOGG(const char *fileName)
|
|||
|
||||
TraceLog(LOG_DEBUG, "[%s] Samples obtained: %i", fileName, numSamplesOgg);
|
||||
|
||||
TraceLog(LOG_INFO, "[%s] OGG file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo");
|
||||
TraceLog(LOG_INFO, "[%s] OGG file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo");
|
||||
|
||||
stb_vorbis_close(oggFile);
|
||||
}
|
||||
|
|
@ -1893,7 +1919,7 @@ static Wave LoadFLAC(const char *fileName)
|
|||
if (wave.channels > 2) TraceLog(LOG_WARNING, "[%s] FLAC channels number (%i) not supported", fileName, wave.channels);
|
||||
|
||||
if (wave.data == NULL) TraceLog(LOG_WARNING, "[%s] FLAC data could not be loaded", fileName);
|
||||
else TraceLog(LOG_INFO, "[%s] FLAC file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo");
|
||||
else TraceLog(LOG_INFO, "[%s] FLAC file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo");
|
||||
|
||||
return wave;
|
||||
}
|
||||
|
|
@ -1920,7 +1946,7 @@ static Wave LoadMP3(const char *fileName)
|
|||
if (wave.channels > 2) TraceLog(LOG_WARNING, "[%s] MP3 channels number (%i) not supported", fileName, wave.channels);
|
||||
|
||||
if (wave.data == NULL) TraceLog(LOG_WARNING, "[%s] MP3 data could not be loaded", fileName);
|
||||
else TraceLog(LOG_INFO, "[%s] MP3 file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1) ? "Mono" : "Stereo");
|
||||
else TraceLog(LOG_INFO, "[%s] MP3 file loaded successfully (%i Hz, %i bit, %s)", fileName, wave.sampleRate, wave.sampleSize, (wave.channels == 1)? "Mono" : "Stereo");
|
||||
|
||||
return wave;
|
||||
}
|
||||
|
|
|
|||
124
src/rlgl.h
124
src/rlgl.h
|
|
@ -356,7 +356,7 @@ typedef unsigned char byte;
|
|||
LOC_MAP_PREFILTER,
|
||||
LOC_MAP_BRDF
|
||||
} ShaderLocationIndex;
|
||||
|
||||
|
||||
// Shader uniform data types
|
||||
typedef enum {
|
||||
UNIFORM_FLOAT = 0,
|
||||
|
|
@ -775,8 +775,8 @@ typedef struct DrawCall {
|
|||
#endif
|
||||
"void main() \n"
|
||||
"{ \n"
|
||||
" vec2 lensCenter = fragTexCoord.x < 0.5 ? leftLensCenter : rightLensCenter; \n"
|
||||
" vec2 screenCenter = fragTexCoord.x < 0.5 ? leftScreenCenter : rightScreenCenter; \n"
|
||||
" vec2 lensCenter = fragTexCoord.x < 0.5? leftLensCenter : rightLensCenter; \n"
|
||||
" vec2 screenCenter = fragTexCoord.x < 0.5? leftScreenCenter : rightScreenCenter; \n"
|
||||
" vec2 theta = (fragTexCoord - lensCenter)*scaleIn; \n"
|
||||
" float rSq = theta.x*theta.x + theta.y*theta.y; \n"
|
||||
" vec2 theta1 = theta*(hmdWarpParam.x + hmdWarpParam.y*rSq + hmdWarpParam.z*rSq*rSq + hmdWarpParam.w*rSq*rSq*rSq); \n"
|
||||
|
|
@ -993,14 +993,14 @@ void rlPushMatrix(void)
|
|||
|
||||
// Pop lattest inserted matrix from stack
|
||||
void rlPopMatrix(void)
|
||||
{
|
||||
{
|
||||
if (stackCounter > 0)
|
||||
{
|
||||
Matrix mat = stack[stackCounter - 1];
|
||||
*currentMatrix = mat;
|
||||
stackCounter--;
|
||||
}
|
||||
|
||||
|
||||
if ((stackCounter == 0) && (currentMatrixMode == RL_MODELVIEW))
|
||||
{
|
||||
currentMatrix = &modelview;
|
||||
|
|
@ -1136,12 +1136,12 @@ void rlEnd(void)
|
|||
// TODO: System could be improved (a bit) just storing every draw alignment value
|
||||
// and adding it to vertexOffset on drawing... maybe in a future...
|
||||
int vertexCount = draws[drawsCounter - 1].vertexCount;
|
||||
int vertexToAlign = (vertexCount >= 4) ? vertexCount%4 : (4 - vertexCount%4);
|
||||
int vertexToAlign = (vertexCount >= 4)? vertexCount%4 : (4 - vertexCount%4);
|
||||
for (int i = 0; i < vertexToAlign; i++) rlVertex3f(-1, -1, -1);
|
||||
|
||||
// Make sure vertexCount is the same for vertices, texcoords, colors and normals
|
||||
// NOTE: In OpenGL 1.1, one glColor call can be made for all the subsequent glVertex calls
|
||||
|
||||
|
||||
// Make sure colors count match vertex count
|
||||
if (vertexData[currentBuffer].vCounter != vertexData[currentBuffer].cCounter)
|
||||
{
|
||||
|
|
@ -1156,7 +1156,7 @@ void rlEnd(void)
|
|||
vertexData[currentBuffer].cCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Make sure texcoords count match vertex count
|
||||
if (vertexData[currentBuffer].vCounter != vertexData[currentBuffer].tcCounter)
|
||||
{
|
||||
|
|
@ -1194,10 +1194,10 @@ void rlEnd(void)
|
|||
void rlVertex3f(float x, float y, float z)
|
||||
{
|
||||
Vector3 vec = { x, y, z };
|
||||
|
||||
|
||||
// Transform provided vector if required
|
||||
if (useTransformMatrix) vec = Vector3Transform(vec, transformMatrix);
|
||||
|
||||
|
||||
// Verify that MAX_BATCH_ELEMENTS limit not reached
|
||||
if (vertexData[currentBuffer].vCounter < (MAX_BATCH_ELEMENTS*4))
|
||||
{
|
||||
|
|
@ -1233,7 +1233,7 @@ void rlTexCoord2f(float x, float y)
|
|||
}
|
||||
|
||||
// Define one vertex (normal)
|
||||
// NOTE: Normals limited to TRIANGLES only ?
|
||||
// NOTE: Normals limited to TRIANGLES only?
|
||||
void rlNormal3f(float x, float y, float z)
|
||||
{
|
||||
// TODO: Normals usage...
|
||||
|
|
@ -1499,7 +1499,7 @@ void rlglInit(int width, int height)
|
|||
//for (int i = 0; i < numComp; i++) TraceLog(LOG_INFO, "Supported compressed format: 0x%x", format[i]);
|
||||
|
||||
// NOTE: We don't need that much data on screen... right now...
|
||||
|
||||
|
||||
// TODO: Automatize extensions loading using rlLoadExtensions() and GLAD
|
||||
// Actually, when rlglInit() is called in InitWindow() in core.c,
|
||||
// OpenGL required extensions have already been loaded (PLATFORM_DESKTOP)
|
||||
|
|
@ -1512,7 +1512,7 @@ void rlglInit(int width, int height)
|
|||
|
||||
// NOTE: On OpenGL 3.3 VAO and NPOT are supported by default
|
||||
vaoSupported = true;
|
||||
|
||||
|
||||
// Multiple texture extensions supported by default
|
||||
texNPOTSupported = true;
|
||||
texFloatSupported = true;
|
||||
|
|
@ -1585,11 +1585,11 @@ void rlglInit(int width, int height)
|
|||
|
||||
// Check texture float support
|
||||
if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) texFloatSupported = true;
|
||||
|
||||
|
||||
// Check depth texture support
|
||||
if ((strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) ||
|
||||
(strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0)) texDepthSupported = true;
|
||||
|
||||
|
||||
if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) maxDepthBits = 24;
|
||||
if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) maxDepthBits = 32;
|
||||
#endif
|
||||
|
|
@ -1648,8 +1648,8 @@ void rlglInit(int width, int height)
|
|||
|
||||
if (debugMarkerSupported) TraceLog(LOG_INFO, "[EXTENSION] Debug Marker supported");
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Initialize buffers, default shaders and default textures
|
||||
//----------------------------------------------------------
|
||||
|
||||
|
|
@ -1666,7 +1666,7 @@ void rlglInit(int width, int height)
|
|||
|
||||
// Init default vertex arrays buffers
|
||||
LoadBuffersDefault();
|
||||
|
||||
|
||||
// Init transformations matrix accumulator
|
||||
transformMatrix = MatrixIdentity();
|
||||
|
||||
|
|
@ -1995,9 +1995,9 @@ unsigned int rlLoadTextureDepth(int width, int height, int bits, bool useRenderB
|
|||
{
|
||||
unsigned int id = 0;
|
||||
unsigned int glInternalFormat = GL_DEPTH_COMPONENT16;
|
||||
|
||||
|
||||
if ((bits != 16) && (bits != 24) && (bits != 32)) bits = 16;
|
||||
|
||||
|
||||
if (bits == 24)
|
||||
{
|
||||
#if defined(GRAPHICS_API_OPENGL_33)
|
||||
|
|
@ -2006,7 +2006,7 @@ unsigned int rlLoadTextureDepth(int width, int height, int bits, bool useRenderB
|
|||
if (maxDepthBits >= 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
if (bits == 32)
|
||||
{
|
||||
#if defined(GRAPHICS_API_OPENGL_33)
|
||||
|
|
@ -2021,7 +2021,7 @@ unsigned int rlLoadTextureDepth(int width, int height, int bits, bool useRenderB
|
|||
glGenTextures(1, &id);
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL);
|
||||
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
|
|
@ -2036,10 +2036,10 @@ unsigned int rlLoadTextureDepth(int width, int height, int bits, bool useRenderB
|
|||
glGenRenderbuffers(1, &id);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, id);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height);
|
||||
|
||||
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
}
|
||||
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
|
|
@ -2053,7 +2053,7 @@ unsigned int rlLoadTextureCubemap(void *data, int size, int format)
|
|||
|
||||
glGenTextures(1, &cubemapId);
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapId);
|
||||
|
||||
|
||||
unsigned int glInternalFormat, glFormat, glType;
|
||||
rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType);
|
||||
|
||||
|
|
@ -2084,7 +2084,7 @@ unsigned int rlLoadTextureCubemap(void *data, int size, int format)
|
|||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set cubemap texture sampling parameters
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
|
@ -2178,14 +2178,14 @@ void rlUnloadTexture(unsigned int id)
|
|||
RenderTexture2D rlLoadRenderTexture(int width, int height, int format, int depthBits, bool useDepthTexture)
|
||||
{
|
||||
RenderTexture2D target = { 0 };
|
||||
|
||||
|
||||
if (useDepthTexture && texDepthSupported) target.depthTexture = true;
|
||||
|
||||
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
|
||||
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
|
||||
// Create the framebuffer object
|
||||
glGenFramebuffers(1, &target.id);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, target.id);
|
||||
|
||||
|
||||
// Create fbo color texture attachment
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
if ((format != -1) && (format < COMPRESSED_DXT1_RGB))
|
||||
|
|
@ -2198,7 +2198,7 @@ RenderTexture2D rlLoadRenderTexture(int width, int height, int format, int depth
|
|||
target.texture.mipmaps = 1;
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Create fbo depth renderbuffer/texture
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
if (depthBits > 0)
|
||||
|
|
@ -2206,11 +2206,11 @@ RenderTexture2D rlLoadRenderTexture(int width, int height, int format, int depth
|
|||
target.depth.id = rlLoadTextureDepth(width, height, depthBits, !useDepthTexture);
|
||||
target.depth.width = width;
|
||||
target.depth.height = height;
|
||||
target.depth.format = 19; //DEPTH_COMPONENT_24BIT ?
|
||||
target.depth.format = 19; //DEPTH_COMPONENT_24BIT?
|
||||
target.depth.mipmaps = 1;
|
||||
}
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
|
||||
|
||||
// Attach color texture and depth renderbuffer to FBO
|
||||
//-----------------------------------------------------------------------------------------------------
|
||||
rlRenderTextureAttach(target, target.texture.id, 0); // COLOR attachment
|
||||
|
|
@ -2235,12 +2235,12 @@ void rlRenderTextureAttach(RenderTexture2D target, unsigned int id, int attachTy
|
|||
glBindFramebuffer(GL_FRAMEBUFFER, target.id);
|
||||
|
||||
if (attachType == 0) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0);
|
||||
else if (attachType == 1)
|
||||
else if (attachType == 1)
|
||||
{
|
||||
if (target.depthTexture) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, id, 0);
|
||||
else glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, id);
|
||||
}
|
||||
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
|
|
@ -2248,7 +2248,7 @@ void rlRenderTextureAttach(RenderTexture2D target, unsigned int id, int attachTy
|
|||
bool rlRenderTextureComplete(RenderTexture target)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, target.id);
|
||||
|
||||
|
||||
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
|
||||
if (status != GL_FRAMEBUFFER_COMPLETE)
|
||||
|
|
@ -2264,9 +2264,9 @@ bool rlRenderTextureComplete(RenderTexture target)
|
|||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
|
||||
return (status == GL_FRAMEBUFFER_COMPLETE);
|
||||
}
|
||||
|
||||
|
|
@ -2349,7 +2349,7 @@ void rlLoadMesh(Mesh *mesh, bool dynamic)
|
|||
TraceLog(LOG_WARNING, "Trying to re-load an already loaded mesh");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
mesh->vaoId = 0; // Vertex Array Object
|
||||
mesh->vboId[0] = 0; // Vertex positions VBO
|
||||
mesh->vboId[1] = 0; // Vertex texcoords VBO
|
||||
|
|
@ -2766,7 +2766,7 @@ unsigned char *rlReadScreenPixels(int width, int height)
|
|||
for (int x = 0; x < (width*4); x++)
|
||||
{
|
||||
imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line
|
||||
|
||||
|
||||
// Set alpha component value to 255 (no trasparent image retrieval)
|
||||
// NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it!
|
||||
if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255;
|
||||
|
|
@ -2822,7 +2822,7 @@ void *rlReadTexturePixels(Texture2D texture)
|
|||
// We are using Option 1, just need to care for texture format on retrieval
|
||||
// NOTE: This behaviour could be conditioned by graphic driver...
|
||||
RenderTexture2D fbo = rlLoadRenderTexture(texture.width, texture.height, UNCOMPRESSED_R8G8B8A8, 16, false);
|
||||
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo.id);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
|
|
@ -2836,7 +2836,7 @@ void *rlReadTexturePixels(Texture2D texture)
|
|||
// Get OpenGL internal formats and data type from our texture format
|
||||
unsigned int glInternalFormat, glFormat, glType;
|
||||
rlGetGlTextureFormats(texture.format, &glInternalFormat, &glFormat, &glType);
|
||||
|
||||
|
||||
// NOTE: We read data as RGBA because FBO texture is configured as RGBA, despite binding a RGB texture...
|
||||
glReadPixels(0, 0, texture.width, texture.height, glFormat, glType, pixels);
|
||||
|
||||
|
|
@ -3064,7 +3064,7 @@ void SetShaderValueV(Shader shader, int uniformLoc, const void *value, int unifo
|
|||
case UNIFORM_SAMPLER2D: glUniform1iv(uniformLoc, count, (int *)value); break;
|
||||
default: TraceLog(LOG_WARNING, "Shader uniform could not be set data type not recognized");
|
||||
}
|
||||
|
||||
|
||||
//glUseProgram(0); // Avoid reseting current shader program, in case other uniforms are set
|
||||
#endif
|
||||
}
|
||||
|
|
@ -3143,7 +3143,7 @@ Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size)
|
|||
// NOTE: Faces are stored as 32 bit floating point values
|
||||
glGenTextures(1, &cubemap.id);
|
||||
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap.id);
|
||||
for (unsigned int i = 0; i < 6; i++)
|
||||
for (unsigned int i = 0; i < 6; i++)
|
||||
{
|
||||
#if defined(GRAPHICS_API_OPENGL_33)
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB32F, size, size, 0, GL_RGB, GL_FLOAT, NULL);
|
||||
|
|
@ -3151,7 +3151,7 @@ Texture2D GenTextureCubemap(Shader shader, Texture2D skyHDR, int size)
|
|||
if (texFloatSupported) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, size, size, 0, GL_RGB, GL_FLOAT, NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
#if defined(GRAPHICS_API_OPENGL_33)
|
||||
|
|
@ -3231,7 +3231,7 @@ Texture2D GenTextureIrradiance(Shader shader, Texture2D cubemap, int size)
|
|||
{
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL);
|
||||
}
|
||||
|
||||
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
|
|
@ -3309,7 +3309,7 @@ Texture2D GenTexturePrefilter(Shader shader, Texture2D cubemap, int size)
|
|||
{
|
||||
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB16F, size, size, 0, GL_RGB, GL_FLOAT, NULL);
|
||||
}
|
||||
|
||||
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
|
|
@ -3417,7 +3417,7 @@ Texture2D GenTextureBRDF(Shader shader, int size)
|
|||
|
||||
// Unbind framebuffer and textures
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
|
||||
// Unload framebuffer but keep color texture
|
||||
glDeleteRenderbuffers(1, &rbo);
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
|
|
@ -3464,7 +3464,7 @@ void EndBlendMode(void)
|
|||
void BeginScissorMode(int x, int y, int width, int height)
|
||||
{
|
||||
rlglDraw(); // Force drawing elements
|
||||
|
||||
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
glScissor(x, screenHeight - (y + height), width, height);
|
||||
}
|
||||
|
|
@ -3473,7 +3473,7 @@ void BeginScissorMode(int x, int y, int width, int height)
|
|||
void EndScissorMode(void)
|
||||
{
|
||||
rlglDraw(); // Force drawing elements
|
||||
|
||||
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
|
|
@ -4050,7 +4050,7 @@ static void LoadBuffersDefault(void)
|
|||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(short)*6*MAX_BATCH_ELEMENTS, vertexData[i].indices, GL_STATIC_DRAW);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
TraceLog(LOG_INFO, "Internal buffers uploaded successfully (GPU)");
|
||||
|
||||
// Unbind the current VAO
|
||||
|
|
@ -4073,7 +4073,7 @@ static void UpdateBuffersDefault(void)
|
|||
glBindBuffer(GL_ARRAY_BUFFER, vertexData[currentBuffer].vboId[0]);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*3*vertexData[currentBuffer].vCounter, vertexData[currentBuffer].vertices);
|
||||
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_BATCH_ELEMENTS, vertexData[currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer
|
||||
|
||||
|
||||
// Texture coordinates buffer
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vertexData[currentBuffer].vboId[1]);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*2*vertexData[currentBuffer].vCounter, vertexData[currentBuffer].texcoords);
|
||||
|
|
@ -4083,13 +4083,13 @@ static void UpdateBuffersDefault(void)
|
|||
glBindBuffer(GL_ARRAY_BUFFER, vertexData[currentBuffer].vboId[2]);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(unsigned char)*4*vertexData[currentBuffer].vCounter, vertexData[currentBuffer].colors);
|
||||
//glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*MAX_BATCH_ELEMENTS, vertexData[currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer
|
||||
|
||||
|
||||
// NOTE: glMapBuffer() causes sync issue.
|
||||
// If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job.
|
||||
// If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job.
|
||||
// To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer().
|
||||
// If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new
|
||||
// allocated pointer immediately even if GPU is still working with the previous data.
|
||||
|
||||
|
||||
// Another option: map the buffer object into client's memory
|
||||
// Probably this code could be moved somewhere else...
|
||||
// vertexData[currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE);
|
||||
|
|
@ -4135,7 +4135,7 @@ static void DrawBuffersDefault(void)
|
|||
glUniform1i(currentShader.locs[LOC_MAP_DIFFUSE], 0);
|
||||
|
||||
// NOTE: Additional map textures not considered for default buffers drawing
|
||||
|
||||
|
||||
int vertexOffset = 0;
|
||||
|
||||
if (vaoSupported) glBindVertexArray(vertexData[currentBuffer].vaoId);
|
||||
|
|
@ -4160,7 +4160,7 @@ static void DrawBuffersDefault(void)
|
|||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
|
||||
|
||||
for (int i = 0; i < drawsCounter; i++)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, draws[i].textureId);
|
||||
|
|
@ -4170,7 +4170,7 @@ static void DrawBuffersDefault(void)
|
|||
{
|
||||
#if defined(GRAPHICS_API_OPENGL_33)
|
||||
// We need to define the number of indices to be processed: quadsCount*6
|
||||
// NOTE: The final parameter tells the GPU the offset in bytes from the
|
||||
// NOTE: The final parameter tells the GPU the offset in bytes from the
|
||||
// start of the index buffer to the location of the first index to process
|
||||
glDrawElements(GL_TRIANGLES, draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(sizeof(GLuint)*vertexOffset/4*6));
|
||||
#elif defined(GRAPHICS_API_OPENGL_ES2)
|
||||
|
|
@ -4216,7 +4216,7 @@ static void DrawBuffersDefault(void)
|
|||
}
|
||||
|
||||
drawsCounter = 1;
|
||||
|
||||
|
||||
// Change to next buffer in the list
|
||||
currentBuffer++;
|
||||
if (currentBuffer >= MAX_BATCH_BUFFERING) currentBuffer = 0;
|
||||
|
|
@ -4369,14 +4369,14 @@ static void GenDrawCube(void)
|
|||
static VrStereoConfig SetStereoConfig(VrDeviceInfo hmd, Shader distortion)
|
||||
{
|
||||
VrStereoConfig config = { 0 };
|
||||
|
||||
|
||||
// Initialize framebuffer and textures for stereo rendering
|
||||
// NOTE: Screen size should match HMD aspect ratio
|
||||
config.stereoFbo = rlLoadRenderTexture(screenWidth, screenHeight, UNCOMPRESSED_R8G8B8A8, 24, false);
|
||||
|
||||
|
||||
// Assign distortion shader
|
||||
config.distortionShader = distortion;
|
||||
|
||||
|
||||
// Compute aspect ratio
|
||||
float aspect = ((float)hmd.hResolution*0.5f)/(float)hmd.vResolution;
|
||||
|
||||
|
|
@ -4442,7 +4442,7 @@ static VrStereoConfig SetStereoConfig(VrDeviceInfo hmd, Shader distortion)
|
|||
SetShaderValue(config.distortionShader, GetShaderLocation(config.distortionShader, "hmdWarpParam"), hmd.lensDistortionValues, UNIFORM_VEC4);
|
||||
SetShaderValue(config.distortionShader, GetShaderLocation(config.distortionShader, "chromaAbParam"), hmd.chromaAbCorrection, UNIFORM_VEC4);
|
||||
#endif
|
||||
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
|
|
|||
14
src/shapes.c
14
src/shapes.c
|
|
@ -132,7 +132,7 @@ void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color)
|
|||
rlPushMatrix();
|
||||
rlTranslatef((float)startPos.x, (float)startPos.y, 0.0f);
|
||||
rlRotatef(RAD2DEG*angle, 0.0f, 0.0f, 1.0f);
|
||||
rlTranslatef(0, (thick > 1.0f) ? -thick/2.0f : -1.0f, 0.0f);
|
||||
rlTranslatef(0, (thick > 1.0f)? -thick/2.0f : -1.0f, 0.0f);
|
||||
|
||||
rlBegin(RL_QUADS);
|
||||
rlColor4ub(color.r, color.g, color.b, color.a);
|
||||
|
|
@ -143,7 +143,7 @@ void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color)
|
|||
|
||||
rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height);
|
||||
rlVertex2f(0.0f, thick);
|
||||
|
||||
|
||||
rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height);
|
||||
rlVertex2f(d, thick);
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ void DrawCircle(int centerX, int centerY, float radius, Color color)
|
|||
void DrawCircleSector(Vector2 center, float radius, int startAngle, int endAngle, Color color)
|
||||
{
|
||||
#define CIRCLE_SECTOR_LENGTH 10
|
||||
|
||||
|
||||
#if defined(SUPPORT_QUADS_DRAW_MODE)
|
||||
if (rlCheckBufferLimit(4*((360/CIRCLE_SECTOR_LENGTH)/2))) rlglDraw();
|
||||
|
||||
|
|
@ -307,10 +307,10 @@ void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color
|
|||
|
||||
rlTexCoord2f(recTexShapes.x/texShapes.width, recTexShapes.y/texShapes.height);
|
||||
rlVertex2f(0.0f, 0.0f);
|
||||
|
||||
|
||||
rlTexCoord2f(recTexShapes.x/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height);
|
||||
rlVertex2f(0.0f, rec.height);
|
||||
|
||||
|
||||
rlTexCoord2f((recTexShapes.x + recTexShapes.width)/texShapes.width, (recTexShapes.y + recTexShapes.height)/texShapes.height);
|
||||
rlVertex2f(rec.width, rec.height);
|
||||
|
||||
|
|
@ -644,7 +644,7 @@ bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec)
|
|||
if (dy <= (rec.height/2.0f)) { return true; }
|
||||
|
||||
float cornerDistanceSq = (dx - rec.width/2.0f)*(dx - rec.width/2.0f) +
|
||||
(dy - rec.height/2.0f)*(dy - rec.height/2.0f);
|
||||
(dy - rec.height/2.0f)*(dy - rec.height/2.0f);
|
||||
|
||||
return (cornerDistanceSq <= (radius*radius));
|
||||
}
|
||||
|
|
@ -742,7 +742,7 @@ static Texture2D GetShapesTexture(void)
|
|||
recTexShapes = (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 };
|
||||
#else
|
||||
texShapes = GetTextureDefault(); // Use default white texture
|
||||
recTexShapes = { 0.0f, 0.0f, 1.0f, 1.0f };
|
||||
recTexShapes = (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f };
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
124
src/text.c
124
src/text.c
|
|
@ -306,9 +306,10 @@ Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCou
|
|||
Font font = { 0 };
|
||||
|
||||
font.baseSize = fontSize;
|
||||
font.charsCount = (charsCount > 0) ? charsCount : 95;
|
||||
font.charsCount = (charsCount > 0)? charsCount : 95;
|
||||
font.chars = LoadFontData(fileName, font.baseSize, fontChars, font.charsCount, FONT_DEFAULT);
|
||||
|
||||
#if defined(SUPPORT_FILEFORMAT_TTF)
|
||||
if (font.chars != NULL)
|
||||
{
|
||||
Image atlas = GenImageFontAtlas(font.chars, font.charsCount, font.baseSize, 2, 0);
|
||||
|
|
@ -316,6 +317,9 @@ Font LoadFontEx(const char *fileName, int fontSize, int *fontChars, int charsCou
|
|||
UnloadImage(atlas);
|
||||
}
|
||||
else font = GetFontDefault();
|
||||
#else
|
||||
font = GetFontDefault();
|
||||
#endif
|
||||
|
||||
return font;
|
||||
}
|
||||
|
|
@ -426,6 +430,7 @@ Font LoadFontFromImage(Image image, Color key, int firstChar)
|
|||
spriteFont.chars[i].offsetX = 0;
|
||||
spriteFont.chars[i].offsetY = 0;
|
||||
spriteFont.chars[i].advanceX = 0;
|
||||
spriteFont.chars[i].data = NULL;
|
||||
}
|
||||
|
||||
spriteFont.baseSize = (int)spriteFont.chars[0].rec.height;
|
||||
|
|
@ -449,6 +454,7 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c
|
|||
|
||||
CharInfo *chars = NULL;
|
||||
|
||||
#if defined(SUPPORT_FILEFORMAT_TTF)
|
||||
// Load font data (including pixel data) from TTF file
|
||||
// NOTE: Loaded information should be enough to generate font image atlas,
|
||||
// using any packaging method
|
||||
|
|
@ -478,7 +484,7 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c
|
|||
stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap);
|
||||
|
||||
// In case no chars count provided, default to 95
|
||||
charsCount = (charsCount > 0) ? charsCount : 95;
|
||||
charsCount = (charsCount > 0)? charsCount : 95;
|
||||
|
||||
// Fill fontChars in case not provided externally
|
||||
// NOTE: By default we fill charsCount consecutevely, starting at 32 (Space)
|
||||
|
|
@ -506,6 +512,7 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c
|
|||
|
||||
if (type != FONT_SDF) chars[i].data = stbtt_GetCodepointBitmap(&fontInfo, scaleFactor, scaleFactor, ch, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY);
|
||||
else if (ch != 32) chars[i].data = stbtt_GetCodepointSDF(&fontInfo, scaleFactor, ch, SDF_CHAR_PADDING, SDF_ON_EDGE_VALUE, SDF_PIXEL_DIST_SCALE, &chw, &chh, &chars[i].offsetX, &chars[i].offsetY);
|
||||
else chars[i].data = NULL;
|
||||
|
||||
if (type == FONT_BITMAP)
|
||||
{
|
||||
|
|
@ -537,18 +544,22 @@ CharInfo *LoadFontData(const char *fileName, int fontSize, int *fontChars, int c
|
|||
if (genFontChars) free(fontChars);
|
||||
}
|
||||
else TraceLog(LOG_WARNING, "[%s] TTF file could not be opened", fileName);
|
||||
#else
|
||||
TraceLog(LOG_WARNING, "[%s] TTF support is disabled", fileName);
|
||||
#endif
|
||||
|
||||
return chars;
|
||||
}
|
||||
|
||||
// Generate image font atlas using chars info
|
||||
// NOTE: Packing method: 0-Default, 1-Skyline
|
||||
#if defined(SUPPORT_FILEFORMAT_TTF)
|
||||
Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int padding, int packMethod)
|
||||
{
|
||||
Image atlas = { 0 };
|
||||
|
||||
// In case no chars count provided we suppose default of 95
|
||||
charsCount = (charsCount > 0) ? charsCount : 95;
|
||||
charsCount = (charsCount > 0)? charsCount : 95;
|
||||
|
||||
// Calculate image size based on required pixel area
|
||||
// NOTE 1: Image is forced to be squared and POT... very conservative!
|
||||
|
|
@ -667,6 +678,7 @@ Image GenImageFontAtlas(CharInfo *chars, int charsCount, int fontSize, int paddi
|
|||
|
||||
return atlas;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Unload Font from GPU memory (VRAM)
|
||||
void UnloadFont(Font font)
|
||||
|
|
@ -674,6 +686,11 @@ void UnloadFont(Font font)
|
|||
// NOTE: Make sure spriteFont is not default font (fallback)
|
||||
if (font.texture.id != GetFontDefault().texture.id)
|
||||
{
|
||||
for (int i = 0; i < font.charsCount; i++)
|
||||
{
|
||||
if(font.chars[i].data != NULL)
|
||||
free(font.chars[i].data);
|
||||
}
|
||||
UnloadTexture(font.texture);
|
||||
free(font.chars);
|
||||
|
||||
|
|
@ -780,14 +797,14 @@ void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, f
|
|||
}
|
||||
|
||||
// Draw text using font inside rectangle limits
|
||||
void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint)
|
||||
void DrawTextRec(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint)
|
||||
{
|
||||
DrawTextRecEx(font, text, rec, fontSize, spacing, wordWrap, tint, 0, 0, WHITE, WHITE);
|
||||
}
|
||||
|
||||
// Draw text using font inside rectangle limits with support for text selection
|
||||
void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint,
|
||||
int selectStart, int selectLength, Color selectText, Color selectBack)
|
||||
void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, float spacing, bool wordWrap, Color tint,
|
||||
int selectStart, int selectLength, Color selectText, Color selectBack)
|
||||
{
|
||||
int length = strlen(text);
|
||||
int textOffsetX = 0; // Offset between characters
|
||||
|
|
@ -803,12 +820,12 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f
|
|||
int state = wordWrap? MEASURE_STATE : DRAW_STATE;
|
||||
int startLine = -1; // Index where to begin drawing (where a line begins)
|
||||
int endLine = -1; // Index where to stop drawing (where a line ends)
|
||||
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
int glyphWidth = 0;
|
||||
letter = (unsigned char)text[i];
|
||||
|
||||
|
||||
if (letter != '\n')
|
||||
{
|
||||
if ((unsigned char)text[i] == 0xc2) // UTF-8 encoding identification HACK!
|
||||
|
|
@ -826,41 +843,41 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f
|
|||
i++;
|
||||
}
|
||||
else index = GetGlyphIndex(font, (unsigned char)text[i]);
|
||||
|
||||
glyphWidth = (font.chars[index].advanceX == 0)?
|
||||
|
||||
glyphWidth = (font.chars[index].advanceX == 0)?
|
||||
(int)(font.chars[index].rec.width*scaleFactor + spacing):
|
||||
(int)(font.chars[index].advanceX*scaleFactor + spacing);
|
||||
}
|
||||
|
||||
|
||||
// NOTE: When wordWrap is ON we first measure how much of the text we can draw
|
||||
// before going outside of the `rec` container. We store this info inside
|
||||
// before going outside of the `rec` container. We store this info inside
|
||||
// `startLine` and `endLine` then we change states, draw the text between those two
|
||||
// variables then change states again and again recursively until the end of the text
|
||||
// (or until we get outside of the container).
|
||||
// When wordWrap is OFF we don't need the measure state so we go to the drawing
|
||||
// state immediately and begin drawing on the next line before we can get outside
|
||||
// When wordWrap is OFF we don't need the measure state so we go to the drawing
|
||||
// state immediately and begin drawing on the next line before we can get outside
|
||||
// the container.
|
||||
if (state == MEASURE_STATE)
|
||||
if (state == MEASURE_STATE)
|
||||
{
|
||||
if ((letter == ' ') || (letter == '\t') || (letter == '\n')) endLine = i;
|
||||
|
||||
if ((textOffsetX + glyphWidth + 1) >= rec.width)
|
||||
|
||||
if ((textOffsetX + glyphWidth + 1) >= rec.width)
|
||||
{
|
||||
endLine = (endLine < 1) ? i : endLine;
|
||||
endLine = (endLine < 1)? i : endLine;
|
||||
if (i == endLine) endLine -= 1;
|
||||
if ((startLine + 1) == endLine) endLine = i - 1;
|
||||
state = !state;
|
||||
}
|
||||
else if ((i + 1) == length)
|
||||
}
|
||||
else if ((i + 1) == length)
|
||||
{
|
||||
endLine = i;
|
||||
state = !state;
|
||||
}
|
||||
else if (letter == '\n')
|
||||
else if (letter == '\n')
|
||||
{
|
||||
state = !state;
|
||||
}
|
||||
|
||||
|
||||
if (state == DRAW_STATE)
|
||||
{
|
||||
textOffsetX = 0;
|
||||
|
|
@ -868,8 +885,8 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f
|
|||
glyphWidth = 0;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
if (letter == '\n')
|
||||
{
|
||||
|
|
@ -878,17 +895,17 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f
|
|||
textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor);
|
||||
textOffsetX = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!wordWrap && ((textOffsetX + glyphWidth + 1) >= rec.width))
|
||||
{
|
||||
textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor);
|
||||
textOffsetX = 0;
|
||||
}
|
||||
|
||||
if ((textOffsetY + (int)((font.baseSize + font.baseSize/2)*scaleFactor)) > rec.height) break;
|
||||
|
||||
|
||||
if ((textOffsetY + (int)(font.baseSize*scaleFactor)) > rec.height) break;
|
||||
|
||||
//draw selected
|
||||
bool isGlyphSelected = false;
|
||||
if ((selectStart >= 0) && (i >= selectStart) && (i < (selectStart + selectLength)))
|
||||
|
|
@ -897,7 +914,7 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f
|
|||
DrawRectangleRec(strec, selectBack);
|
||||
isGlyphSelected = true;
|
||||
}
|
||||
|
||||
|
||||
//draw glyph
|
||||
if ((letter != ' ') && (letter != '\t'))
|
||||
{
|
||||
|
|
@ -905,12 +922,12 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f
|
|||
(Rectangle){ rec.x + textOffsetX + font.chars[index].offsetX*scaleFactor,
|
||||
rec.y + textOffsetY + font.chars[index].offsetY*scaleFactor,
|
||||
font.chars[index].rec.width*scaleFactor,
|
||||
font.chars[index].rec.height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f,
|
||||
(!isGlyphSelected) ? tint : selectText);
|
||||
font.chars[index].rec.height*scaleFactor }, (Vector2){ 0, 0 }, 0.0f,
|
||||
(!isGlyphSelected)? tint : selectText);
|
||||
}
|
||||
}
|
||||
|
||||
if (wordWrap && (i == endLine))
|
||||
|
||||
if (wordWrap && (i == endLine))
|
||||
{
|
||||
textOffsetY += (int)((font.baseSize + font.baseSize/2)*scaleFactor);
|
||||
textOffsetX = 0;
|
||||
|
|
@ -920,7 +937,7 @@ void DrawTextRecEx(Font font, const char *text, Rectangle rec, float fontSize, f
|
|||
state = !state;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
textOffsetX += glyphWidth;
|
||||
}
|
||||
}
|
||||
|
|
@ -958,11 +975,11 @@ Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing
|
|||
|
||||
unsigned char letter = 0; // Current character
|
||||
int index = 0; // Index position in sprite font
|
||||
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
lenCounter++;
|
||||
|
||||
|
||||
if (text[i] != '\n')
|
||||
{
|
||||
if ((unsigned char)text[i] == 0xc2) // UTF-8 encoding identification
|
||||
|
|
@ -1095,7 +1112,7 @@ const char *TextSubtext(const char *text, int position, int length)
|
|||
const char *TextReplace(char *text, const char *replace, const char *by)
|
||||
{
|
||||
char *result;
|
||||
|
||||
|
||||
char *insertPoint; // Next insert point
|
||||
char *temp; // Temp pointer
|
||||
int replaceLen; // Replace string length of (the string to remove)
|
||||
|
|
@ -1153,7 +1170,7 @@ const char *TextInsert(const char *text, const char *insert, int position)
|
|||
for (int i = 0; i < position; i++) result[i] = text[i];
|
||||
for (int i = position; i < insertLen + position; i++) result[i] = insert[i];
|
||||
for (int i = (insertLen + position); i < (textLen + insertLen); i++) result[i] = text[i];
|
||||
|
||||
|
||||
result[textLen + insertLen] = '\0'; // Make sure text string is valid!
|
||||
|
||||
return result;
|
||||
|
|
@ -1164,7 +1181,7 @@ const char *TextInsert(const char *text, const char *insert, int position)
|
|||
const char *TextJoin(const char **textList, int count, const char *delimiter)
|
||||
{
|
||||
// TODO: Make sure joined text could fit inside MAX_TEXT_BUFFER_LENGTH
|
||||
|
||||
|
||||
static char text[MAX_TEXT_BUFFER_LENGTH] = { 0 };
|
||||
memset(text, 0, MAX_TEXT_BUFFER_LENGTH);
|
||||
|
||||
|
|
@ -1187,9 +1204,9 @@ const char **TextSplit(const char *text, char delimiter, int *count)
|
|||
// all used memory is static... it has some limitations:
|
||||
// 1. Maximum number of possible split strings is set by MAX_SUBSTRINGS_COUNT
|
||||
// 2. Maximum size of text to split is MAX_TEXT_BUFFER_LENGTH
|
||||
|
||||
|
||||
#define MAX_SUBSTRINGS_COUNT 64
|
||||
|
||||
|
||||
static const char *result[MAX_SUBSTRINGS_COUNT] = { NULL };
|
||||
static char buffer[MAX_TEXT_BUFFER_LENGTH] = { 0 };
|
||||
memset(buffer, 0, MAX_TEXT_BUFFER_LENGTH);
|
||||
|
|
@ -1198,7 +1215,7 @@ const char **TextSplit(const char *text, char delimiter, int *count)
|
|||
int counter = 1;
|
||||
|
||||
// Count how many substrings we have on text and point to every one
|
||||
for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++)
|
||||
for (int i = 0; i < MAX_TEXT_BUFFER_LENGTH; i++)
|
||||
{
|
||||
buffer[i] = text[i];
|
||||
if (buffer[i] == '\0') break;
|
||||
|
|
@ -1207,7 +1224,7 @@ const char **TextSplit(const char *text, char delimiter, int *count)
|
|||
buffer[i] = '\0'; // Set an end of string at this point
|
||||
result[counter] = buffer + i + 1;
|
||||
counter++;
|
||||
|
||||
|
||||
if (counter == MAX_SUBSTRINGS_COUNT) break;
|
||||
}
|
||||
}
|
||||
|
|
@ -1229,11 +1246,11 @@ void TextAppend(char *text, const char *append, int *position)
|
|||
int TextFindIndex(const char *text, const char *find)
|
||||
{
|
||||
int position = -1;
|
||||
|
||||
|
||||
char *ptr = strstr(text, find);
|
||||
|
||||
|
||||
if (ptr != NULL) position = ptr - text;
|
||||
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
|
|
@ -1304,10 +1321,10 @@ int TextToInteger(const char *text)
|
|||
{
|
||||
if ((text[i] > 47) && (text[i] < 58)) result += ((int)text[i] - 48)*units;
|
||||
else { result = -1; break; }
|
||||
|
||||
|
||||
units *= 10;
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -1376,10 +1393,10 @@ static Font LoadBMFont(const char *fileName)
|
|||
char *lastSlash = NULL;
|
||||
|
||||
lastSlash = strrchr(fileName, '/');
|
||||
if (lastSlash == NULL)
|
||||
{
|
||||
lastSlash = strrchr(fileName, '\\');
|
||||
}
|
||||
if (lastSlash == NULL)
|
||||
{
|
||||
lastSlash = strrchr(fileName, '\\');
|
||||
}
|
||||
|
||||
// NOTE: We need some extra space to avoid memory corruption on next allocations!
|
||||
texPath = malloc(strlen(fileName) - strlen(lastSlash) + strlen(texFileName) + 4);
|
||||
|
|
@ -1428,6 +1445,7 @@ static Font LoadBMFont(const char *fileName)
|
|||
font.chars[i].offsetX = charOffsetX;
|
||||
font.chars[i].offsetY = charOffsetY;
|
||||
font.chars[i].advanceX = charAdvanceX;
|
||||
font.chars[i].data = NULL;
|
||||
}
|
||||
|
||||
fclose(fntFile);
|
||||
|
|
|
|||
286
src/textures.c
286
src/textures.c
|
|
@ -182,7 +182,11 @@ Image LoadImage(const char *fileName)
|
|||
{
|
||||
Image image = { 0 };
|
||||
|
||||
#if defined(SUPPORT_FILEFORMAT_PNG)
|
||||
if ((IsFileExtension(fileName, ".png"))
|
||||
#else
|
||||
if ((false)
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_BMP)
|
||||
|| (IsFileExtension(fileName, ".bmp"))
|
||||
#endif
|
||||
|
|
@ -398,90 +402,6 @@ Texture2D LoadTextureFromImage(Image image)
|
|||
return texture;
|
||||
}
|
||||
|
||||
// Load cubemap from image, multiple image cubemap layouts supported
|
||||
TextureCubemap LoadTextureCubemap(Image image, int layoutType)
|
||||
{
|
||||
TextureCubemap cubemap = { 0 };
|
||||
|
||||
if (layoutType == CUBEMAP_AUTO_DETECT) // Try to automatically guess layout type
|
||||
{
|
||||
// Check image width/height to determine the type of cubemap provided
|
||||
if (image.width > image.height)
|
||||
{
|
||||
if ((image.width/6) == image.height) { layoutType = CUBEMAP_LINE_HORIZONTAL; cubemap.width = image.width/6; }
|
||||
else if ((image.width/4) == (image.height/3)) { layoutType = CUBEMAP_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; }
|
||||
else if (image.width >= (int)((float)image.height*1.85f)) { layoutType = CUBEMAP_PANORAMA; cubemap.width = image.width/4; }
|
||||
}
|
||||
else if (image.height > image.width)
|
||||
{
|
||||
if ((image.height/6) == image.width) { layoutType = CUBEMAP_LINE_VERTICAL; cubemap.width = image.height/6; }
|
||||
else if ((image.width/3) == (image.height/4)) { layoutType = CUBEMAP_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; }
|
||||
}
|
||||
|
||||
cubemap.height = cubemap.width;
|
||||
}
|
||||
|
||||
int size = cubemap.width;
|
||||
|
||||
if (layoutType != CUBEMAP_AUTO_DETECT)
|
||||
{
|
||||
//unsigned int dataSize = GetPixelDataSize(size, size, format);
|
||||
//void *facesData = malloc(size*size*dataSize*6); // Get memory for 6 faces in a column
|
||||
|
||||
Image faces = { 0 }; // Vertical column image
|
||||
Rectangle faceRecs[6] = { 0 }; // Face source rectangles
|
||||
for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, size, size };
|
||||
|
||||
if (layoutType == CUBEMAP_LINE_VERTICAL)
|
||||
{
|
||||
faces = image;
|
||||
for (int i = 0; i < 6; i++) faceRecs[i].y = size*i;
|
||||
}
|
||||
else if (layoutType == CUBEMAP_PANORAMA)
|
||||
{
|
||||
// TODO: Convert panorama image to square faces...
|
||||
}
|
||||
else
|
||||
{
|
||||
if (layoutType == CUBEMAP_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = size*i;
|
||||
else if (layoutType == CUBEMAP_CROSS_THREE_BY_FOUR)
|
||||
{
|
||||
faceRecs[0].x = size; faceRecs[0].y = size;
|
||||
faceRecs[1].x = size; faceRecs[1].y = 3*size;
|
||||
faceRecs[2].x = size; faceRecs[2].y = 0;
|
||||
faceRecs[3].x = size; faceRecs[3].y = 2*size;
|
||||
faceRecs[4].x = 0; faceRecs[4].y = size;
|
||||
faceRecs[5].x = 2*size; faceRecs[5].y = size;
|
||||
}
|
||||
else if (layoutType == CUBEMAP_CROSS_FOUR_BY_THREE)
|
||||
{
|
||||
faceRecs[0].x = 2*size; faceRecs[0].y = size;
|
||||
faceRecs[1].x = 0; faceRecs[1].y = size;
|
||||
faceRecs[2].x = size; faceRecs[2].y = 0;
|
||||
faceRecs[3].x = size; faceRecs[3].y = 2*size;
|
||||
faceRecs[4].x = size; faceRecs[4].y = size;
|
||||
faceRecs[5].x = 3*size; faceRecs[5].y = size;
|
||||
}
|
||||
|
||||
// Convert image data to 6 faces in a vertical column, that's the optimum layout for loading
|
||||
faces = GenImageColor(size, size*6, MAGENTA);
|
||||
ImageFormat(&faces, image.format);
|
||||
|
||||
// TODO: Image formating does not work with compressed textures!
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, size*i, size, size });
|
||||
|
||||
cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format);
|
||||
if (cubemap.id == 0) TraceLog(LOG_WARNING, "Cubemap image could not be loaded.");
|
||||
|
||||
UnloadImage(faces);
|
||||
}
|
||||
else TraceLog(LOG_WARNING, "Cubemap image layout can not be detected.");
|
||||
|
||||
return cubemap;
|
||||
}
|
||||
|
||||
// Load texture for rendering (framebuffer)
|
||||
// NOTE: Render texture is loaded by default with RGBA color attachment and depth RenderBuffer
|
||||
RenderTexture2D LoadRenderTexture(int width, int height)
|
||||
|
|
@ -668,7 +588,7 @@ Vector4 *GetImageDataNormalized(Image image)
|
|||
pixels[i].x = (float)((pixel & 0b1111100000000000) >> 11)*(1.0f/31);
|
||||
pixels[i].y = (float)((pixel & 0b0000011111000000) >> 6)*(1.0f/31);
|
||||
pixels[i].z = (float)((pixel & 0b0000000000111110) >> 1)*(1.0f/31);
|
||||
pixels[i].w = ((pixel & 0b0000000000000001) == 0) ? 0.0f : 1.0f;
|
||||
pixels[i].w = ((pixel & 0b0000000000000001) == 0)? 0.0f : 1.0f;
|
||||
|
||||
} break;
|
||||
case UNCOMPRESSED_R5G6B5:
|
||||
|
|
@ -825,14 +745,27 @@ void ExportImage(Image image, const char *fileName)
|
|||
{
|
||||
int success = 0;
|
||||
|
||||
#if defined(SUPPORT_IMAGE_EXPORT)
|
||||
// NOTE: Getting Color array as RGBA unsigned char values
|
||||
unsigned char *imgData = (unsigned char *)GetImageData(image);
|
||||
|
||||
#if defined(SUPPORT_FILEFORMAT_PNG)
|
||||
if (IsFileExtension(fileName, ".png")) success = stbi_write_png(fileName, image.width, image.height, 4, imgData, image.width*4);
|
||||
#else
|
||||
if (false) {}
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_BMP)
|
||||
else if (IsFileExtension(fileName, ".bmp")) success = stbi_write_bmp(fileName, image.width, image.height, 4, imgData);
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_TGA)
|
||||
else if (IsFileExtension(fileName, ".tga")) success = stbi_write_tga(fileName, image.width, image.height, 4, imgData);
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_JPG)
|
||||
else if (IsFileExtension(fileName, ".jpg")) success = stbi_write_jpg(fileName, image.width, image.height, 4, imgData, 80); // JPG quality: between 1 and 100
|
||||
#endif
|
||||
#if defined(SUPPORT_FILEFORMAT_KTX)
|
||||
else if (IsFileExtension(fileName, ".ktx")) success = SaveKTX(image, fileName);
|
||||
#endif
|
||||
else if (IsFileExtension(fileName, ".raw"))
|
||||
{
|
||||
// Export raw pixel data (without header)
|
||||
|
|
@ -842,10 +775,11 @@ void ExportImage(Image image, const char *fileName)
|
|||
fclose(rawFile);
|
||||
}
|
||||
|
||||
free(imgData);
|
||||
#endif
|
||||
|
||||
if (success != 0) TraceLog(LOG_INFO, "Image exported successfully: %s", fileName);
|
||||
else TraceLog(LOG_WARNING, "Image could not be exported.");
|
||||
|
||||
free(imgData);
|
||||
}
|
||||
|
||||
// Export image as code file (.h) defining an array of bytes
|
||||
|
|
@ -880,7 +814,7 @@ void ExportImageAsCode(Image image, const char *fileName)
|
|||
fprintf(txtFile, "#define %s_FORMAT %i // raylib internal pixel format\n\n", varFileName, image.format);
|
||||
|
||||
fprintf(txtFile, "static unsigned char %s_DATA[%i] = { ", varFileName, dataSize);
|
||||
for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0) ? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]);
|
||||
for (int i = 0; i < dataSize - 1; i++) fprintf(txtFile, ((i%BYTES_TEXT_PER_LINE == 0)? "0x%x,\n" : "0x%x, "), ((unsigned char *)image.data)[i]);
|
||||
fprintf(txtFile, "0x%x };\n", ((unsigned char *)image.data)[dataSize - 1]);
|
||||
|
||||
fclose(txtFile);
|
||||
|
|
@ -1050,7 +984,7 @@ void ImageFormat(Image *image, int newFormat)
|
|||
r = (unsigned char)(round(pixels[i].x*31.0f));
|
||||
g = (unsigned char)(round(pixels[i].y*31.0f));
|
||||
b = (unsigned char)(round(pixels[i].z*31.0f));
|
||||
a = (pixels[i].w > ((float)ALPHA_THRESHOLD/255.0f)) ? 1 : 0;
|
||||
a = (pixels[i].w > ((float)ALPHA_THRESHOLD/255.0f))? 1 : 0;
|
||||
|
||||
((unsigned short *)image->data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1 | (unsigned short)a;
|
||||
}
|
||||
|
|
@ -1133,7 +1067,9 @@ void ImageFormat(Image *image, int newFormat)
|
|||
if (image->mipmaps > 1)
|
||||
{
|
||||
image->mipmaps = 1;
|
||||
#if defined(SUPPORT_IMAGE_MANIPULATION)
|
||||
if (image->data != NULL) ImageMipmaps(image);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else TraceLog(LOG_WARNING, "Image data format is compressed, can not be converted");
|
||||
|
|
@ -1202,38 +1138,6 @@ void ImageAlphaClear(Image *image, Color color, float threshold)
|
|||
ImageFormat(image, prevFormat);
|
||||
}
|
||||
|
||||
// Crop image depending on alpha value
|
||||
void ImageAlphaCrop(Image *image, float threshold)
|
||||
{
|
||||
Color *pixels = GetImageData(*image);
|
||||
|
||||
int xMin = 65536; // Define a big enough number
|
||||
int xMax = 0;
|
||||
int yMin = 65536;
|
||||
int yMax = 0;
|
||||
|
||||
for (int y = 0; y < image->height; y++)
|
||||
{
|
||||
for (int x = 0; x < image->width; x++)
|
||||
{
|
||||
if (pixels[y*image->width + x].a > (unsigned char)(threshold*255.0f))
|
||||
{
|
||||
if (x < xMin) xMin = x;
|
||||
if (x > xMax) xMax = x;
|
||||
if (y < yMin) yMin = y;
|
||||
if (y > yMax) yMax = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle crop = { xMin, yMin, (xMax + 1) - xMin, (yMax + 1) - yMin };
|
||||
|
||||
free(pixels);
|
||||
|
||||
// Check for not empty image brefore cropping
|
||||
if (!((xMax < xMin) || (yMax < yMin))) ImageCrop(image, crop);
|
||||
}
|
||||
|
||||
// Premultiply alpha channel
|
||||
void ImageAlphaPremultiply(Image *image)
|
||||
{
|
||||
|
|
@ -1259,6 +1163,90 @@ void ImageAlphaPremultiply(Image *image)
|
|||
|
||||
|
||||
#if defined(SUPPORT_IMAGE_MANIPULATION)
|
||||
// Load cubemap from image, multiple image cubemap layouts supported
|
||||
TextureCubemap LoadTextureCubemap(Image image, int layoutType)
|
||||
{
|
||||
TextureCubemap cubemap = { 0 };
|
||||
|
||||
if (layoutType == CUBEMAP_AUTO_DETECT) // Try to automatically guess layout type
|
||||
{
|
||||
// Check image width/height to determine the type of cubemap provided
|
||||
if (image.width > image.height)
|
||||
{
|
||||
if ((image.width/6) == image.height) { layoutType = CUBEMAP_LINE_HORIZONTAL; cubemap.width = image.width/6; }
|
||||
else if ((image.width/4) == (image.height/3)) { layoutType = CUBEMAP_CROSS_FOUR_BY_THREE; cubemap.width = image.width/4; }
|
||||
else if (image.width >= (int)((float)image.height*1.85f)) { layoutType = CUBEMAP_PANORAMA; cubemap.width = image.width/4; }
|
||||
}
|
||||
else if (image.height > image.width)
|
||||
{
|
||||
if ((image.height/6) == image.width) { layoutType = CUBEMAP_LINE_VERTICAL; cubemap.width = image.height/6; }
|
||||
else if ((image.width/3) == (image.height/4)) { layoutType = CUBEMAP_CROSS_THREE_BY_FOUR; cubemap.width = image.width/3; }
|
||||
}
|
||||
|
||||
cubemap.height = cubemap.width;
|
||||
}
|
||||
|
||||
int size = cubemap.width;
|
||||
|
||||
if (layoutType != CUBEMAP_AUTO_DETECT)
|
||||
{
|
||||
//unsigned int dataSize = GetPixelDataSize(size, size, format);
|
||||
//void *facesData = malloc(size*size*dataSize*6); // Get memory for 6 faces in a column
|
||||
|
||||
Image faces = { 0 }; // Vertical column image
|
||||
Rectangle faceRecs[6] = { 0 }; // Face source rectangles
|
||||
for (int i = 0; i < 6; i++) faceRecs[i] = (Rectangle){ 0, 0, size, size };
|
||||
|
||||
if (layoutType == CUBEMAP_LINE_VERTICAL)
|
||||
{
|
||||
faces = image;
|
||||
for (int i = 0; i < 6; i++) faceRecs[i].y = size*i;
|
||||
}
|
||||
else if (layoutType == CUBEMAP_PANORAMA)
|
||||
{
|
||||
// TODO: Convert panorama image to square faces...
|
||||
}
|
||||
else
|
||||
{
|
||||
if (layoutType == CUBEMAP_LINE_HORIZONTAL) for (int i = 0; i < 6; i++) faceRecs[i].x = size*i;
|
||||
else if (layoutType == CUBEMAP_CROSS_THREE_BY_FOUR)
|
||||
{
|
||||
faceRecs[0].x = size; faceRecs[0].y = size;
|
||||
faceRecs[1].x = size; faceRecs[1].y = 3*size;
|
||||
faceRecs[2].x = size; faceRecs[2].y = 0;
|
||||
faceRecs[3].x = size; faceRecs[3].y = 2*size;
|
||||
faceRecs[4].x = 0; faceRecs[4].y = size;
|
||||
faceRecs[5].x = 2*size; faceRecs[5].y = size;
|
||||
}
|
||||
else if (layoutType == CUBEMAP_CROSS_FOUR_BY_THREE)
|
||||
{
|
||||
faceRecs[0].x = 2*size; faceRecs[0].y = size;
|
||||
faceRecs[1].x = 0; faceRecs[1].y = size;
|
||||
faceRecs[2].x = size; faceRecs[2].y = 0;
|
||||
faceRecs[3].x = size; faceRecs[3].y = 2*size;
|
||||
faceRecs[4].x = size; faceRecs[4].y = size;
|
||||
faceRecs[5].x = 3*size; faceRecs[5].y = size;
|
||||
}
|
||||
|
||||
// Convert image data to 6 faces in a vertical column, that's the optimum layout for loading
|
||||
faces = GenImageColor(size, size*6, MAGENTA);
|
||||
ImageFormat(&faces, image.format);
|
||||
|
||||
// TODO: Image formating does not work with compressed textures!
|
||||
}
|
||||
|
||||
for (int i = 0; i < 6; i++) ImageDraw(&faces, image, faceRecs[i], (Rectangle){ 0, size*i, size, size });
|
||||
|
||||
cubemap.id = rlLoadTextureCubemap(faces.data, size, faces.format);
|
||||
if (cubemap.id == 0) TraceLog(LOG_WARNING, "Cubemap image could not be loaded.");
|
||||
|
||||
UnloadImage(faces);
|
||||
}
|
||||
else TraceLog(LOG_WARNING, "Cubemap image layout can not be detected.");
|
||||
|
||||
return cubemap;
|
||||
}
|
||||
|
||||
// Crop an image to area defined by a rectangle
|
||||
// NOTE: Security checks are performed in case rectangle goes out of bounds
|
||||
void ImageCrop(Image *image, Rectangle crop)
|
||||
|
|
@ -1309,6 +1297,38 @@ void ImageCrop(Image *image, Rectangle crop)
|
|||
}
|
||||
}
|
||||
|
||||
// Crop image depending on alpha value
|
||||
void ImageAlphaCrop(Image *image, float threshold)
|
||||
{
|
||||
Color *pixels = GetImageData(*image);
|
||||
|
||||
int xMin = 65536; // Define a big enough number
|
||||
int xMax = 0;
|
||||
int yMin = 65536;
|
||||
int yMax = 0;
|
||||
|
||||
for (int y = 0; y < image->height; y++)
|
||||
{
|
||||
for (int x = 0; x < image->width; x++)
|
||||
{
|
||||
if (pixels[y*image->width + x].a > (unsigned char)(threshold*255.0f))
|
||||
{
|
||||
if (x < xMin) xMin = x;
|
||||
if (x > xMax) xMax = x;
|
||||
if (y < yMin) yMin = y;
|
||||
if (y > yMax) yMax = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle crop = { xMin, yMin, (xMax + 1) - xMin, (yMax + 1) - yMin };
|
||||
|
||||
free(pixels);
|
||||
|
||||
// Check for not empty image brefore cropping
|
||||
if (!((xMax < xMin) || (yMax < yMin))) ImageCrop(image, crop);
|
||||
}
|
||||
|
||||
// Resize and image to new size
|
||||
// NOTE: Uses stb default scaling filters (both bicubic):
|
||||
// STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM
|
||||
|
|
@ -1611,7 +1631,7 @@ Color *ImageExtractPalette(Image image, int maxPaletteSize, int *extractCount)
|
|||
if (palCount >= maxPaletteSize)
|
||||
{
|
||||
i = image.width*image.height; // Finish palette get
|
||||
printf("WARNING: Image palette is greater than %i colors!\n", maxPaletteSize);
|
||||
TraceLog(LOG_WARNING, "Image palette is greater than %i colors!", maxPaletteSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2146,7 +2166,6 @@ void ImageColorReplace(Image *image, Color color, Color replace)
|
|||
}
|
||||
#endif // SUPPORT_IMAGE_MANIPULATION
|
||||
|
||||
#if defined(SUPPORT_IMAGE_GENERATION)
|
||||
// Generate image: plain color
|
||||
Image GenImageColor(int width, int height, Color color)
|
||||
{
|
||||
|
|
@ -2161,6 +2180,7 @@ Image GenImageColor(int width, int height, Color color)
|
|||
return image;
|
||||
}
|
||||
|
||||
#if defined(SUPPORT_IMAGE_GENERATION)
|
||||
// Generate image: vertical gradient
|
||||
Image GenImageGradientV(int width, int height, Color top, Color bottom)
|
||||
{
|
||||
|
|
@ -2211,7 +2231,7 @@ Image GenImageGradientH(int width, int height, Color left, Color right)
|
|||
Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer)
|
||||
{
|
||||
Color *pixels = (Color *)malloc(width*height*sizeof(Color));
|
||||
float radius = (width < height) ? (float)width/2.0f : (float)height/2.0f;
|
||||
float radius = (width < height)? (float)width/2.0f : (float)height/2.0f;
|
||||
|
||||
float centerX = (float)width/2.0f;
|
||||
float centerY = (float)height/2.0f;
|
||||
|
|
@ -2509,7 +2529,7 @@ void DrawTextureQuad(Texture2D texture, Vector2 tiling, Vector2 offset, Rectangl
|
|||
{
|
||||
Rectangle source = { offset.x*texture.width, offset.y*texture.height, tiling.x*texture.width, tiling.y*texture.height };
|
||||
Vector2 origin = { 0.0f, 0.0f };
|
||||
|
||||
|
||||
DrawTexturePro(texture, source, quad, origin, 0.0f, tint);
|
||||
}
|
||||
|
||||
|
|
@ -3173,25 +3193,27 @@ static int SaveKTX(Image image, const char *fileName)
|
|||
{
|
||||
KTXHeader ktxHeader;
|
||||
|
||||
// KTX identifier (v2.2)
|
||||
// KTX identifier (v1.1)
|
||||
//unsigned char id[12] = { '«', 'K', 'T', 'X', ' ', '1', '1', '»', '\r', '\n', '\x1A', '\n' };
|
||||
//unsigned char id[12] = { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A };
|
||||
|
||||
const char ktxIdentifier[12] = { 0xAB, 'K', 'T', 'X', ' ', '1', '1', 0xBB, '\r', '\n', 0x1A, '\n' };
|
||||
|
||||
// Get the image header
|
||||
strcpy(ktxHeader.id, "«KTX 11»\r\n\x1A\n"); // KTX 1.1 signature
|
||||
strncpy(ktxHeader.id, ktxIdentifier, 12); // KTX 1.1 signature
|
||||
ktxHeader.endianness = 0;
|
||||
ktxHeader.glType = 0; // Obtained from image.format
|
||||
ktxHeader.glType = 0; // Obtained from image.format
|
||||
ktxHeader.glTypeSize = 1;
|
||||
ktxHeader.glFormat = 0; // Obtained from image.format
|
||||
ktxHeader.glInternalFormat = 0; // Obtained from image.format
|
||||
ktxHeader.glFormat = 0; // Obtained from image.format
|
||||
ktxHeader.glInternalFormat = 0; // Obtained from image.format
|
||||
ktxHeader.glBaseInternalFormat = 0;
|
||||
ktxHeader.width = image.width;
|
||||
ktxHeader.height = image.height;
|
||||
ktxHeader.depth = 0;
|
||||
ktxHeader.elements = 0;
|
||||
ktxHeader.faces = 1;
|
||||
ktxHeader.mipmapLevels = image.mipmaps; // If it was 0, it means mipmaps should be generated on loading (not for compressed formats)
|
||||
ktxHeader.keyValueDataSize = 0; // No extra data after the header
|
||||
ktxHeader.mipmapLevels = image.mipmaps; // If it was 0, it means mipmaps should be generated on loading (not for compressed formats)
|
||||
ktxHeader.keyValueDataSize = 0; // No extra data after the header
|
||||
|
||||
rlGetGlTextureFormats(image.format, &ktxHeader.glInternalFormat, &ktxHeader.glFormat, &ktxHeader.glType); // rlgl module function
|
||||
ktxHeader.glBaseInternalFormat = ktxHeader.glFormat; // KTX 1.1 only
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user