diff options
| author | Ray <raysan5@gmail.com> | 2015-07-03 20:49:16 +0200 |
|---|---|---|
| committer | Ray <raysan5@gmail.com> | 2015-07-03 20:49:16 +0200 |
| commit | a59433e7a3b22d5ded7505689fb44f1927194077 (patch) | |
| tree | 4e75d2a6213b0b799b3ea12c677986158905bff4 /src | |
| parent | cae6f3c613794e5e02233aa1590eb1ea9a8239cd (diff) | |
| parent | cd08ae7b858b62b341aebc86a7a4e915447c9423 (diff) | |
| download | raylib-a59433e7a3b22d5ded7505689fb44f1927194077.tar.gz raylib-a59433e7a3b22d5ded7505689fb44f1927194077.zip | |
Merge pull request #23 from raysan5/develop
Integrate develop branch
Diffstat (limited to 'src')
| -rw-r--r-- | src/audio.c | 23 | ||||
| -rw-r--r-- | src/camera.c | 455 | ||||
| -rw-r--r-- | src/core.c | 534 | ||||
| -rw-r--r-- | src/gestures.c | 645 | ||||
| -rw-r--r-- | src/makefile | 15 | ||||
| -rw-r--r-- | src/models.c | 888 | ||||
| -rw-r--r-- | src/raylib.h | 189 | ||||
| -rw-r--r-- | src/raymath.c | 288 | ||||
| -rw-r--r-- | src/raymath.h | 35 | ||||
| -rw-r--r-- | src/rlgl.c | 1773 | ||||
| -rw-r--r-- | src/rlgl.h | 77 | ||||
| -rw-r--r-- | src/shapes.c | 46 | ||||
| -rw-r--r-- | src/simple150.frag | 14 | ||||
| -rw-r--r-- | src/simple150.vert | 21 | ||||
| -rw-r--r-- | src/stb_image.h | 206 | ||||
| -rw-r--r-- | src/stb_image_write.h | 289 | ||||
| -rw-r--r-- | src/stb_rect_pack.h | 560 | ||||
| -rw-r--r-- | src/stb_truetype.h | 2632 | ||||
| -rw-r--r-- | src/stb_vorbis.c | 6 | ||||
| -rw-r--r-- | src/stb_vorbis.h | 11 | ||||
| -rw-r--r-- | src/text.c | 263 | ||||
| -rw-r--r-- | src/textures.c | 1060 | ||||
| -rw-r--r-- | src/utils.c | 16 |
23 files changed, 8124 insertions, 1922 deletions
diff --git a/src/audio.c b/src/audio.c index 40c24895..9653c091 100644 --- a/src/audio.c +++ b/src/audio.c @@ -39,7 +39,8 @@ #include "utils.h" // rRES data decompression utility function // NOTE: Includes Android fopen function map -#include "stb_vorbis.h" // OGG loading functions +//#define STB_VORBIS_HEADER_ONLY +#include "stb_vorbis.h" // OGG loading functions //---------------------------------------------------------------------------------- // Defines and Macros @@ -49,7 +50,7 @@ #if defined(PLATFORM_RPI) // NOTE: On RPI should be lower to avoid frame-stalls #define MUSIC_BUFFER_SIZE 4096*2 // PCM data buffer (short) - 16Kb (RPI) -#else +#else // NOTE: On HTML5 (emscripten) this is allocated on heap, by default it's only 16MB!...just take care... #define MUSIC_BUFFER_SIZE 4096*8 // PCM data buffer (short) - 64Kb #endif @@ -201,7 +202,7 @@ Sound LoadSound(char *fileName) // Attach sound buffer to source alSourcei(source, AL_BUFFER, buffer); - + TraceLog(INFO, "[%s] Sound file loaded successfully (SampleRate: %i, BitRate: %i, Channels: %i)", fileName, wave.sampleRate, wave.bitsPerSample, wave.channels); // Unallocate WAV data @@ -283,7 +284,7 @@ Sound LoadSoundFromRES(const char *rresName, int resId) FILE *rresFile = fopen(rresName, "rb"); - if (rresFile == NULL) + if (rresFile == NULL) { TraceLog(WARNING, "[%s] rRES raylib resource file could not be opened", rresName); } @@ -378,7 +379,7 @@ Sound LoadSoundFromRES(const char *rresName, int resId) // Attach sound buffer to source alSourcei(source, AL_BUFFER, buffer); - + TraceLog(INFO, "[%s] Sound loaded successfully from resource (SampleRate: %i, BitRate: %i, Channels: %i)", rresName, wave.sampleRate, wave.bitsPerSample, wave.channels); // Unallocate WAV data @@ -574,6 +575,7 @@ void PauseMusicStream(void) { TraceLog(INFO, "Pausing music stream"); alSourcePause(currentMusic.source); + musicEnabled = false; } } @@ -581,10 +583,14 @@ void PauseMusicStream(void) void ResumeMusicStream(void) { // Resume music playing... if music available! - if (musicEnabled) + ALenum state; + alGetSourcei(currentMusic.source, AL_SOURCE_STATE, &state); + + if (state == AL_PAUSED) { - TraceLog(INFO, "Resume music stream"); + TraceLog(INFO, "Resuming music stream"); alSourcePlay(currentMusic.source); + musicEnabled = true; } } @@ -865,13 +871,12 @@ static Wave LoadOGG(char *fileName) TraceLog(DEBUG, "[%s] Total samples calculated: %i", fileName, totalSamples); - //short *data wave.data = malloc(sizeof(short)*totalSamplesLength); int samplesObtained = stb_vorbis_get_samples_short_interleaved(oggFile, info.channels, wave.data, totalSamplesLength); TraceLog(DEBUG, "[%s] Samples obtained: %i", fileName, samplesObtained); - + TraceLog(INFO, "[%s] OGG file loaded successfully (SampleRate: %i, BitRate: %i, Channels: %i)", fileName, wave.sampleRate, wave.bitsPerSample, wave.channels); stb_vorbis_close(oggFile); diff --git a/src/camera.c b/src/camera.c new file mode 100644 index 00000000..cb99ba6b --- /dev/null +++ b/src/camera.c @@ -0,0 +1,455 @@ +/********************************************************************************************** +* +* raylib.camera +* +* Camera Modes Setup and Control Functions +* +* Copyright (c) 2015 Marc Palau and Ramon Santamaria +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include <math.h> + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// CAMERA_GENERIC +#define CAMERA_SCROLL_SENSITIVITY 1.5 + +// FREE_CAMERA +#define FREE_CAMERA_MOUSE_SENSITIVITY 0.01 +#define FREE_CAMERA_DISTANCE_MIN_CLAMP 0.3 +#define FREE_CAMERA_DISTANCE_MAX_CLAMP 120 +#define FREE_CAMERA_MIN_CLAMP 85 +#define FREE_CAMERA_MAX_CLAMP -85 +#define FREE_CAMERA_SMOOTH_ZOOM_SENSITIVITY 0.05 +#define FREE_CAMERA_PANNING_DIVIDER 5.1 + +// ORBITAL_CAMERA +#define ORBITAL_CAMERA_SPEED 0.01 + +// FIRST_PERSON +//#define FIRST_PERSON_MOUSE_SENSITIVITY 0.003 +#define FIRST_PERSON_FOCUS_DISTANCE 25 +#define FIRST_PERSON_MIN_CLAMP 85 +#define FIRST_PERSON_MAX_CLAMP -85 + +#define FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER 5.0 +#define FIRST_PERSON_STEP_DIVIDER 30.0 +#define FIRST_PERSON_WAVING_DIVIDER 200.0 + +#define FIRST_PERSON_HEIGHT_RELATIVE_EYES_POSITION 0.85 + +// THIRD_PERSON +//#define THIRD_PERSON_MOUSE_SENSITIVITY 0.003 +#define THIRD_PERSON_DISTANCE_CLAMP 1.2 +#define THIRD_PERSON_MIN_CLAMP 5 +#define THIRD_PERSON_MAX_CLAMP -85 +#define THIRD_PERSON_OFFSET (Vector3){ 0.4, 0, 0 } + +// PLAYER (used by camera) +#define PLAYER_WIDTH 0.4 +#define PLAYER_HEIGHT 0.9 +#define PLAYER_DEPTH 0.4 +#define PLAYER_MOVEMENT_DIVIDER 20.0 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static Camera internalCamera = {{2,0,2},{0,0,0},{0,1,0}}; +static Vector2 cameraAngle = { 0, 0 }; +static float cameraTargetDistance = 5; +static Vector3 resetingPosition = { 0, 0, 0 }; +static int resetingKey = 'Z'; +static Vector2 cameraMousePosition = { 0, 0 }; +static Vector2 cameraMouseVariation = { 0, 0 }; +static float mouseSensitivity = 0.003; +static int cameraMovementController[6] = { 'W', 'A', 'S', 'D', 'E', 'Q' }; +static int cameraMovementCounter = 0; +static bool cameraUseGravity = true; +static int pawnControllingKey = MOUSE_MIDDLE_BUTTON; +static int fnControllingKey = KEY_LEFT_ALT; +static int smoothZoomControllingKey = KEY_LEFT_CONTROL; + +static int cameraMode = CAMERA_CUSTOM; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static void ProcessCamera(Camera *camera, Vector3 *playerPosition); +/* +static void SetCameraControls(int front, int left, int back, right, up, down); +static void SetMouseSensitivity(int sensitivity); +static void SetResetPosition(Vector3 resetPosition); +static void SetResetControl(int resetKey); +static void SetPawnControl(int pawnControlKey); +static void SetFnControl(int fnControlKey); +static void SetSmoothZoomControl(int smoothZoomControlKey); +*/ + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Select camera mode (multiple camera modes available) +void SetCameraMode(int mode) +{ + if ((cameraMode == CAMERA_FIRST_PERSON) && (mode == CAMERA_FREE)) + { + cameraMode = CAMERA_THIRD_PERSON; + cameraTargetDistance = 5; + cameraAngle.y = -40 * DEG2RAD; + ProcessCamera(&internalCamera, &internalCamera.position); + } + else if ((cameraMode == CAMERA_FIRST_PERSON) && (mode == CAMERA_ORBITAL)) + { + cameraMode = CAMERA_THIRD_PERSON; + cameraTargetDistance = 5; + cameraAngle.y = -40 * DEG2RAD; + ProcessCamera(&internalCamera, &internalCamera.position); + } + else if ((cameraMode == CAMERA_CUSTOM) && (mode == CAMERA_FREE)) + { + cameraTargetDistance = 10; + cameraAngle.x = 45 * DEG2RAD; + cameraAngle.y = -40 * DEG2RAD; + internalCamera.target = (Vector3){ 0, 0, 0}; + ProcessCamera(&internalCamera, &internalCamera.position); + } + else if ((cameraMode == CAMERA_CUSTOM) && (mode == CAMERA_ORBITAL)) + { + cameraTargetDistance = 10; + cameraAngle.x = 225 * DEG2RAD; + cameraAngle.y = -40 * DEG2RAD; + internalCamera.target = (Vector3){ 3, 0, 3}; + ProcessCamera(&internalCamera, &internalCamera.position); + } + + cameraMode = mode; +} + +// Update camera with position +Camera UpdateCamera(Vector3 *position) +{ + // Calculate camera + if (cameraMode != CAMERA_CUSTOM) ProcessCamera(&internalCamera, position); + + return internalCamera; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Process desired camera mode and controls +static void ProcessCamera(Camera *camera, Vector3 *playerPosition) +{ +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) + // Mouse movement detection + if ((cameraMode != CAMERA_FREE) && (cameraMode != CAMERA_ORBITAL)) + { + HideCursor(); + + if (GetMousePosition().x < GetScreenHeight() / 3) SetMousePosition((Vector2){ GetScreenWidth() - GetScreenHeight() / 3, GetMousePosition().y}); + else if (GetMousePosition().y < GetScreenHeight() / 3) SetMousePosition((Vector2){ GetMousePosition().x, GetScreenHeight() - GetScreenHeight() / 3}); + else if (GetMousePosition().x > GetScreenWidth() - GetScreenHeight() / 3) SetMousePosition((Vector2) { GetScreenHeight() / 3, GetMousePosition().y}); + else if (GetMousePosition().y > GetScreenHeight() - GetScreenHeight() / 3) SetMousePosition((Vector2){ GetMousePosition().x, GetScreenHeight() / 3}); + else + { + cameraMouseVariation.x = GetMousePosition().x - cameraMousePosition.x; + cameraMouseVariation.y = GetMousePosition().y - cameraMousePosition.y; + } + } + else + { + ShowCursor(); + + cameraMouseVariation.x = GetMousePosition().x - cameraMousePosition.x; + cameraMouseVariation.y = GetMousePosition().y - cameraMousePosition.y; + } + + cameraMousePosition = GetMousePosition(); + + // Support for multiple automatic camera modes + switch (cameraMode) + { + case CAMERA_FREE: + { + // Camera zoom + if ((cameraTargetDistance < FREE_CAMERA_DISTANCE_MAX_CLAMP) && (GetMouseWheelMove() < 0)) + { + cameraTargetDistance -= (GetMouseWheelMove() * CAMERA_SCROLL_SENSITIVITY); + + if (cameraTargetDistance > FREE_CAMERA_DISTANCE_MAX_CLAMP) cameraTargetDistance = FREE_CAMERA_DISTANCE_MAX_CLAMP; + } + // Camera looking down + else if ((camera->position.y > camera->target.y) && (cameraTargetDistance == FREE_CAMERA_DISTANCE_MAX_CLAMP) && (GetMouseWheelMove() < 0)) + { + camera->target.x += GetMouseWheelMove() * (camera->target.x - camera->position.x) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.y += GetMouseWheelMove() * (camera->target.y - camera->position.y) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.z += GetMouseWheelMove() * (camera->target.z - camera->position.z) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + } + else if ((camera->position.y > camera->target.y) && (camera->target.y >= 0)) + { + camera->target.x += GetMouseWheelMove() * (camera->target.x - camera->position.x) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.y += GetMouseWheelMove() * (camera->target.y - camera->position.y) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.z += GetMouseWheelMove() * (camera->target.z - camera->position.z) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + + if (camera->target.y < 0) camera->target.y = -0.001; + } + else if ((camera->position.y > camera->target.y) && (camera->target.y < 0) && (GetMouseWheelMove() > 0)) + { + cameraTargetDistance -= (GetMouseWheelMove() * CAMERA_SCROLL_SENSITIVITY); + if (cameraTargetDistance < FREE_CAMERA_DISTANCE_MIN_CLAMP) cameraTargetDistance = FREE_CAMERA_DISTANCE_MIN_CLAMP; + } + // Camera looking up + else if ((camera->position.y < camera->target.y) && (cameraTargetDistance == FREE_CAMERA_DISTANCE_MAX_CLAMP) && (GetMouseWheelMove() < 0)) + { + camera->target.x += GetMouseWheelMove() * (camera->target.x - camera->position.x) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.y += GetMouseWheelMove() * (camera->target.y - camera->position.y) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.z += GetMouseWheelMove() * (camera->target.z - camera->position.z) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + } + else if ((camera->position.y < camera->target.y) && (camera->target.y <= 0)) + { + camera->target.x += GetMouseWheelMove() * (camera->target.x - camera->position.x) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.y += GetMouseWheelMove() * (camera->target.y - camera->position.y) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + camera->target.z += GetMouseWheelMove() * (camera->target.z - camera->position.z) * CAMERA_SCROLL_SENSITIVITY / cameraTargetDistance; + + if (camera->target.y > 0) camera->target.y = 0.001; + } + else if ((camera->position.y < camera->target.y) && (camera->target.y > 0) && (GetMouseWheelMove() > 0)) + { + cameraTargetDistance -= (GetMouseWheelMove() * CAMERA_SCROLL_SENSITIVITY); + if (cameraTargetDistance < FREE_CAMERA_DISTANCE_MIN_CLAMP) cameraTargetDistance = FREE_CAMERA_DISTANCE_MIN_CLAMP; + } + + // Inputs + if (IsKeyDown(fnControllingKey)) + { + if (IsKeyDown(smoothZoomControllingKey)) + { + // Camera smooth zoom + if (IsMouseButtonDown(pawnControllingKey)) cameraTargetDistance += (cameraMouseVariation.y * FREE_CAMERA_SMOOTH_ZOOM_SENSITIVITY); + } + // Camera orientation calculation + else if (IsMouseButtonDown(pawnControllingKey)) + { + // Camera orientation calculation + // Get the mouse sensitivity + cameraAngle.x += cameraMouseVariation.x * -FREE_CAMERA_MOUSE_SENSITIVITY; + cameraAngle.y += cameraMouseVariation.y * -FREE_CAMERA_MOUSE_SENSITIVITY; + + // Angle clamp + if (cameraAngle.y > FREE_CAMERA_MIN_CLAMP * DEG2RAD) cameraAngle.y = FREE_CAMERA_MIN_CLAMP * DEG2RAD; + else if (cameraAngle.y < FREE_CAMERA_MAX_CLAMP * DEG2RAD) cameraAngle.y = FREE_CAMERA_MAX_CLAMP * DEG2RAD; + } + } + // Paning + else if (IsMouseButtonDown(pawnControllingKey)) + { + camera->target.x += ((cameraMouseVariation.x * -FREE_CAMERA_MOUSE_SENSITIVITY) * cos(cameraAngle.x) + (cameraMouseVariation.y * FREE_CAMERA_MOUSE_SENSITIVITY) * sin(cameraAngle.x) * sin(cameraAngle.y)) * (cameraTargetDistance / FREE_CAMERA_PANNING_DIVIDER); + camera->target.y += ((cameraMouseVariation.y * FREE_CAMERA_MOUSE_SENSITIVITY) * cos(cameraAngle.y)) * (cameraTargetDistance / FREE_CAMERA_PANNING_DIVIDER); + camera->target.z += ((cameraMouseVariation.x * FREE_CAMERA_MOUSE_SENSITIVITY) * sin(cameraAngle.x) + (cameraMouseVariation.y * FREE_CAMERA_MOUSE_SENSITIVITY) * cos(cameraAngle.x) * sin(cameraAngle.y)) * (cameraTargetDistance / FREE_CAMERA_PANNING_DIVIDER); + } + + // Focus to center + if (IsKeyDown(resetingKey)) camera->target = resetingPosition; + + // Camera position update + camera->position.x = sin(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.x; + + if (cameraAngle.y <= 0) camera->position.y = sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + else camera->position.y = -sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + + camera->position.z = cos(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.z; + + } break; + case CAMERA_ORBITAL: + { + cameraAngle.x += ORBITAL_CAMERA_SPEED; + + // Camera zoom + cameraTargetDistance -= (GetMouseWheelMove() * CAMERA_SCROLL_SENSITIVITY); + // Camera distance clamp + if (cameraTargetDistance < THIRD_PERSON_DISTANCE_CLAMP) cameraTargetDistance = THIRD_PERSON_DISTANCE_CLAMP; + + // Focus to center + if (IsKeyDown('Z')) camera->target = (Vector3) { 0, 0, 0 }; + + // Camera position update + camera->position.x = sin(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.x; + + if (cameraAngle.y <= 0) camera->position.y = sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + else camera->position.y = -sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + + camera->position.z = cos(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.z; + + } break; + case CAMERA_FIRST_PERSON: + case CAMERA_THIRD_PERSON: + { + bool isMoving = false; + + // Keyboard inputs + if (IsKeyDown(cameraMovementController[0])) + { + playerPosition->x -= sin(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + playerPosition->z -= cos(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + if (!cameraUseGravity) camera->position.y += sin(cameraAngle.y) / PLAYER_MOVEMENT_DIVIDER; + + isMoving = true; + } + else if (IsKeyDown(cameraMovementController[2])) + { + playerPosition->x += sin(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + playerPosition->z += cos(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + if (!cameraUseGravity) camera->position.y -= sin(cameraAngle.y) / PLAYER_MOVEMENT_DIVIDER; + + isMoving = true; + } + + if (IsKeyDown(cameraMovementController[1])) + { + playerPosition->x -= cos(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + playerPosition->z += sin(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + + isMoving = true; + } + else if (IsKeyDown(cameraMovementController[3])) + { + playerPosition->x += cos(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + playerPosition->z -= sin(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; + + isMoving = true; + } + + if (IsKeyDown(cameraMovementController[4])) + { + if (!cameraUseGravity) playerPosition->y += 1 / PLAYER_MOVEMENT_DIVIDER; + } + else if (IsKeyDown(cameraMovementController[5])) + { + if (!cameraUseGravity) playerPosition->y -= 1 / PLAYER_MOVEMENT_DIVIDER; + } + + if (cameraMode == CAMERA_THIRD_PERSON) + { + // Camera orientation calculation + // Get the mouse sensitivity + cameraAngle.x += cameraMouseVariation.x * -mouseSensitivity; + cameraAngle.y += cameraMouseVariation.y * -mouseSensitivity; + + // Angle clamp + if (cameraAngle.y > THIRD_PERSON_MIN_CLAMP * DEG2RAD) cameraAngle.y = THIRD_PERSON_MIN_CLAMP * DEG2RAD; + else if (cameraAngle.y < THIRD_PERSON_MAX_CLAMP * DEG2RAD) cameraAngle.y = THIRD_PERSON_MAX_CLAMP * DEG2RAD; + + // Camera zoom + cameraTargetDistance -= (GetMouseWheelMove() * CAMERA_SCROLL_SENSITIVITY); + + // Camera distance clamp + if (cameraTargetDistance < THIRD_PERSON_DISTANCE_CLAMP) cameraTargetDistance = THIRD_PERSON_DISTANCE_CLAMP; + + // Camera is always looking at player + camera->target.x = playerPosition->x + THIRD_PERSON_OFFSET.x * cos(cameraAngle.x) + THIRD_PERSON_OFFSET.z * sin(cameraAngle.x); + camera->target.y = playerPosition->y + PLAYER_HEIGHT * FIRST_PERSON_HEIGHT_RELATIVE_EYES_POSITION + THIRD_PERSON_OFFSET.y; + camera->target.z = playerPosition->z + THIRD_PERSON_OFFSET.z * sin(cameraAngle.x) - THIRD_PERSON_OFFSET.x * sin(cameraAngle.x); + + // Camera position update + camera->position.x = sin(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.x; + + if (cameraAngle.y <= 0) camera->position.y = sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + else camera->position.y = -sin(cameraAngle.y) * cameraTargetDistance * sin(cameraAngle.y) + camera->target.y; + + camera->position.z = cos(cameraAngle.x) * cameraTargetDistance * cos(cameraAngle.y) + camera->target.z; + } + else + { + if (isMoving) cameraMovementCounter++; + + // Camera orientation calculation + // Get the mouse sensitivity + cameraAngle.x += cameraMouseVariation.x * -mouseSensitivity; + cameraAngle.y += cameraMouseVariation.y * -mouseSensitivity; + + // Angle clamp + if (cameraAngle.y > FIRST_PERSON_MIN_CLAMP * DEG2RAD) cameraAngle.y = FIRST_PERSON_MIN_CLAMP * DEG2RAD; + else if (cameraAngle.y < FIRST_PERSON_MAX_CLAMP * DEG2RAD) cameraAngle.y = FIRST_PERSON_MAX_CLAMP * DEG2RAD; + + // Camera is always looking at player + camera->target.x = camera->position.x - sin(cameraAngle.x) * FIRST_PERSON_FOCUS_DISTANCE; + camera->target.y = camera->position.y + sin(cameraAngle.y) * FIRST_PERSON_FOCUS_DISTANCE; + camera->target.z = camera->position.z - cos(cameraAngle.x) * FIRST_PERSON_FOCUS_DISTANCE; + + camera->position.x = playerPosition->x; + camera->position.y = (playerPosition->y + PLAYER_HEIGHT * FIRST_PERSON_HEIGHT_RELATIVE_EYES_POSITION) - sin(cameraMovementCounter / FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER) / FIRST_PERSON_STEP_DIVIDER; + camera->position.z = playerPosition->z; + + camera->up.x = sin(cameraMovementCounter / (FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER * 2)) / FIRST_PERSON_WAVING_DIVIDER; + camera->up.z = -sin(cameraMovementCounter / (FIRST_PERSON_STEP_TRIGONOMETRIC_DIVIDER * 2)) / FIRST_PERSON_WAVING_DIVIDER; + } + } break; + default: break; + } +#endif +} + +void SetCameraControls(int frontKey, int leftKey, int backKey, int rightKey, int upKey, int downKey) +{ + cameraMovementController[0] = frontKey; + cameraMovementController[1] = leftKey; + cameraMovementController[2] = backKey; + cameraMovementController[3] = rightKey; + cameraMovementController[4] = upKey; + cameraMovementController[5] = downKey; +} + +void SetMouseSensitivity(float sensitivity) +{ + mouseSensitivity = (sensitivity / 10000.0); +} + +void SetResetPosition(Vector3 resetPosition) +{ + resetingPosition = resetPosition; +} + +void SetResetControl(int resetKey) +{ + resetingKey = resetKey; +} + +void SetPawnControl(int pawnControlKey) +{ + pawnControllingKey = pawnControlKey; +} + +void SetFnControl(int fnControlKey) +{ + fnControllingKey = fnControlKey; +} + +void SetSmoothZoomControl(int smoothZoomControlKey) +{ + smoothZoomControllingKey = smoothZoomControlKey; +} + +void SetOrbitalTarget(Vector3 target) +{ + internalCamera.target = target; +} @@ -45,13 +45,18 @@ #include <stdio.h> // Standard input / output lib #include <stdlib.h> // Declares malloc() and free() for memory management, rand(), atexit() #include <stdint.h> // Required for typedef unsigned long long int uint64_t, used by hi-res timer -#include <time.h> // Useful to initialize random seed - Android/RPI hi-res timer +#include <time.h> // Useful to initialize random seed - Android/RPI hi-res timer (NOTE: Linux only!) #include <math.h> // Math related functions, tan() used to set perspective #include <string.h> // String function definitions, memset() #include <errno.h> // Macros for reporting and retrieving error conditions through error codes #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) #include <GLFW/glfw3.h> // GLFW3 library: Windows, OpenGL context and Input management + #ifdef __linux + #define GLFW_EXPOSE_NATIVE_X11 // Linux specific definitions for getting + #define GLFW_EXPOSE_NATIVE_GLX // native functions like glfwGetX11Window + #include <GLFW/glfw3native.h> // which are required for hiding mouse + #endif //#include <GL/gl.h> // OpenGL functions (GLFW3 already includes gl.h) //#define GLFW_DLL // Using GLFW DLL on Windows -> No, we use static version! #endif @@ -104,28 +109,13 @@ //---------------------------------------------------------------------------------- #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) static GLFWwindow *window; // Native window (graphic device) +static bool windowMinimized = false; #elif defined(PLATFORM_ANDROID) static struct android_app *app; // Android activity static struct android_poll_source *source; // Android events polling source static int ident, events; static bool windowReady = false; // Used to detect display initialization -// Gestures detection variables -static float tapTouchX, tapTouchY; -static int64_t lastTapTime = 0; -static float lastTapX = 0, lastTapY = 0; -static bool touchTap = false; -static bool doubleTap = false; -static bool drag = false; -static int stdVector[MAX_TOUCH_POINTS]; -static int indexPosition = 0; -const AInputEvent* eventDrag; -static int32_t touchId; -const int32_t DOUBLE_TAP_TIMEOUT = 300*1000000; -const int32_t DOUBLE_TAP_SLOP = 100; -const int32_t TAP_TIMEOUT = 180*1000000; -const int32_t TOUCH_SLOP = 8; - #elif defined(PLATFORM_RPI) static EGL_DISPMANX_WINDOW_T nativeWindow; // Native window (graphic device) @@ -152,8 +142,7 @@ static int gamepadStream = -1; // Gamepad device file descripto #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) static EGLDisplay display; // Native display device (physical screen connection) static EGLSurface surface; // Surface to draw on, framebuffers (connected to context) -static EGLContext context; // Graphic context, mode in which drawing can be done - +static EGLContext context; // Graphic context, mode in which drawing can be done static uint64_t baseTime; // Base time measure for hi-res timer static bool windowShouldClose = false; // Flag to set window for closing #endif @@ -191,11 +180,8 @@ static int currentMouseWheelY = 0; // Required to track mouse wheel var static int exitKey = KEY_ESCAPE; // Default exit key (ESC) static int lastKeyPressed = -1; -#endif -#if defined(PLATFORM_ANDROID) -static float touchX; // Touch position X -static float touchY; // Touch position Y +static bool cursorHidden; #endif static double currentTime, previousTime; // Used to track timmings @@ -206,6 +192,9 @@ static double targetTime = 0.0; // Desired time for one frame, if 0 static char configFlags = 0; static bool showLogo = false; +// Shaders variables +static bool enabledPostpro = false; + //---------------------------------------------------------------------------------- // Other Modules Functions Declaration (required by core) //---------------------------------------------------------------------------------- @@ -214,6 +203,17 @@ extern void UnloadDefaultFont(void); // [Module: text] Unloads defaul extern void UpdateMusicStream(void); // [Module: audio] Updates buffers for music streaming +extern Vector2 GetRawPosition(void); +extern void ResetGestures(void); + +#if defined(PLATFORM_ANDROID) +extern void InitAndroidGestures(struct android_app *app); +#endif + +#if defined(PLATFORM_WEB) +extern void InitWebGestures(void); // [Module: gestures] Initializes emscripten gestures for web +#endif + //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- @@ -227,6 +227,7 @@ static void SwapBuffers(void); // Copy back buffer to f static void PollInputEvents(void); // Register user events static void LogoAnimation(void); // Plays raylib logo appearing animation static void SetupFramebufferSize(int displayWidth, int displayHeight); + #if defined(PLATFORM_RPI) static void InitMouse(void); // Mouse initialization (including mouse thread) static void *MouseThread(void *arg); // Mouse reading thread @@ -243,6 +244,7 @@ static void CharCallback(GLFWwindow *window, unsigned int key); static void ScrollCallback(GLFWwindow *window, double xoffset, double yoffset); // GLFW3 Srolling Callback, runs on mouse wheel static void CursorEnterCallback(GLFWwindow *window, int enter); // GLFW3 Cursor Enter Callback, cursor enters client area static void WindowSizeCallback(GLFWwindow *window, int width, int height); // GLFW3 WindowSize Callback, runs when window is resized +static void WindowIconifyCallback(GLFWwindow* window, int iconified); // GLFW3 WindowIconify Callback, runs when window is minimized/restored #endif #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_RPI) @@ -250,8 +252,7 @@ static void TakeScreenshot(void); #endif #if defined(PLATFORM_ANDROID) -static int32_t InputCallback(struct android_app *app, AInputEvent *event); // Process Android activity input events -static void CommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands +static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands #endif //---------------------------------------------------------------------------------- @@ -261,8 +262,8 @@ static void CommandCallback(struct android_app *app, int32_t cmd); // // Initialize Window and Graphics Context (OpenGL) void InitWindow(int width, int height, const char *title) { - TraceLog(INFO, "Initializing raylib..."); - + TraceLog(INFO, "Initializing raylib (v1.3.0)"); + // Store window title (could be useful...) windowTitle = title; @@ -285,6 +286,11 @@ void InitWindow(int width, int height, const char *title) InitKeyboard(); // Keyboard init InitGamepad(); // Gamepad init #endif + +#if defined(PLATFORM_WEB) + InitWebGestures(); // Init touch input events for web +#endif + mousePosition.x = screenWidth/2; mousePosition.y = screenHeight/2; @@ -300,8 +306,8 @@ void InitWindow(int width, int height, const char *title) // Android activity initialization void InitWindow(int width, int height, struct android_app *state) { - TraceLog(INFO, "Initializing raylib..."); - + TraceLog(INFO, "Initializing raylib (v1.3.0)"); + app_dummy(); screenWidth = width; @@ -336,8 +342,9 @@ void InitWindow(int width, int height, struct android_app *state) //AConfiguration_getScreenLong(app->config); //state->userData = &engine; - app->onAppCmd = CommandCallback; - app->onInputEvent = InputCallback; + app->onAppCmd = AndroidCommandCallback; + + InitAndroidGestures(app); InitAssetManager(app->activity->assetManager); @@ -399,6 +406,9 @@ void CloseWindow(void) bool WindowShouldClose(void) { #if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) + // While window minimized, stop loop execution + while (windowMinimized) glfwPollEvents(); + return (glfwWindowShouldClose(window)); #elif defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) return windowShouldClose; @@ -429,7 +439,7 @@ void SetCustomCursor(const char *cursorImage) cursor = LoadTexture(cursorImage); #if defined(PLATFORM_DESKTOP) - // NOTE: emscripten not implemented + // NOTE: emscripten not implemented glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); #endif customCursor = true; @@ -469,22 +479,31 @@ void BeginDrawing(void) updateTime = currentTime - previousTime; previousTime = currentTime; + if (enabledPostpro) rlEnableFBO(); + rlClearScreenBuffers(); rlLoadIdentity(); // Reset current matrix (MODELVIEW) rlMultMatrixf(GetMatrixVector(downscaleView)); // If downscale required, apply it here -// rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 + //rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 // NOTE: Not required with OpenGL 3.3+ } // End canvas drawing and Swap Buffers (Double Buffering) void EndDrawing(void) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + + if (enabledPostpro) rlglDrawPostpro(); // Draw postprocessing effect (shader) SwapBuffers(); // Copy back buffer to front buffer + +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) + ResetGestures(); +#endif + PollInputEvents(); // Poll user events UpdateMusicStream(); // NOTE: Function checks if music is enabled @@ -510,7 +529,7 @@ void EndDrawing(void) // Initializes 3D mode for drawing (Camera setup) void Begin3dMode(Camera camera) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) rlMatrixMode(RL_PROJECTION); // Switch to projection matrix @@ -528,14 +547,14 @@ void Begin3dMode(Camera camera) rlLoadIdentity(); // Reset current matrix (MODELVIEW) // Setup Camera view - Matrix matLookAt = MatrixLookAt(camera.position, camera.target, camera.up); - rlMultMatrixf(GetMatrixVector(matLookAt)); // Multiply MODELVIEW matrix by view matrix (camera) + Matrix view = MatrixLookAt(camera.position, camera.target, camera.up); + rlMultMatrixf(GetMatrixVector(view)); // Multiply MODELVIEW matrix by view matrix (camera) } // Ends 3D mode and returns to default 2D orthographic mode void End3dMode(void) { - rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) + rlglDraw(); // Draw Buffers (Only OpenGL 3+ and ES2) rlMatrixMode(RL_PROJECTION); // Switch to projection matrix rlPopMatrix(); // Restore previous matrix (PROJECTION) from matrix stack @@ -612,9 +631,8 @@ Color Fade(Color color, float alpha) return (Color){color.r, color.g, color.b, color.a*alpha}; } -// Enable some window configurations (SetWindowFlags()?) -// TODO: Review function name and usage -void SetupFlags(char flags) +// Enable some window/system configurations +void SetConfigFlags(char flags) { configFlags = flags; @@ -628,6 +646,41 @@ void ShowLogo(void) showLogo = true; } +Ray GetMouseRay(Vector2 mousePosition, Camera camera) +{ + Ray ray; + + Matrix proj = MatrixIdentity(); + Matrix view = MatrixLookAt(camera.position, camera.target, camera.up); + + float aspect = (GLfloat)GetScreenWidth()/(GLfloat)GetScreenHeight(); + double top = 0.1f*tanf(45.0f*PI / 360.0f); + double right = top*aspect; + + proj = MatrixFrustum(-right, right, -top, top, 0.01f, 1000.0f); + MatrixTranspose(&proj); + + float realy = (float)GetScreenHeight() - mousePosition.y; + + //float z; + // glReadPixels(mousePosition.x, mousePosition.y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &z); + //http://www.bfilipek.com/2012/06/select-mouse-opengl.html + + Vector3 nearPoint = { mousePosition.x, realy, 0.0f }; + Vector3 farPoint = { mousePosition.x, realy, 1.0f }; + + //nearPoint = internalCamera.position; + farPoint = rlglUnproject(farPoint, proj, view); + + Vector3 direction = VectorSubtract(farPoint, nearPoint); + VectorNormalize(&direction); + + ray.position = nearPoint; + ray.direction = direction; + + return ray; +} + //---------------------------------------------------------------------------------- // Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions //---------------------------------------------------------------------------------- @@ -733,7 +786,7 @@ void SetMousePosition(Vector2 position) { mousePosition = position; #if defined(PLATFORM_DESKTOP) - // NOTE: emscripten not implemented + // NOTE: emscripten not implemented glfwSetCursorPos(window, position.x, position.y); #endif } @@ -741,17 +794,52 @@ void SetMousePosition(Vector2 position) // Returns mouse wheel movement Y int GetMouseWheelMove(void) { - previousMouseWheelY = currentMouseWheelY; + return previousMouseWheelY; +} - currentMouseWheelY = 0; +// Hide mouse cursor +void HideCursor() +{ +#if defined(PLATFORM_DESKTOP) + #ifdef __linux + XColor Col; + const char Nil[] = {0}; + + Pixmap Pix = XCreateBitmapFromData(glfwGetX11Display(), glfwGetX11Window(window), Nil, 1, 1); + Cursor Cur = XCreatePixmapCursor(glfwGetX11Display(), Pix, Pix, &Col, &Col, 0, 0); + + XDefineCursor(glfwGetX11Display(), glfwGetX11Window(window), Cur); + XFreeCursor(glfwGetX11Display(), Cur); + #else + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); + #endif +#endif + cursorHidden = true; +} - return previousMouseWheelY; +// Show mouse cursor +void ShowCursor() +{ +#if defined(PLATFORM_DESKTOP) + #ifdef __linux + XUndefineCursor(glfwGetX11Display(), glfwGetX11Window(window)); + #else + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + #endif +#endif + cursorHidden = false; +} + +// Check if mouse cursor is hidden +bool IsCursorHidden() +{ + return cursorHidden; } #endif // TODO: Enable gamepad usage on Rapsberry Pi // NOTE: emscripten not implemented -#if defined(PLATFORM_DESKTOP) +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) // Detect if a gamepad is available bool IsGamepadAvailable(int gamepad) { @@ -848,65 +936,72 @@ bool IsGamepadButtonUp(int gamepad, int button) } #endif -#if defined(PLATFORM_ANDROID) -bool IsScreenTouched(void) -{ - return touchTap; -} - -bool IsDoubleTap(void) -{ - if (doubleTap) TraceLog(INFO, "DOUBLE TAP gesture detected"); - - return doubleTap; -} - -bool IsDragGesture(void) -{ - return drag; -} - +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) // Returns touch position X int GetTouchX(void) { - return (int)touchX; + return (int)GetRawPosition().x; } // Returns touch position Y int GetTouchY(void) { - return (int)touchY; + return (int)GetRawPosition().y; } // Returns touch position XY Vector2 GetTouchPosition(void) { - Vector2 position = { touchX, touchY }; + Vector2 position = GetRawPosition(); + + if ((screenWidth > displayWidth) || (screenHeight > displayHeight)) + { + // TODO: Seems to work ok but... review! + position.x = position.x*((float)screenWidth / (float)(displayWidth - renderOffsetX)) - renderOffsetX/2; + position.y = position.y*((float)screenHeight / (float)(displayHeight - renderOffsetY)) - renderOffsetY/2; + } + else + { + position.x = position.x*((float)renderWidth / (float)displayWidth) - renderOffsetX/2; + position.y = position.y*((float)renderHeight / (float)displayHeight) - renderOffsetY/2; + } return position; } +#endif -/*bool GetPointer(Vector2 *dragPositions) +// Set postprocessing shader +void SetPostproShader(Shader shader) { - //static int stdVector[MAX_TOUCH_POINTS]; - //static int indexPosition = 0; - //if (indexPosition == 0) return false; - Vector2 vec_pointers_[]; - - //eventDrag - int32_t iIndex = FindIndex( eventDrag, vec_pointers_[0] ); - - if (iIndex == -1) return false; - - float x = AMotionEvent_getX(eventDrag, iIndex); - float y = AMotionEvent_getY(eventDrag, iIndex); - - *dragPositions = Vector2( x, y ); + if (rlGetVersion() == OPENGL_11) TraceLog(WARNING, "Postprocessing shaders not supported on OpenGL 1.1"); + else + { + if (!enabledPostpro) + { + enabledPostpro = true; + rlglInitPostpro(); + rlglSetPostproShader(shader); + } + else + { + rlglSetPostproShader(shader); + } + } +} +// Set custom shader to be used in batch draw +void SetCustomShader(Shader shader) +{ + rlglSetCustomShader(shader); +} - return true; -}*/ -#endif +// Set default shader to be used in batch draw +void SetDefaultShader(void) +{ + rlglSetDefaultShader(); + + enabledPostpro = false; +} //---------------------------------------------------------------------------------- // Module specific Functions Definition @@ -946,7 +1041,7 @@ static void InitDisplay(int width, int height) displayWidth = screenWidth; displayHeight = screenHeight; #endif - + glfwDefaultWindowHints(); // Set default windows hints glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); // Avoid window being resizable @@ -956,13 +1051,18 @@ static void InitDisplay(int width, int height) //glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_API); // Default OpenGL API to use. Alternative: GLFW_OPENGL_ES_API //glfwWindowHint(GLFW_AUX_BUFFERS, 0); // Number of auxiliar buffers - // NOTE: When asking for an OpenGL context version, most drivers provide highest supported version + // NOTE: When asking for an OpenGL context version, most drivers provide highest supported version // with forward compatibility to older OpenGL versions. // For example, if using OpenGL 1.1, driver can provide a 3.3 context fordward compatible. if (rlGetVersion() == OPENGL_33) { - //glfwWindowHint(GLFW_SAMPLES, 4); // Enables multisampling x4 (MSAA), default is 0 + if (configFlags & FLAG_MSAA_4X_HINT) + { + glfwWindowHint(GLFW_SAMPLES, 4); // Enables multisampling x4 (MSAA), default is 0 + TraceLog(INFO, "Enabled MSAA x4"); + } + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Choose OpenGL major version (just hint) glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Choose OpenGL minor version (just hint) glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Profiles Hint: Only 3.2 and above! @@ -1009,12 +1109,18 @@ static void InitDisplay(int width, int height) glfwSetMouseButtonCallback(window, MouseButtonCallback); glfwSetCharCallback(window, CharCallback); glfwSetScrollCallback(window, ScrollCallback); + glfwSetWindowIconifyCallback(window, WindowIconifyCallback); glfwMakeContextCurrent(window); - //glfwSwapInterval(0); // Disables GPU v-sync (if set), so frames are not limited to screen refresh rate (60Hz -> 60 FPS) - // If not set, swap interval uses GPU v-sync configuration - // Framerate can be setup using SetTargetFPS() + // Enables GPU v-sync, so frames are not limited to screen refresh rate (60Hz -> 60 FPS) + // If not set, swap interval uses GPU v-sync configuration + // Framerate can be setup using SetTargetFPS() + if (configFlags & FLAG_VSYNC_HINT) + { + glfwSwapInterval(1); + TraceLog(INFO, "Trying to enable VSYNC"); + } //glfwGetFramebufferSize(window, &renderWidth, &renderHeight); // Get framebuffer size of current window @@ -1036,6 +1142,7 @@ static void InitDisplay(int width, int height) VC_RECT_T srcRect; #endif + // TODO: if (configFlags & FLAG_MSAA_4X_HINT) activate (EGL_SAMPLES, 4) const EGLint framebufferAttribs[] = { EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // Type of context support -> Required on RPI? @@ -1158,7 +1265,7 @@ static void InitDisplay(int width, int height) } // Initialize OpenGL graphics -void InitGraphics(void) +static void InitGraphics(void) { rlglInit(); // Init rlgl @@ -1216,7 +1323,7 @@ static void MouseButtonCallback(GLFWwindow *window, int button, int action, int static void CharCallback(GLFWwindow *window, unsigned int key) { lastKeyPressed = key; - + //TraceLog(INFO, "Char Callback Key pressed: %i\n", key); } @@ -1242,212 +1349,30 @@ static void WindowSizeCallback(GLFWwindow *window, int width, int height) // Background must be also re-cleared ClearBackground(RAYWHITE); } -#endif -#if defined(PLATFORM_ANDROID) -// Android: Process activity input events -static int32_t InputCallback(struct android_app *app, AInputEvent *event) +// GLFW3 WindowIconify Callback, runs when window is minimized/restored +static void WindowIconifyCallback(GLFWwindow* window, int iconified) { - int type = AInputEvent_getType(event); - //int32_t key = 0; - - if (type == AINPUT_EVENT_TYPE_MOTION) + if (iconified) { - // Detect TOUCH position - if ((screenWidth > displayWidth) || (screenHeight > displayHeight)) - { - // TODO: Seems to work ok but... review! - touchX = AMotionEvent_getX(event, 0) * ((float)screenWidth / (float)(displayWidth - renderOffsetX)) - renderOffsetX/2; - touchY = AMotionEvent_getY(event, 0) * ((float)screenHeight / (float)(displayHeight - renderOffsetY)) - renderOffsetY/2; - } - else - { - touchX = AMotionEvent_getX(event, 0) * ((float)renderWidth / (float)displayWidth) - renderOffsetX/2; - touchY = AMotionEvent_getY(event, 0) * ((float)renderHeight / (float)displayHeight) - renderOffsetY/2; - } - - // Detect TAP event -/* - if (AMotionEvent_getPointerCount(event) > 1 ) - { - // Only support single touch - return false; - } -*/ - int32_t action = AMotionEvent_getAction(event); - unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; + // The window was iconified + PauseMusicStream(); - switch (flags) - { - case AMOTION_EVENT_ACTION_DOWN: - { - touchId = AMotionEvent_getPointerId(event, 0); - tapTouchX = AMotionEvent_getX(event, 0); - tapTouchY = AMotionEvent_getY(event, 0); - - } break; - case AMOTION_EVENT_ACTION_UP: - { - int64_t eventTime = AMotionEvent_getEventTime(event); - int64_t downTime = AMotionEvent_getDownTime(event); - - if (eventTime - downTime <= TAP_TIMEOUT) - { - if (touchId == AMotionEvent_getPointerId(event, 0)) - { - float x = AMotionEvent_getX(event, 0) - tapTouchX; - float y = AMotionEvent_getY(event, 0) - tapTouchY; - - float densityFactor = 1.0f; - - if ( x*x + y*y < TOUCH_SLOP*TOUCH_SLOP * densityFactor) - { - // TAP Detected - touchTap = true; - } - } - } - break; - } - } - - //float AMotionEvent_getX(event, size_t pointer_index); - //int32_t AMotionEvent_getButtonState(event); // Pressed buttons - //int32_t AMotionEvent_getPointerId(event, size_t pointer_index); - //size_t pointerCount = AMotionEvent_getPointerCount(event); - //float AMotionEvent_getPressure(const AInputEvent *motion_event, size_t pointer_index); // 0 to 1 - //float AMotionEvent_getSize(const AInputEvent *motion_event, size_t pointer_index); // Pressed area - - // Detect DOUBLE TAP event - bool tapDetected = touchTap; - - switch (flags) - { - case AMOTION_EVENT_ACTION_DOWN: - { - int64_t eventTime = AMotionEvent_getEventTime(event); - - if (eventTime - lastTapTime <= DOUBLE_TAP_TIMEOUT) - { - float x = AMotionEvent_getX(event, 0) - lastTapX; - float y = AMotionEvent_getY(event, 0) - lastTapY; - - float densityFactor = 1.0f; - - if ((x*x + y*y) < (DOUBLE_TAP_SLOP*DOUBLE_TAP_SLOP*densityFactor)) - { - // Doubletap detected - doubleTap = true; - - } - } - } break; - case AMOTION_EVENT_ACTION_UP: - { - if (tapDetected) - { - lastTapTime = AMotionEvent_getEventTime(event); - lastTapX = AMotionEvent_getX(event, 0); - lastTapY = AMotionEvent_getY(event, 0); - - } - } break; - } - - - // Detect DRAG event - //int32_t action = AMotionEvent_getAction(event); - - int32_t index = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; - //uint32_t flags = action & AMOTION_EVENT_ACTION_MASK; - //event_ = event; - - int32_t count = AMotionEvent_getPointerCount(event); - - switch (flags) - { - case AMOTION_EVENT_ACTION_DOWN: - { - stdVector[indexPosition] = AMotionEvent_getPointerId(event, 0); - indexPosition++; - TraceLog(INFO, "ACTION_DOWN"); - - //ret = GESTURE_STATE_START; - } break; - case AMOTION_EVENT_ACTION_POINTER_DOWN: - { - stdVector[indexPosition] = AMotionEvent_getPointerId(event, index); - indexPosition++; - TraceLog(INFO, "ACTION_POINTER_DOWN"); - - } break; - case AMOTION_EVENT_ACTION_UP: - { - //int value = stdVector[indexPosition]; - indexPosition--; - //ret = GESTURE_STATE_END; - TraceLog(INFO, "ACTION_UP"); - - } break; - case AMOTION_EVENT_ACTION_POINTER_UP: - { - int32_t releasedPointerId = AMotionEvent_getPointerId(event, index); - - int i = 0; - for (i = 0; i < MAX_TOUCH_POINTS; i++) - { - if (stdVector[i] == releasedPointerId) - { - for (int k = i; k < indexPosition - 1; k++) - { - stdVector[k] = stdVector[k + 1]; - } - - //indexPosition--; - indexPosition = 0; - break; - } - } - - if (i <= 1) - { - // Reset pinch or drag - //if (count == 2) //ret = GESTURE_STATE_START; - } - TraceLog(INFO, "ACTION_POINTER_UP"); - - } break; - case AMOTION_EVENT_ACTION_MOVE: - { - if (count == 1) - { - //TraceLog(INFO, "DRAG gesture detected"); - - drag = true; //ret = GESTURE_STATE_MOVE; - } - else break; - TraceLog(INFO, "ACTION_MOVE"); - - } break; - case AMOTION_EVENT_ACTION_CANCEL: break; - default: break; - } - - //-------------------------------------------------------------------- - - return 1; + windowMinimized = true; } - else if (type == AINPUT_EVENT_TYPE_KEY) + else { - //key = AKeyEvent_getKeyCode(event); - //int32_t AKeyEvent_getMetaState(event); - } + // The window was restored + ResumeMusicStream(); - return 0; + windowMinimized = false; + } } +#endif +#if defined(PLATFORM_ANDROID) // Android: Process activity lifecycle commands -static void CommandCallback(struct android_app *app, int32_t cmd) +static void AndroidCommandCallback(struct android_app *app, int32_t cmd) { switch (cmd) { @@ -1636,24 +1561,24 @@ static void PollInputEvents(void) // Keyboard polling // Automatically managed by GLFW3 through callback lastKeyPressed = -1; - + // Register previous keys states for (int i = 0; i < 512; i++) previousKeyState[i] = currentKeyState[i]; - + // Register previous mouse states for (int i = 0; i < 3; i++) previousMouseState[i] = currentMouseState[i]; - - glfwPollEvents(); // Register keyboard/mouse events + + previousMouseWheelY = currentMouseWheelY; + currentMouseWheelY = 0; + + glfwPollEvents(); // Register keyboard/mouse events... and window events! #elif defined(PLATFORM_ANDROID) // TODO: Check virtual keyboard (?) - // Reset touch events - touchTap = false; - doubleTap = false; - drag = false; - // Poll Events (registered events) + // TODO: Enable/disable activityMinimized to block activity if minimized + //while ((ident = ALooper_pollAll(activityMinimized ? 0 : -1, NULL, &events,(void**)&source)) >= 0) while ((ident = ALooper_pollAll(0, NULL, &events,(void**)&source)) >= 0) { // Process this event @@ -1664,8 +1589,8 @@ static void PollInputEvents(void) { // NOTE: Never close window, native activity is controlled by the system! //TraceLog(INFO, "Closing Window..."); - //windowShouldClose = true; - + //windowShouldClose = true; + //ANativeActivity_finish(app->activity); } } @@ -1952,13 +1877,13 @@ static void SetupFramebufferSize(int displayWidth, int displayHeight) if (widthRatio <= heightRatio) { renderWidth = displayWidth; - renderHeight = (int)((float)screenHeight*widthRatio); + renderHeight = (int)round((float)screenHeight*widthRatio); renderOffsetX = 0; renderOffsetY = (displayHeight - renderHeight); } else { - renderWidth = (int)((float)screenWidth*heightRatio); + renderWidth = (int)round((float)screenWidth*heightRatio); renderHeight = displayHeight; renderOffsetX = (displayWidth - renderWidth); renderOffsetY = 0; @@ -1988,13 +1913,13 @@ static void SetupFramebufferSize(int displayWidth, int displayHeight) if (displayRatio <= screenRatio) { renderWidth = screenWidth; - renderHeight = (int)((float)screenWidth/displayRatio); + renderHeight = (int)round((float)screenWidth/displayRatio); renderOffsetX = 0; renderOffsetY = (renderHeight - screenHeight); } else { - renderWidth = (int)((float)screenHeight*displayRatio); + renderWidth = (int)round((float)screenHeight*displayRatio); renderHeight = screenHeight; renderOffsetX = (renderWidth - screenWidth); renderOffsetY = 0; @@ -2134,4 +2059,3 @@ static void LogoAnimation(void) showLogo = false; // Prevent for repeating when reloading window (Android) } - diff --git a/src/gestures.c b/src/gestures.c new file mode 100644 index 00000000..13209b32 --- /dev/null +++ b/src/gestures.c @@ -0,0 +1,645 @@ +/********************************************************************************************** +* +* raylib.gestures +* +* Gestures Detection and Usage Functions Definitions +* +* Copyright (c) 2015 Marc Palau and Ramon Santamaria +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" +#include "utils.h" + +#include <stdlib.h> // malloc(), free() +#include <stdio.h> // printf(), fprintf() +#include <math.h> // Used for ... +#include <stdint.h> // Defines int32_t, int64_t + +#if defined(_WIN32) + //#include <Windows.h> +#elif defined(__linux) + #include <time.h> // Used for clock functions +#endif + +#if defined(PLATFORM_ANDROID) + #include <jni.h> // Java native interface + #include <android/sensor.h> // Android sensors functions + #include <android/window.h> // Defines AWINDOW_FLAG_FULLSCREEN and others +#endif + +#if defined(PLATFORM_WEB) + #include <emscripten/emscripten.h> + #include <emscripten/html5.h> +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#define FORCE_TO_SWIPE 20 +#define TAP_TIMEOUT 300 + +#define MAX_TOUCH_POINTS 4 + +typedef enum { + TYPE_MOTIONLESS, + TYPE_DRAG, + TYPE_DUAL_INPUT +} GestureType; + +typedef enum { + UP, + DOWN, + MOVE +} ActionType; + +typedef struct { + ActionType action; + int pointCount; + int pointerId[MAX_TOUCH_POINTS]; + Vector2 position[MAX_TOUCH_POINTS]; +} GestureEvent; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- + +static GestureType gestureType = TYPE_MOTIONLESS; +static double eventTime = 0; +//static int32_t touchId; // Not used... + +// Tap +// Our initial press position on tap +static Vector2 initialTapPosition = { 0, 0 }; + +// Double tap +// If we are double tapping or not +static bool doubleTapping = false; +// If we recently made a tap +static bool untap = false; + +// Drag +// Our initial press position on drag +static Vector2 initialDragPosition = { 0, 0 }; +// Position that will compare itself with the mouse one +static Vector2 endDragPosition = { 0, 0 }; +// Position of the last event detection +static Vector2 lastDragPosition = { 0, 0 }; +// The total drag vector +static Vector2 dragVector = { 0, 0 }; +// The distance traveled dragging +static float magnitude = 0; +// The angle direction of the drag +static float angle = 0; +// A magnitude to calculate how fast we did the drag ( pixels per frame ) +static float intensity = 0; +// Time that have passed while dragging +static int draggingTimeCounter = 0; + +// Pinch +// First initial pinch position +static Vector2 firstInitialPinchPosition = { 0, 0 }; +// Second initial pinch position +static Vector2 secondInitialPinchPosition = { 0, 0 }; +// First end pinch position +static Vector2 firstEndPinchPosition = { 0, 0 }; +// Second end pinch position +static Vector2 secondEndPinchPosition = { 0, 0 }; +// Delta Displacement +static float pinchDelta = 0; + +// Detected gesture +static int currentGesture = GESTURE_NONE; + +static Vector2 touchPosition; + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +extern void ProcessMotionEvent(GestureEvent event); +extern void ResetGestures(void); +extern Vector2 GetRawPosition(void); + +static float CalculateAngle(Vector2 initialPosition, Vector2 actualPosition, float magnitude); +static float OnPinch(); +static void SetDualInput(GestureEvent event); +static float Distance(Vector2 v1, Vector2 v2); +static float DotProduct(Vector2 v1, Vector2 v2); +static double GetCurrentTime(); + +#if defined(PLATFORM_WEB) +static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); +#endif + +#if defined(PLATFORM_ANDROID) +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event); +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition +//---------------------------------------------------------------------------------- + +// Returns tap position XY +extern Vector2 GetRawPosition(void) +{ + return touchPosition; +} + +// Check if a gesture have been detected +bool IsGestureDetected(void) +{ + if (currentGesture == GESTURE_DRAG) TraceLog(INFO, "DRAG"); + else if (currentGesture == GESTURE_TAP) TraceLog(INFO, "TAP"); + else if (currentGesture == GESTURE_DOUBLETAP) TraceLog(INFO, "DOUBLE"); + else if (currentGesture == GESTURE_HOLD) TraceLog(INFO, "HOLD"); + else if (currentGesture == GESTURE_SWIPE_RIGHT) TraceLog(INFO, "RIGHT"); + else if (currentGesture == GESTURE_SWIPE_UP) TraceLog(INFO, "UP"); + else if (currentGesture == GESTURE_SWIPE_LEFT) TraceLog(INFO, "LEFT"); + else if (currentGesture == GESTURE_SWIPE_DOWN) TraceLog(INFO, "DOWN"); + else if (currentGesture == GESTURE_PINCH_IN) TraceLog(INFO, "PINCH IN"); + else if (currentGesture == GESTURE_PINCH_OUT) TraceLog(INFO, "PINCH OUT"); + + if (currentGesture != GESTURE_NONE) return true; + else return false; +} + +// Check gesture type +int GetGestureType(void) +{ + return currentGesture; +} + +// Get drag intensity (pixels per frame) +float GetDragIntensity(void) +{ + return intensity; +} + +// Get drag angle +// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise +float GetDragAngle(void) +{ + return angle; +} + +// Get drag vector (between initial and final position) +Vector2 GetDragVector(void) +{ + return dragVector; +} + +// Hold time measured in frames +int GetHoldDuration(void) +{ + return 0; +} + +// Get magnitude between two pinch points +float GetPinchDelta(void) +{ + return pinchDelta; +} + +// Get angle beween two pinch points +// NOTE: Angle in degrees, horizontal-right is 0, counterclock-wise +float GetPinchAngle(void) +{ + return 0; +} + +extern void ResetGestures(void) +{ + if (currentGesture == GESTURE_TAP) currentGesture = GESTURE_HOLD; + else if (currentGesture != GESTURE_HOLD) currentGesture = GESTURE_NONE; +} + +#if defined(PLATFORM_WEB) +extern void InitWebGestures(void) +{ + /* + emscripten_set_touchstart_callback("#canvas", data, 0, Emscripten_HandleTouch); + emscripten_set_touchend_callback("#canvas", data, 0, Emscripten_HandleTouch); + emscripten_set_touchmove_callback("#canvas", data, 0, Emscripten_HandleTouch); + emscripten_set_touchcancel_callback("#canvas", data, 0, Emscripten_HandleTouch); + */ + + //emscripten_set_touchstart_callback(0, NULL, 1, Emscripten_HandleTouch); + + emscripten_set_touchstart_callback("#canvas", NULL, 1, EmscriptenInputCallback); + emscripten_set_touchend_callback("#canvas", NULL, 1, EmscriptenInputCallback); + emscripten_set_touchmove_callback("#canvas", NULL, 1, EmscriptenInputCallback); + emscripten_set_touchcancel_callback("#canvas", NULL, 1, EmscriptenInputCallback); +} +#endif + +#if defined(PLATFORM_ANDROID) +extern void InitAndroidGestures(struct android_app *app) +{ + app->onInputEvent = AndroidInputCallback; + + // TODO: Receive frameBuffer data: displayWidth/displayHeight, renderWidth/renderHeight, screenWidth/screenHeight +} +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +extern void ProcessMotionEvent(GestureEvent event) +{ + // Resets + dragVector = (Vector2){ 0, 0 }; + pinchDelta = 0; + + switch (gestureType) + { + case TYPE_MOTIONLESS: // Detect TAP, DOUBLE_TAP and HOLD events + { + if (event.action == DOWN) + { + if (event.pointCount > 1) SetDualInput(event); + else + { + // Set the press position + initialTapPosition = event.position[0]; + + // If too much time have passed, we reset the double tap + if (GetCurrentTime() - eventTime > TAP_TIMEOUT) untap = false; + + // If we are in time, we detect the double tap + if (untap) doubleTapping = true; + + // Update our event time + eventTime = GetCurrentTime(); + + // Set hold + if (doubleTapping) currentGesture = GESTURE_DOUBLETAP; + else currentGesture = GESTURE_TAP; + } + } + else if (event.action == UP) + { + currentGesture = GESTURE_NONE; + + // Detect that we are tapping instead of holding + if (GetCurrentTime() - eventTime < TAP_TIMEOUT) + { + if (doubleTapping) untap = false; + else untap = true; + } + + // Tap finished + doubleTapping = false; + + // Update our event time + eventTime = GetCurrentTime(); + } + // Begin dragging + else if (event.action == MOVE) + { + if (event.pointCount > 1) SetDualInput(event); + else + { + // Set the drag starting position + initialDragPosition = initialTapPosition; + endDragPosition = initialDragPosition; + + // Initialize drag + draggingTimeCounter = 0; + gestureType = TYPE_DRAG; + currentGesture = GESTURE_NONE; + } + } + } break; + case TYPE_DRAG: // Detect DRAG and SWIPE events + { + // end of the drag + if (event.action == UP) + { + // Return Swipe if we have enough sensitivity + if (intensity > FORCE_TO_SWIPE) + { + if (angle < 30 || angle > 330) currentGesture = GESTURE_SWIPE_RIGHT; // Right + else if (angle > 60 && angle < 120) currentGesture = GESTURE_SWIPE_UP; // Up + else if (angle > 150 && angle < 210) currentGesture = GESTURE_SWIPE_LEFT; // Left + else if (angle > 240 && angle < 300) currentGesture = GESTURE_SWIPE_DOWN; // Down + } + + magnitude = 0; + angle = 0; + intensity = 0; + + gestureType = TYPE_MOTIONLESS; + } + // Update while we are dragging + else if (event.action == MOVE) + { + if (event.pointCount > 1) SetDualInput(event); + else + { + lastDragPosition = endDragPosition; + + endDragPosition = GetRawPosition(); + + //endDragPosition.x = AMotionEvent_getX(event, 0); + //endDragPosition.y = AMotionEvent_getY(event, 0); + + // Calculate attributes + dragVector = (Vector2){ endDragPosition.x - lastDragPosition.x, endDragPosition.y - lastDragPosition.y }; + magnitude = sqrt(pow(endDragPosition.x - initialDragPosition.x, 2) + pow(endDragPosition.y - initialDragPosition.y, 2)); + angle = CalculateAngle(initialDragPosition, endDragPosition, magnitude); + intensity = magnitude / (float)draggingTimeCounter; + + currentGesture = GESTURE_DRAG; + draggingTimeCounter++; + } + } + } break; + case TYPE_DUAL_INPUT: + { + if (event.action == UP) + { + if (event.pointCount == 1) + { + // Set the drag starting position + initialTapPosition = event.position[0]; + } + gestureType = TYPE_MOTIONLESS; + } + else if (event.action == MOVE) + { + // Adapt the ending position of the inputs + firstEndPinchPosition = event.position[0]; + secondEndPinchPosition = event.position[1]; + + // If there is no more than two inputs + if (event.pointCount == 2) + { + // Detect pinch delta + pinchDelta = OnPinch(); + + // Pinch gesture resolution + if (pinchDelta != 0) + { + if (pinchDelta > 0) currentGesture = GESTURE_PINCH_IN; + else currentGesture = GESTURE_PINCH_OUT; + } + } + else + { + // Set the drag starting position + initialTapPosition = event.position[0]; + + gestureType = TYPE_MOTIONLESS; + } + + // Readapt the initial position of the inputs + firstInitialPinchPosition = firstEndPinchPosition; + secondInitialPinchPosition = secondEndPinchPosition; + } + } break; + } + //-------------------------------------------------------------------- +} + + +static float CalculateAngle(Vector2 initialPosition, Vector2 actualPosition, float magnitude) +{ + float angle; + + // Calculate arcsinus of the movement ( Our sinus is (actualPosition.y - initialPosition.y) / magnitude) + angle = asin((actualPosition.y - initialPosition.y) / magnitude); + angle *= RAD2DEG; + + // Calculate angle depending on the sector + if (actualPosition.x - initialPosition.x >= 0) + { + // Sector 4 + if (actualPosition.y - initialPosition.y >= 0) + { + angle *= -1; + angle += 360; + } + // Sector 1 + else + { + angle *= -1; + } + } + else + { + // Sector 3 + if (actualPosition.y - initialPosition.y >= 0) + { + angle += 180; + } + // Sector 2 + else + { + angle *= -1; + angle = 180 - angle; + } + } + + return angle; +} + +static float OnPinch() +{ + // Calculate distances + float initialDistance = Distance(firstInitialPinchPosition, secondInitialPinchPosition); + float endDistance = Distance(firstEndPinchPosition, secondEndPinchPosition); + + // Calculate Vectors + Vector2 firstTouchVector = { firstEndPinchPosition.x - firstInitialPinchPosition.x, firstEndPinchPosition.y - firstInitialPinchPosition.y }; + Vector2 secondTouchVector = { secondEndPinchPosition.x - secondInitialPinchPosition.x, secondEndPinchPosition.y - secondInitialPinchPosition.y }; + + // Detect the pinch gesture + // Calculate Distances + if (DotProduct(firstTouchVector, secondTouchVector) < -0.5) return initialDistance - endDistance; + else return 0; +} + +static void SetDualInput(GestureEvent event) +{ + initialDragPosition = (Vector2){ 0, 0 }; + endDragPosition = (Vector2){ 0, 0 }; + lastDragPosition = (Vector2){ 0, 0 }; + + // Initialize positions + firstInitialPinchPosition = event.position[0]; + secondInitialPinchPosition = event.position[1]; + + firstEndPinchPosition = firstInitialPinchPosition; + secondEndPinchPosition = secondInitialPinchPosition; + + // Resets + magnitude = 0; + angle = 0; + intensity = 0; + + gestureType = TYPE_DUAL_INPUT; +} + +static float Distance(Vector2 v1, Vector2 v2) +{ + float result; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + + result = sqrt(dx*dx + dy*dy); + + return result; +} + +static float DotProduct(Vector2 v1, Vector2 v2) +{ + float result; + + float v1Module = sqrt(v1.x*v1.x + v1.y*v1.y); + float v2Module = sqrt(v2.x*v2.x + v2.y*v2.y); + + Vector2 v1Normalized = { v1.x / v1Module, v1.y / v1Module }; + Vector2 v2Normalized = { v2.x / v2Module, v2.y / v2Module }; + + result = v1Normalized.x*v2Normalized.x + v1Normalized.y*v2Normalized.y; + + return result; +} + +static double GetCurrentTime() +{ + double time = 0; +#if defined(_WIN32) +/* + // NOTE: Requires Windows.h + FILETIME tm; + GetSystemTimePreciseAsFileTime(&tm); + ULONGLONG nowTime = ((ULONGLONG)tm.dwHighDateTime << 32) | (ULONGLONG)tm.dwLowDateTime; // Time provided in 100-nanosecond intervals + + time = ((double)nowTime/10000000.0); // time in seconds +*/ +#endif + +#if defined(__linux) + // NOTE: Only for Linux-based systems + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + uint64_t nowTime = (uint64_t)now.tv_sec*1000000000LLU + (uint64_t)now.tv_nsec; // Time provided in nanoseconds + + time = ((double)nowTime/1000000.0); // time in miliseconds +#endif + + return time; +} + +#if defined(PLATFORM_ANDROID) +// Android: Get input events +static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) +{ + int type = AInputEvent_getType(event); + + if (type == AINPUT_EVENT_TYPE_MOTION) + { + touchPosition.x = AMotionEvent_getX(event, 0); + touchPosition.y = AMotionEvent_getY(event, 0); + } + else if (type == AINPUT_EVENT_TYPE_KEY) + { + //int32_t key = AKeyEvent_getKeyCode(event); + //int32_t AKeyEvent_getMetaState(event); + } + + int32_t action = AMotionEvent_getAction(event); + unsigned int flags = action & AMOTION_EVENT_ACTION_MASK; + + GestureEvent gestureEvent; + + // Action + if (flags == AMOTION_EVENT_ACTION_DOWN) gestureEvent.action = DOWN; + else if (flags == AMOTION_EVENT_ACTION_UP) gestureEvent.action = UP; + else if (flags == AMOTION_EVENT_ACTION_MOVE) gestureEvent.action = MOVE; + + // Points + gestureEvent.pointCount = AMotionEvent_getPointerCount(event); + + // Position + gestureEvent.position[0] = (Vector2){ AMotionEvent_getX(event, 0), AMotionEvent_getY(event, 0) }; + gestureEvent.position[1] = (Vector2){ AMotionEvent_getX(event, 1), AMotionEvent_getY(event, 1) }; + + ProcessMotionEvent(gestureEvent); + + return 0; +} +#endif + +#if defined(PLATFORM_WEB) +// Web: Get input events +static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) +{ + /* + for (int i = 0; i < touchEvent->numTouches; i++) + { + long x, y, id; + + if (!touchEvent->touches[i].isChanged) continue; + + id = touchEvent->touches[i].identifier; + x = touchEvent->touches[i].canvasX; + y = touchEvent->touches[i].canvasY; + } + + 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" : ""); + + for(int i = 0; i < event->numTouches; ++i) + { + const EmscriptenTouchPoint *t = &event->touches[i]; + + printf(" %ld: screen: (%ld,%ld), client: (%ld,%ld), page: (%ld,%ld), isChanged: %d, onTarget: %d, canvas: (%ld, %ld)\n", + t->identifier, t->screenX, t->screenY, t->clientX, t->clientY, t->pageX, t->pageY, t->isChanged, t->onTarget, t->canvasX, t->canvasY); + } + */ + GestureEvent gestureEvent; + + // Action + if (eventType == EMSCRIPTEN_EVENT_TOUCHSTART) gestureEvent.action = DOWN; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHEND) gestureEvent.action = UP; + else if (eventType == EMSCRIPTEN_EVENT_TOUCHMOVE) gestureEvent.action = MOVE; + + // Points + gestureEvent.pointCount = touchEvent->numTouches; + + // Position + gestureEvent.position[0] = (Vector2){ touchEvent->touches[0].canvasX, touchEvent->touches[0].canvasY }; + gestureEvent.position[1] = (Vector2){ touchEvent->touches[1].canvasX, touchEvent->touches[1].canvasY }; + + touchPosition = gestureEvent.position[0]; + + ProcessMotionEvent(gestureEvent); + + return 1; +} +#endif + + + + + + diff --git a/src/makefile b/src/makefile index a726b92e..11bbea9d 100644 --- a/src/makefile +++ b/src/makefile @@ -93,7 +93,7 @@ else endif # define all object files required -OBJS = core.o rlgl.o raymath.o shapes.o text.o textures.o models.o audio.o utils.o stb_vorbis.o +OBJS = core.o rlgl.o raymath.o shapes.o text.o textures.o models.o audio.o utils.o camera.o gestures.o # typing 'make' will invoke the first target entry in the file, # in this case, the 'default' target entry is raylib @@ -144,9 +144,13 @@ audio.o: audio.c utils.o: utils.c $(CC) -c utils.c $(CFLAGS) $(INCLUDES) -D$(PLATFORM) -# compile stb_vorbis library -stb_vorbis.o: stb_vorbis.c - $(CC) -c stb_vorbis.c $(CFLAGS) $(INCLUDES) -D$(PLATFORM) +# compile camera module +camera.o: camera.c + $(CC) -c camera.c $(CFLAGS) $(INCLUDES) -D$(PLATFORM) + +# compile gestures module +gestures.o: gestures.c + $(CC) -c gestures.c $(CFLAGS) $(INCLUDES) -D$(PLATFORM) # clean everything clean: @@ -155,8 +159,7 @@ ifeq ($(PLATFORM),PLATFORM_DESKTOP) rm -f *.o libraylib.a else ifeq ($(PLATFORM_OS),LINUX) - find . -type f -executable -delete - rm -f *.o libraylib.a + find -type f -executable | xargs file -i | grep -E 'x-object|x-archive|x-sharedlib|x-executable' | rev | cut -d ':' -f 2- | rev | xargs rm -f else del *.o libraylib.a endif diff --git a/src/models.c b/src/models.c index f61f79f5..8d772f02 100644 --- a/src/models.c +++ b/src/models.c @@ -40,7 +40,7 @@ //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- -// Nop... +#define CUBIC_MAP_HALF_BLOCK_SIZE 0.5 //---------------------------------------------------------------------------------- // Types and Structures Definition @@ -285,7 +285,6 @@ void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color rlPushMatrix(); rlTranslatef(centerPos.x, centerPos.y, centerPos.z); rlScalef(radius, radius, radius); - //rlRotatef(rotation, 0, 1, 0); rlBegin(RL_TRIANGLES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -325,7 +324,6 @@ void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Col rlPushMatrix(); rlTranslatef(centerPos.x, centerPos.y, centerPos.z); rlScalef(radius, radius, radius); - //rlRotatef(rotation, 0, 1, 0); rlBegin(RL_LINES); rlColor4ub(color.r, color.g, color.b, color.a); @@ -446,48 +444,17 @@ void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, fl rlPopMatrix(); } -// Draw a quad -void DrawQuad(Vector3 vertices[4], Vector2 textcoords[4], Vector3 normals[4], Color colors[4]) -{ - rlBegin(RL_QUADS); - rlColor4ub(colors[0].r, colors[0].g, colors[0].b, colors[0].a); - rlNormal3f(normals[0].x, normals[0].y, normals[0].z); - rlTexCoord2f(textcoords[0].x, textcoords[0].y); - rlVertex3f(vertices[0].x, vertices[0].y, vertices[0].z); - - rlColor4ub(colors[1].r, colors[1].g, colors[1].b, colors[1].a); - rlNormal3f(normals[1].x, normals[1].y, normals[1].z); - rlTexCoord2f(textcoords[1].x, textcoords[1].y); - rlVertex3f(vertices[1].x, vertices[1].y, vertices[1].z); - - rlColor4ub(colors[2].r, colors[2].g, colors[2].b, colors[2].a); - rlNormal3f(normals[2].x, normals[2].y, normals[2].z); - rlTexCoord2f(textcoords[2].x, textcoords[2].y); - rlVertex3f(vertices[2].x, vertices[2].y, vertices[2].z); - - rlColor4ub(colors[3].r, colors[3].g, colors[3].b, colors[3].a); - rlNormal3f(normals[3].x, normals[3].y, normals[3].z); - rlTexCoord2f(textcoords[3].x, textcoords[3].y); - rlVertex3f(vertices[3].x, vertices[3].y, vertices[3].z); - rlEnd(); -} - // Draw a plane -void DrawPlane(Vector3 centerPos, Vector2 size, Vector3 rotation, Color color) +void DrawPlane(Vector3 centerPos, Vector2 size, Color color) { // NOTE: QUADS usage require defining a texture on OpenGL 3.3+ if (rlGetVersion() != OPENGL_11) rlEnableTexture(whiteTexture); // Default white texture - // NOTE: Plane is always created on XZ ground and then rotated + // NOTE: Plane is always created on XZ ground rlPushMatrix(); rlTranslatef(centerPos.x, centerPos.y, centerPos.z); rlScalef(size.x, 1.0f, size.y); - // TODO: Review multiples rotations Gimbal-Lock... use matrix or quaternions... - rlRotatef(rotation.x, 1, 0, 0); - rlRotatef(rotation.y, 0, 1, 0); - rlRotatef(rotation.z, 0, 0, 1); - rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); rlNormal3f(0.0f, 1.0f, 0.0f); @@ -501,51 +468,34 @@ void DrawPlane(Vector3 centerPos, Vector2 size, Vector3 rotation, Color color) if (rlGetVersion() != OPENGL_11) rlDisableTexture(); } -// Draw a plane with divisions -// TODO: Test this function -void DrawPlaneEx(Vector3 centerPos, Vector2 size, Vector3 rotation, int slicesX, int slicesZ, Color color) +// Draw a quad +void DrawQuad(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, Color color) { - float quadWidth = size.x / slicesX; - float quadLenght = size.y / slicesZ; - - float texPieceW = 1 / size.x; - float texPieceH = 1 / size.y; - - // NOTE: Plane is always created on XZ ground and then rotated - rlPushMatrix(); - rlTranslatef(-size.x / 2, 0.0f, -size.y / 2); - rlTranslatef(centerPos.x, centerPos.y, centerPos.z); - - // TODO: Review multiples rotations Gimbal-Lock... use matrix or quaternions... - rlRotatef(rotation.x, 1, 0, 0); - rlRotatef(rotation.y, 0, 1, 0); - rlRotatef(rotation.z, 0, 0, 1); - - rlBegin(RL_QUADS); - rlColor4ub(color.r, color.g, color.b, color.a); - rlNormal3f(0.0f, 1.0f, 0.0f); - - for (int z = 0; z < slicesZ; z++) - { - for (int x = 0; x < slicesX; x++) - { - // Draw the plane quad by quad (with textcoords) - rlTexCoord2f((float)x * texPieceW, (float)z * texPieceH); - rlVertex3f((float)x * quadWidth, 0.0f, (float)z * quadLenght); + // TODO: Calculate normals from vertex position + + rlBegin(RL_QUADS); + rlColor4ub(color.r, color.g, color.b, color.a); + //rlNormal3f(0.0f, 0.0f, 0.0f); - rlTexCoord2f((float)x * texPieceW + texPieceW, (float)z * texPieceH); - rlVertex3f((float)x * quadWidth + quadWidth, 0.0f, (float)z * quadLenght); + rlVertex3f(v1.x, v1.y, v1.z); + rlVertex3f(v2.x, v2.y, v2.z); + rlVertex3f(v3.x, v3.y, v3.z); + rlVertex3f(v4.x, v4.y, v4.z); + rlEnd(); +} - rlTexCoord2f((float)x * texPieceW + texPieceW, (float)z * texPieceH + texPieceH); - rlVertex3f((float)x * quadWidth + quadWidth, 0.0f, (float)z * quadLenght + quadLenght); +// Draw a ray line +void DrawRay(Ray ray, Color color) +{ + float scale = 10000; - rlTexCoord2f((float)x * texPieceW, (float)z * texPieceH + texPieceH); - rlVertex3f((float)x * quadWidth, 0.0f, (float)z * quadLenght + quadLenght); - } - } - rlEnd(); + rlBegin(RL_LINES); + rlColor4ub(color.r, color.g, color.b, color.a); + rlColor4ub(color.r, color.g, color.b, color.a); - rlPopMatrix(); + rlVertex3f(ray.position.x, ray.position.y, ray.position.z); + rlVertex3f(ray.position.x + ray.direction.x*scale, ray.position.y + ray.direction.y*scale, ray.position.z + ray.direction.z*scale); + rlEnd(); } // Draw a grid centered at (0, 0, 0) @@ -604,76 +554,7 @@ void DrawGizmo(Vector3 position) rlPopMatrix(); } -void DrawGizmoEx(Vector3 position, Vector3 rotation, float scale) -{ - // NOTE: RGB = XYZ - rlPushMatrix(); - rlTranslatef(position.x, position.y, position.z); - rlScalef(scale, scale, scale); - rlRotatef(rotation.y, 0, 1, 0); - - rlBegin(RL_LINES); - // X Axis - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x, position.y, position.z); - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x + 1, position.y, position.z); - - // ArrowX - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x + 1.1, position.y, position.z); - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x + .9, position.y, position.z + .1); - - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x + 1.1, position.y, position.z); - rlColor4ub(200, 0, 0, 255); rlVertex3f(position.x + .9, position.y, position.z - .1); - - // Y Axis - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x, position.y, position.z); - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x, position.y + 1, position.z); - - // ArrowY - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x, position.y + 1.1, position.z); - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x + .1, position.y + .9, position.z); - - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x, position.y + 1.1, position.z); - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x - .1, position.y + .9, position.z); - - // Z Axis - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x, position.y, position.z); - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x, position.y, position.z - 1); - - // ArrowZ - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x, position.y, position.z - 1.1); - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x + .1, position.y, position.z - .9); - - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x, position.y, position.z - 1.1); - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x - .1, position.y, position.z - .9); - - // Extra - int n = 3; - - // X Axis - for (int i=0; i < 360; i += 6) - { - rlColor4ub(200, 0, 0, 255); rlVertex3f(0, position.x + sin(DEG2RAD*i) * scale/n, position.y + cos(DEG2RAD*i) * scale/n); - rlColor4ub(200, 0, 0, 255); rlVertex3f(0, position.x + sin(DEG2RAD*(i+6)) * scale/n, position.y + cos(DEG2RAD*(i+6)) * scale/n); - } - - // Y Axis - for (int i=0; i < 360; i += 6) - { - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x + sin(DEG2RAD*i) * scale/n, 0, position.y + cos(DEG2RAD*i) * scale/n); - rlColor4ub(0, 200, 0, 255); rlVertex3f(position.x + sin(DEG2RAD*(i+6)) * scale/n, 0, position.y + cos(DEG2RAD*(i+6)) * scale/n); - } - - // Z Axis - for (int i=0; i < 360; i += 6) - { - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x + sin(DEG2RAD*i) * scale/n, position.y + cos(DEG2RAD*i) * scale/n, 0); - rlColor4ub(0, 0, 200, 255); rlVertex3f(position.x + sin(DEG2RAD*(i+6)) * scale/n, position.y + cos(DEG2RAD*(i+6)) * scale/n, 0); - } - rlEnd(); - rlPopMatrix(); -} - -// Load a 3d model +// Load a 3d model (from file) Model LoadModel(const char *fileName) { VertexData vData; @@ -683,17 +564,34 @@ Model LoadModel(const char *fileName) // NOTE: At this point we have all vertex, texcoord, normal data for the model in vData struct + // NOTE: model properties (transform, texture, shader) are initialized inside rlglLoadModel() Model model = rlglLoadModel(vData); // Upload vertex data to GPU // Now that vertex data is uploaded to GPU, we can free arrays - // NOTE: Despite vertex data is useless on OpenGL 3.3 or ES2, we will keep it... - //free(vData.vertices); - //free(vData.texcoords); - //free(vData.normals); + // NOTE: We don't need CPU vertex data on OpenGL 3.3 or ES2 + if (rlGetVersion() != OPENGL_11) + { + free(vData.vertices); + free(vData.texcoords); + free(vData.normals); + } return model; } +// Load a 3d model (from vertex data) +Model LoadModelEx(VertexData data) +{ + Model model; + + // NOTE: model properties (transform, texture, shader) are initialized inside rlglLoadModel() + model = rlglLoadModel(data); // Upload vertex data to GPU + + // NOTE: Vertex data is managed externally, must be deallocated manually + + return model; +} + // Load a heightmap image as a 3d model Model LoadHeightmap(Image heightmap, float maxHeight) { @@ -701,6 +599,8 @@ Model LoadHeightmap(Image heightmap, float maxHeight) int mapX = heightmap.width; int mapZ = heightmap.height; + + Color *heightmapPixels = GetPixelData(heightmap); // NOTE: One vertex per pixel // TODO: Consider resolution when generating model data? @@ -730,15 +630,15 @@ Model LoadHeightmap(Image heightmap, float maxHeight) // one triangle - 3 vertex vData.vertices[vCounter] = x; - vData.vertices[vCounter + 1] = GetHeightValue(heightmap.pixels[x + z*mapX])*scaleFactor; + vData.vertices[vCounter + 1] = GetHeightValue(heightmapPixels[x + z*mapX])*scaleFactor; vData.vertices[vCounter + 2] = z; vData.vertices[vCounter + 3] = x; - vData.vertices[vCounter + 4] = GetHeightValue(heightmap.pixels[x + (z+1)*mapX])*scaleFactor; + vData.vertices[vCounter + 4] = GetHeightValue(heightmapPixels[x + (z+1)*mapX])*scaleFactor; vData.vertices[vCounter + 5] = z+1; vData.vertices[vCounter + 6] = x+1; - vData.vertices[vCounter + 7] = GetHeightValue(heightmap.pixels[(x+1) + z*mapX])*scaleFactor; + vData.vertices[vCounter + 7] = GetHeightValue(heightmapPixels[(x+1) + z*mapX])*scaleFactor; vData.vertices[vCounter + 8] = z; // another triangle - 3 vertex @@ -751,7 +651,7 @@ Model LoadHeightmap(Image heightmap, float maxHeight) vData.vertices[vCounter + 14] = vData.vertices[vCounter + 5]; vData.vertices[vCounter + 15] = x+1; - vData.vertices[vCounter + 16] = GetHeightValue(heightmap.pixels[(x+1) + (z+1)*mapX])*scaleFactor; + vData.vertices[vCounter + 16] = GetHeightValue(heightmapPixels[(x+1) + (z+1)*mapX])*scaleFactor; vData.vertices[vCounter + 17] = z+1; vCounter += 18; // 6 vertex, 18 floats @@ -793,6 +693,8 @@ Model LoadHeightmap(Image heightmap, float maxHeight) trisCounter += 2; } } + + free(heightmapPixels); // Fill color data // NOTE: Not used any more... just one plain color defined at DrawModel() @@ -803,26 +705,31 @@ Model LoadHeightmap(Image heightmap, float maxHeight) Model model = rlglLoadModel(vData); // Now that vertex data is uploaded to GPU, we can free arrays - // NOTE: Despite vertex data is useless on OpenGL 3.3 or ES2, we will keep it... - //free(vData.vertices); - //free(vData.texcoords); - //free(vData.normals); + // NOTE: We don't need CPU vertex data on OpenGL 3.3 or ES2 + if (rlGetVersion() != OPENGL_11) + { + free(vData.vertices); + free(vData.texcoords); + free(vData.normals); + } return model; } // Load a map image as a 3d model (cubes based) -Model LoadCubicmap(Image cubesmap) +Model LoadCubicmap(Image cubicmap) { VertexData vData; + Color *cubicmapPixels = GetPixelData(cubicmap); + // Map cube size will be 1.0 float mapCubeSide = 1.0f; - int mapWidth = cubesmap.width * (int)mapCubeSide; - int mapHeight = cubesmap.height * (int)mapCubeSide; + int mapWidth = cubicmap.width * (int)mapCubeSide; + int mapHeight = cubicmap.height * (int)mapCubeSide; // NOTE: Max possible number of triangles numCubes * (12 triangles by cube) - int maxTriangles = cubesmap.width*cubesmap.height*12; + int maxTriangles = cubicmap.width*cubicmap.height*12; int vCounter = 0; // Used to count vertices int tcCounter = 0; // Used to count texcoords @@ -830,12 +737,35 @@ Model LoadCubicmap(Image cubesmap) float w = mapCubeSide; float h = mapCubeSide; - float h2 = mapCubeSide; + float h2 = mapCubeSide * 1.5; // TODO: Review walls height... Vector3 *mapVertices = (Vector3 *)malloc(maxTriangles * 3 * sizeof(Vector3)); Vector2 *mapTexcoords = (Vector2 *)malloc(maxTriangles * 3 * sizeof(Vector2)); Vector3 *mapNormals = (Vector3 *)malloc(maxTriangles * 3 * sizeof(Vector3)); + // Define the 6 normals of the cube, we will combine them accordingly later... + Vector3 n1 = { 1.0f, 0.0f, 0.0f }; + Vector3 n2 = { -1.0f, 0.0f, 0.0f }; + Vector3 n3 = { 0.0f, 1.0f, 0.0f }; + Vector3 n4 = { 0.0f, -1.0f, 0.0f }; + Vector3 n5 = { 0.0f, 0.0f, 1.0f }; + Vector3 n6 = { 0.0f, 0.0f, -1.0f }; + + // NOTE: We use texture rectangles to define different textures for top-bottom-front-back-right-left (6) + typedef struct RectangleF { + float x; + float y; + float width; + float height; + } RectangleF; + + RectangleF rightTexUV = { 0, 0, 0.5, 0.5 }; + RectangleF leftTexUV = { 0.5, 0, 0.5, 0.5 }; + RectangleF frontTexUV = { 0, 0, 0.5, 0.5 }; + RectangleF backTexUV = { 0.5, 0, 0.5, 0.5 }; + RectangleF topTexUV = { 0, 0.5, 0.5, 0.5 }; + RectangleF bottomTexUV = { 0.5, 0.5, 0.5, 0.5 }; + for (int z = 0; z < mapHeight; z += mapCubeSide) { for (int x = 0; x < mapWidth; x += mapCubeSide) @@ -850,25 +780,10 @@ Model LoadCubicmap(Image cubesmap) Vector3 v7 = { x - w/2, 0, z + h/2 }; Vector3 v8 = { x + w/2, 0, z + h/2 }; - // Define the 6 normals of the cube, we will combine them accordingly later... - Vector3 n1 = { 1.0f, 0.0f, 0.0f }; - Vector3 n2 = { -1.0f, 0.0f, 0.0f }; - Vector3 n3 = { 0.0f, 1.0f, 0.0f }; - Vector3 n4 = { 0.0f, -1.0f, 0.0f }; - Vector3 n5 = { 0.0f, 0.0f, 1.0f }; - Vector3 n6 = { 0.0f, 0.0f, -1.0f }; - - // Define the 4 texture coordinates of the cube, we will combine them accordingly later... - // TODO: Use texture rectangles to define different textures for top-bottom-front-back-right-left (6) - Vector2 vt2 = { 0.0f, 0.0f }; - Vector2 vt1 = { 0.0f, 1.0f }; - Vector2 vt4 = { 1.0f, 0.0f }; - Vector2 vt3 = { 1.0f, 1.0f }; - // We check pixel color to be WHITE, we will full cubes - if ((cubesmap.pixels[z*cubesmap.width + x].r == 255) && - (cubesmap.pixels[z*cubesmap.width + x].g == 255) && - (cubesmap.pixels[z*cubesmap.width + x].b == 255)) + if ((cubicmapPixels[z*cubicmap.width + x].r == 255) && + (cubicmapPixels[z*cubicmap.width + x].g == 255) && + (cubicmapPixels[z*cubicmap.width + x].b == 255)) { // Define triangles (Checking Collateral Cubes!) //---------------------------------------------- @@ -890,12 +805,12 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n3; nCounter += 6; - mapTexcoords[tcCounter] = vt2; - mapTexcoords[tcCounter + 1] = vt1; - mapTexcoords[tcCounter + 2] = vt3; - mapTexcoords[tcCounter + 3] = vt2; - mapTexcoords[tcCounter + 4] = vt3; - mapTexcoords[tcCounter + 5] = vt4; + mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; tcCounter += 6; // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) @@ -915,18 +830,18 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n4; nCounter += 6; - mapTexcoords[tcCounter] = vt4; - mapTexcoords[tcCounter + 1] = vt1; - mapTexcoords[tcCounter + 2] = vt3; - mapTexcoords[tcCounter + 3] = vt4; - mapTexcoords[tcCounter + 4] = vt2; - mapTexcoords[tcCounter + 5] = vt1; + mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; tcCounter += 6; - if (((z < cubesmap.height - 1) && - (cubesmap.pixels[(z + 1)*cubesmap.width + x].r == 0) && - (cubesmap.pixels[(z + 1)*cubesmap.width + x].g == 0) && - (cubesmap.pixels[(z + 1)*cubesmap.width + x].b == 0)) || (z == cubesmap.height - 1)) + if (((z < cubicmap.height - 1) && + (cubicmapPixels[(z + 1)*cubicmap.width + x].r == 0) && + (cubicmapPixels[(z + 1)*cubicmap.width + x].g == 0) && + (cubicmapPixels[(z + 1)*cubicmap.width + x].b == 0)) || (z == cubicmap.height - 1)) { // Define front triangles (2 tris, 6 vertex) --> v2 v7 v3, v3 v7 v8 // NOTE: Collateral occluded faces are not generated @@ -946,19 +861,19 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n6; nCounter += 6; - mapTexcoords[tcCounter] = vt2; - mapTexcoords[tcCounter + 1] = vt1; - mapTexcoords[tcCounter + 2] = vt4; - mapTexcoords[tcCounter + 3] = vt4; - mapTexcoords[tcCounter + 4] = vt1; - mapTexcoords[tcCounter + 5] = vt3; + mapTexcoords[tcCounter] = (Vector2){ frontTexUV.x, frontTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ frontTexUV.x, frontTexUV.y + frontTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ frontTexUV.x + frontTexUV.width, frontTexUV.y + frontTexUV.height }; tcCounter += 6; } if (((z > 0) && - (cubesmap.pixels[(z - 1)*cubesmap.width + x].r == 0) && - (cubesmap.pixels[(z - 1)*cubesmap.width + x].g == 0) && - (cubesmap.pixels[(z - 1)*cubesmap.width + x].b == 0)) || (z == 0)) + (cubicmapPixels[(z - 1)*cubicmap.width + x].r == 0) && + (cubicmapPixels[(z - 1)*cubicmap.width + x].g == 0) && + (cubicmapPixels[(z - 1)*cubicmap.width + x].b == 0)) || (z == 0)) { // Define back triangles (2 tris, 6 vertex) --> v1 v5 v6, v1 v4 v5 // NOTE: Collateral occluded faces are not generated @@ -978,19 +893,19 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n5; nCounter += 6; - mapTexcoords[tcCounter] = vt4; - mapTexcoords[tcCounter + 1] = vt1; - mapTexcoords[tcCounter + 2] = vt3; - mapTexcoords[tcCounter + 3] = vt4; - mapTexcoords[tcCounter + 4] = vt2; - mapTexcoords[tcCounter + 5] = vt1; + mapTexcoords[tcCounter] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y + backTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ backTexUV.x + backTexUV.width, backTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ backTexUV.x, backTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ backTexUV.x, backTexUV.y + backTexUV.height }; tcCounter += 6; } - if (((x < cubesmap.width - 1) && - (cubesmap.pixels[z*cubesmap.width + (x + 1)].r == 0) && - (cubesmap.pixels[z*cubesmap.width + (x + 1)].g == 0) && - (cubesmap.pixels[z*cubesmap.width + (x + 1)].b == 0)) || (x == cubesmap.width - 1)) + if (((x < cubicmap.width - 1) && + (cubicmapPixels[z*cubicmap.width + (x + 1)].r == 0) && + (cubicmapPixels[z*cubicmap.width + (x + 1)].g == 0) && + (cubicmapPixels[z*cubicmap.width + (x + 1)].b == 0)) || (x == cubicmap.width - 1)) { // Define right triangles (2 tris, 6 vertex) --> v3 v8 v4, v4 v8 v5 // NOTE: Collateral occluded faces are not generated @@ -1010,19 +925,19 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n1; nCounter += 6; - mapTexcoords[tcCounter] = vt2; - mapTexcoords[tcCounter + 1] = vt1; - mapTexcoords[tcCounter + 2] = vt4; - mapTexcoords[tcCounter + 3] = vt4; - mapTexcoords[tcCounter + 4] = vt1; - mapTexcoords[tcCounter + 5] = vt3; + mapTexcoords[tcCounter] = (Vector2){ rightTexUV.x, rightTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ rightTexUV.x, rightTexUV.y + rightTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ rightTexUV.x + rightTexUV.width, rightTexUV.y + rightTexUV.height }; tcCounter += 6; } if (((x > 0) && - (cubesmap.pixels[z*cubesmap.width + (x - 1)].r == 0) && - (cubesmap.pixels[z*cubesmap.width + (x - 1)].g == 0) && - (cubesmap.pixels[z*cubesmap.width + (x - 1)].b == 0)) || (x == 0)) + (cubicmapPixels[z*cubicmap.width + (x - 1)].r == 0) && + (cubicmapPixels[z*cubicmap.width + (x - 1)].g == 0) && + (cubicmapPixels[z*cubicmap.width + (x - 1)].b == 0)) || (x == 0)) { // Define left triangles (2 tris, 6 vertex) --> v1 v7 v2, v1 v6 v7 // NOTE: Collateral occluded faces are not generated @@ -1042,25 +957,69 @@ Model LoadCubicmap(Image cubesmap) mapNormals[nCounter + 5] = n2; nCounter += 6; - mapTexcoords[tcCounter] = vt2; - mapTexcoords[tcCounter + 1] = vt3; - mapTexcoords[tcCounter + 2] = vt4; - mapTexcoords[tcCounter + 3] = vt2; - mapTexcoords[tcCounter + 4] = vt1; - mapTexcoords[tcCounter + 5] = vt3; + mapTexcoords[tcCounter] = (Vector2){ leftTexUV.x, leftTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y }; + mapTexcoords[tcCounter + 3] = (Vector2){ leftTexUV.x, leftTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ leftTexUV.x, leftTexUV.y + leftTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ leftTexUV.x + leftTexUV.width, leftTexUV.y + leftTexUV.height }; tcCounter += 6; } } // We check pixel color to be BLACK, we will only draw floor and roof - else if ((cubesmap.pixels[z*cubesmap.width + x].r == 0) && - (cubesmap.pixels[z*cubesmap.width + x].g == 0) && - (cubesmap.pixels[z*cubesmap.width + x].b == 0)) + else if ((cubicmapPixels[z*cubicmap.width + x].r == 0) && + (cubicmapPixels[z*cubicmap.width + x].g == 0) && + (cubicmapPixels[z*cubicmap.width + x].b == 0)) { - // Define top triangles (2 tris, 6 vertex --> v1-v3-v2, v1-v4-v3) - // TODO: ... + // Define top triangles (2 tris, 6 vertex --> v1-v2-v3, v1-v3-v4) + mapVertices[vCounter] = v1; + mapVertices[vCounter + 1] = v3; + mapVertices[vCounter + 2] = v2; + mapVertices[vCounter + 3] = v1; + mapVertices[vCounter + 4] = v4; + mapVertices[vCounter + 5] = v3; + vCounter += 6; + + mapNormals[nCounter] = n4; + mapNormals[nCounter + 1] = n4; + mapNormals[nCounter + 2] = n4; + mapNormals[nCounter + 3] = n4; + mapNormals[nCounter + 4] = n4; + mapNormals[nCounter + 5] = n4; + nCounter += 6; + + mapTexcoords[tcCounter] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ topTexUV.x, topTexUV.y + topTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ topTexUV.x, topTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y }; + mapTexcoords[tcCounter + 5] = (Vector2){ topTexUV.x + topTexUV.width, topTexUV.y + topTexUV.height }; + tcCounter += 6; + + // Define bottom triangles (2 tris, 6 vertex --> v6-v8-v7, v6-v5-v8) + mapVertices[vCounter] = v6; + mapVertices[vCounter + 1] = v7; + mapVertices[vCounter + 2] = v8; + mapVertices[vCounter + 3] = v6; + mapVertices[vCounter + 4] = v8; + mapVertices[vCounter + 5] = v5; + vCounter += 6; + + mapNormals[nCounter] = n3; + mapNormals[nCounter + 1] = n3; + mapNormals[nCounter + 2] = n3; + mapNormals[nCounter + 3] = n3; + mapNormals[nCounter + 4] = n3; + mapNormals[nCounter + 5] = n3; + nCounter += 6; - // Define bottom triangles (2 tris, 6 vertex --> v6-v7-v8, v6-v8-v5) - // TODO: ... + mapTexcoords[tcCounter] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 1] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 2] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 3] = (Vector2){ bottomTexUV.x + bottomTexUV.width, bottomTexUV.y }; + mapTexcoords[tcCounter + 4] = (Vector2){ bottomTexUV.x, bottomTexUV.y + bottomTexUV.height }; + mapTexcoords[tcCounter + 5] = (Vector2){ bottomTexUV.x, bottomTexUV.y }; + tcCounter += 6; } } } @@ -1112,16 +1071,21 @@ Model LoadCubicmap(Image cubesmap) free(mapVertices); free(mapNormals); free(mapTexcoords); + + free(cubicmapPixels); // NOTE: At this point we have all vertex, texcoord, normal data for the model in vData struct Model model = rlglLoadModel(vData); // Now that vertex data is uploaded to GPU, we can free arrays - // NOTE: Despite vertex data is useless on OpenGL 3.3 or ES2, we will keep it... - //free(vData.vertices); - //free(vData.texcoords); - //free(vData.normals); + // NOTE: We don't need CPU vertex data on OpenGL 3.3 or ES2 + if (rlGetVersion() != OPENGL_11) + { + free(vData.vertices); + free(vData.texcoords); + free(vData.normals); + } return model; } @@ -1129,45 +1093,80 @@ Model LoadCubicmap(Image cubesmap) // Unload 3d model from memory void UnloadModel(Model model) { - free(model.mesh.vertices); - free(model.mesh.texcoords); - free(model.mesh.normals); + if (rlGetVersion() == OPENGL_11) + { + free(model.mesh.vertices); + free(model.mesh.texcoords); + free(model.mesh.normals); + } - rlDeleteBuffers(model.vboId[0]); - rlDeleteBuffers(model.vboId[1]); - rlDeleteBuffers(model.vboId[2]); + rlDeleteBuffers(model.mesh.vboId[0]); + rlDeleteBuffers(model.mesh.vboId[1]); + rlDeleteBuffers(model.mesh.vboId[2]); - rlDeleteVertexArrays(model.vaoId); + rlDeleteVertexArrays(model.mesh.vaoId); + //rlDeleteTextures(model.texture.id); + //rlDeleteShader(model.shader.id); } +// Link a texture to a model void SetModelTexture(Model *model, Texture2D texture) { - if (texture.id <= 0) model->textureId = 1; // Default white texture (use mesh color) - else model->textureId = texture.id; + if (texture.id <= 0) + { + model->texture.id = whiteTexture; // Default white texture (use mesh color) + model->shader.texDiffuseId = whiteTexture; + } + else + { + model->texture = texture; + model->shader.texDiffuseId = texture.id; + } +} + +// Load a custom shader (vertex shader + fragment shader) +Shader LoadShader(char *vsFileName, char *fsFileName) +{ + Shader shader = rlglLoadShader(vsFileName, fsFileName); + + return shader; +} + +// Unload a custom shader from memory +void UnloadShader(Shader shader) +{ + rlDeleteShader(shader.id); +} + +// Set shader for a model +void SetModelShader(Model *model, Shader shader) +{ + rlglSetModelShader(model, shader); } // Draw a model (with texture if set) void DrawModel(Model model, Vector3 position, float scale, Color tint) { Vector3 vScale = { scale, scale, scale }; - Vector3 rotation = { 0, 0, 0 }; + Vector3 rotationAxis = { 0, 0, 0 }; - rlglDrawModel(model, position, rotation, vScale, tint, false); + DrawModelEx(model, position, 0.0f, rotationAxis, vScale, tint); } // Draw a model with extended parameters -void DrawModelEx(Model model, Vector3 position, Vector3 rotation, Vector3 scale, Color tint) +void DrawModelEx(Model model, Vector3 position, float rotationAngle, Vector3 rotationAxis, Vector3 scale, Color tint) { - rlglDrawModel(model, position, rotation, scale, tint, false); + // NOTE: Rotation must be provided in degrees, it's converted to radians inside rlglDrawModel() + rlglDrawModel(model, position, rotationAngle, rotationAxis, scale, tint, false); } // Draw a model wires (with texture if set) void DrawModelWires(Model model, Vector3 position, float scale, Color color) { Vector3 vScale = { scale, scale, scale }; - Vector3 rotation = { 0, 0, 0 }; + Vector3 rotationAxis = { 0, 0, 0 }; - rlglDrawModel(model, position, rotation, vScale, color, true); + rlglDrawModel(model, position, 0.0f, rotationAxis, vScale, color, true); } // Draw a billboard @@ -1180,7 +1179,10 @@ void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, MatrixTranspose(&viewMatrix); Vector3 right = { viewMatrix.m0, viewMatrix.m4, viewMatrix.m8 }; - Vector3 up = { viewMatrix.m1, viewMatrix.m5, viewMatrix.m9 }; + //Vector3 up = { viewMatrix.m1, viewMatrix.m5, viewMatrix.m9 }; + + // NOTE: Billboard locked to axis-Y + Vector3 up = { 0, 1, 0 }; /* a-------b | | @@ -1250,11 +1252,11 @@ void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vec // Bottom-left corner for texture and quad rlTexCoord2f((float)sourceRec.x / texture.width, (float)sourceRec.y / texture.height); rlVertex3f(a.x, a.y, a.z); - + // Top-left corner for texture and quad rlTexCoord2f((float)sourceRec.x / texture.width, (float)(sourceRec.y + sourceRec.height) / texture.height); rlVertex3f(d.x, d.y, d.z); - + // Top-right corner for texture and quad rlTexCoord2f((float)(sourceRec.x + sourceRec.width) / texture.width, (float)(sourceRec.y + sourceRec.height) / texture.height); rlVertex3f(c.x, c.y, c.z); @@ -1267,6 +1269,338 @@ void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vec rlDisableTexture(); } + +bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB) +{ + bool collision = false; + + float dx = centerA.x - centerB.x; // X distance between centers + float dy = centerA.y - centerB.y; // Y distance between centers + float dz = centerA.z - centerB.z; // Y distance between centers + + float distance = sqrt(dx*dx + dy*dy + dz*dz); // Distance between centers + + if (distance <= (radiusA + radiusB)) collision = true; + + return collision; +} + +bool CheckCollisionBoxes(Vector3 minBBox1, Vector3 maxBBox1, Vector3 minBBox2, Vector3 maxBBox2) +{ + /* + // Get min and max vertex to construct bounds (AABB) + Vector3 minVertex = tempVertices[0]; + Vector3 maxVertex = tempVertices[0]; + + for (int i = 1; i < tempVertices.Count; i++) + { + minVertex = Vector3.Min(minVertex, tempVertices[i]); + maxVertex = Vector3.Max(maxVertex, tempVertices[i]); + } + + bounds = new BoundingBox(minVertex, maxVertex); + */ + + bool collision = true; + + if ((maxBBox1.x >= minBBox2.x) && (minBBox1.x <= maxBBox2.x)) + { + if ((maxBBox1.y < minBBox2.y) || (minBBox1.y > maxBBox2.y)) collision = false; + if ((maxBBox1.z < minBBox2.z) || (minBBox1.z > maxBBox2.z)) collision = false; + } + else collision = false; + + return collision; +} + +bool CheckCollisionBoxSphere(Vector3 minBBox, Vector3 maxBBox, Vector3 centerSphere, float radiusSphere) +{ + bool collision = false; + + if ((centerSphere.x - minBBox.x > radiusSphere) && (centerSphere.y - minBBox.y > radiusSphere) && (centerSphere.z - minBBox.z > radiusSphere) && + (maxBBox.x - centerSphere.x > radiusSphere) && (maxBBox.y - centerSphere.y > radiusSphere) && (maxBBox.z - centerSphere.z > radiusSphere)) + { + collision = true; + } + else + { + float dmin = 0; + + if (centerSphere.x - minBBox.x <= radiusSphere) + dmin += (centerSphere.x - minBBox.x) * (centerSphere.x - minBBox.x); + else if (maxBBox.x - centerSphere.x <= radiusSphere) + dmin += (centerSphere.x - maxBBox.x) * (centerSphere.x - maxBBox.x); + + if (centerSphere.y - minBBox.y <= radiusSphere) + dmin += (centerSphere.y - minBBox.y) * (centerSphere.y - minBBox.y); + else if (maxBBox.y - centerSphere.y <= radiusSphere) + dmin += (centerSphere.y - maxBBox.y) * (centerSphere.y - maxBBox.y); + + if (centerSphere.z - minBBox.z <= radiusSphere) + dmin += (centerSphere.z - minBBox.z) * (centerSphere.z - minBBox.z); + else if (maxBBox.z - centerSphere.z <= radiusSphere) + dmin += (centerSphere.z - maxBBox.z) * (centerSphere.z - maxBBox.z); + + if (dmin <= radiusSphere * radiusSphere) collision = true; + } + + return collision; +} + +// TODO +//BoundingBox GetCollisionArea(BoundingBox box1, BoundingBox box2) + +// Detect and resolve cubicmap collisions +// NOTE: player position (or camera) is modified inside this function +Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *playerPosition, float radius) +{ + Color *cubicmapPixels = GetPixelData(cubicmap); + + // Detect the cell where the player is located + Vector3 impactDirection = { 0, 0, 0 }; + + int locationCellX = 0; + int locationCellY = 0; + + locationCellX = floor(playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE); + locationCellY = floor(playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE); + + if (locationCellX >= 0 && locationCellY >= 0 && locationCellX < cubicmap.width && locationCellY < cubicmap.height) + { + // Multiple Axis -------------------------------------------------------------------------------------------- + + // Axis x-, y- + if (locationCellX > 0 && locationCellY > 0) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) + { + playerPosition->x = locationCellX + mapPosition.x - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + playerPosition->z = locationCellY + mapPosition.z - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + + // Axis x-, y+ + if (locationCellX > 0 && locationCellY < cubicmap.height - 1) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) + { + playerPosition->x = locationCellX + mapPosition.x - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + playerPosition->z = locationCellY + mapPosition.z + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + + // Axis x+, y- + if (locationCellX < cubicmap.width - 1 && locationCellY > 0) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) + { + playerPosition->x = locationCellX + mapPosition.x + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + playerPosition->z = locationCellY + mapPosition.z - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + + // Axis x+, y+ + if (locationCellX < cubicmap.width - 1 && locationCellY < cubicmap.height - 1) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) + { + playerPosition->x = locationCellX + mapPosition.x + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + playerPosition->z = locationCellY + mapPosition.z + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + + // Single Axis --------------------------------------------------------------------------------------------------- + + // Axis x- + if (locationCellX > 0) + { + if (cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) + { + if ((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) + { + playerPosition->x = locationCellX + mapPosition.x - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 0}; + } + } + } + // Axis x+ + if (locationCellX < cubicmap.width - 1) + { + if (cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) + { + if ((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) + { + playerPosition->x = locationCellX + mapPosition.x + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 1, 0, 0}; + } + } + } + // Axis y- + if (locationCellY > 0) + { + if (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0) + { + if ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius) + { + playerPosition->z = locationCellY + mapPosition.z - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 0, 0, 1}; + } + } + } + // Axis y+ + if (locationCellY < cubicmap.height - 1) + { + if (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0) + { + if ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius) + { + playerPosition->z = locationCellY + mapPosition.z + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + impactDirection = (Vector3) { 0, 0, 1}; + } + } + } + + // Diagonals ------------------------------------------------------------------------------------------------------- + + // Axis x-, y- + if (locationCellX > 0 && locationCellY > 0) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r == 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r == 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX - 1)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX) > ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY)) playerPosition->x = locationCellX + mapPosition.x - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + else playerPosition->z = locationCellY + mapPosition.z - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + + // Return ricochet + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius / 3) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius / 3)) + { + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + } + + // Axis x-, y+ + if (locationCellX > 0 && locationCellY < cubicmap.height - 1) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r == 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r == 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX - 1)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX) > (1 - ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY))) playerPosition->x = locationCellX + mapPosition.x - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + else playerPosition->z = locationCellY + mapPosition.z + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + + // Return ricochet + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius / 3) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius / 3)) + { + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + } + + // Axis x+, y- + if (locationCellX < cubicmap.width - 1 && locationCellY > 0) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r == 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r == 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX + 1)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX) < (1 - ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY))) playerPosition->x = locationCellX + mapPosition.x + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + else playerPosition->z = locationCellY + mapPosition.z - (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + + // Return ricochet + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius / 3) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius / 3)) + { + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + } + + // Axis x+, y+ + if (locationCellX < cubicmap.width - 1 && locationCellY < cubicmap.height - 1) + { + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r == 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r == 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX + 1)].r != 0)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) + { + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX) < ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY)) playerPosition->x = locationCellX + mapPosition.x + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + else playerPosition->z = locationCellY + mapPosition.z + (CUBIC_MAP_HALF_BLOCK_SIZE - radius); + + // Return ricochet + if (((playerPosition->x - mapPosition.x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius / 3) && + ((playerPosition->z - mapPosition.z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius / 3)) + { + impactDirection = (Vector3) { 1, 0, 1}; + } + } + } + } + } + + // Floor collision + if (playerPosition->y <= radius) + { + playerPosition->y = radius + 0.01; + impactDirection = (Vector3) { impactDirection.x, 1, impactDirection.z}; + } + // Roof collision + else if (playerPosition->y >= 1.5 - radius) + { + playerPosition->y = (1.5 - radius) - 0.01; + impactDirection = (Vector3) { impactDirection.x, 1, impactDirection.z}; + } + + free(cubicmapPixels); + + return impactDirection; +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + // Get current vertex y altitude (proportional to pixel colors in grayscale) static float GetHeightValue(Color pixel) { @@ -1289,7 +1623,7 @@ static VertexData LoadOBJ(const char *fileName) FILE *objFile; objFile = fopen(fileName, "rt"); - + if (objFile == NULL) { TraceLog(WARNING, "[%s] OBJ file could not be opened", fileName); @@ -1524,35 +1858,3 @@ static VertexData LoadOBJ(const char *fileName) return vData; } - -bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB) -{ - - return false; -} - -bool CheckCollisionBoxes(Vector3 minBBox1, Vector3 maxBBox1, Vector3 minBBox2, Vector3 maxBBox2) -{ - /* - // Get min and max vertex to construct bounds (AABB) - Vector3 minVertex = tempVertices[0]; - Vector3 maxVertex = tempVertices[0]; - - for (int i = 1; i < tempVertices.Count; i++) - { - minVertex = Vector3.Min(minVertex, tempVertices[i]); - maxVertex = Vector3.Max(maxVertex, tempVertices[i]); - } - - bounds = new BoundingBox(minVertex, maxVertex); - */ - return false; -} - -bool CheckCollisionBoxSphere(Vector3 minBBox, Vector3 maxBBox, Vector3 centerSphere, Vector3 radiusSphere) -{ - - return false; -} - -//BoundingBox GetCollisionArea(BoundingBox box1, BoundingBox box2)
\ No newline at end of file diff --git a/src/raylib.h b/src/raylib.h index 69966069..6800b260 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -1,6 +1,6 @@ /********************************************************************************************** * -* raylib 1.2 (www.raylib.com) +* raylib 1.3.0 (www.raylib.com) * * A simple and easy-to-use library to learn videogames programming * @@ -27,7 +27,6 @@ * * Some design decisions: * 32bit Colors - All defined color are always RGBA -* 32bit Textures - All loaded images are converted automatically to RGBA textures * SpriteFonts - All loaded sprite-font images are converted to RGBA and POT textures * One custom default font is loaded automatically when InitWindow() * If using OpenGL 3.3+ or ES2, one default shader is loaded automatically (internally defined) @@ -78,7 +77,7 @@ // Some basic Defines //---------------------------------------------------------------------------------- #ifndef PI -#define PI 3.14159265358979323846 + #define PI 3.14159265358979323846 #endif #define DEG2RAD (PI / 180.0f) @@ -90,8 +89,9 @@ #define FLAG_SHOW_MOUSE_CURSOR 4 #define FLAG_CENTERED_MODE 8 #define FLAG_MSAA_4X_HINT 16 +#define FLAG_VSYNC_HINT 32 -// Keyboard Function Keys +// Keyboard Function Keys #define KEY_SPACE 32 #define KEY_ESCAPE 256 #define KEY_ENTER 257 @@ -177,8 +177,10 @@ // Types and Structures Definition //---------------------------------------------------------------------------------- +#ifndef __cplusplus // Boolean type typedef enum { false, true } bool; +#endif // byte type typedef unsigned char byte; @@ -196,6 +198,14 @@ typedef struct Vector3 { float z; } Vector3; +// Matrix type (OpenGL style 4x4 - right handed, column major) +typedef struct Matrix { + float m0, m4, m8, m12; + float m1, m5, m9, m13; + float m2, m6, m10, m14; + float m3, m7, m11, m15; +} Matrix; + // Color type, RGBA (32bit) typedef struct Color { unsigned char r; @@ -215,17 +225,21 @@ typedef struct Rectangle { // Image type, bpp always RGBA (32bit) // NOTE: Data stored in CPU memory (RAM) typedef struct Image { - Color *pixels; - int width; - int height; + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (TextureFormat) } Image; // Texture2D type, bpp always RGBA (32bit) // NOTE: Data stored in GPU memory typedef struct Texture2D { - unsigned int id; // OpenGL id - int width; - int height; + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (TextureFormat) } Texture2D; // Character type (one font glyph) @@ -251,25 +265,60 @@ typedef struct Camera { Vector3 up; } Camera; +// Camera modes +typedef enum { CAMERA_CUSTOM = 0, CAMERA_FREE, CAMERA_ORBITAL, CAMERA_FIRST_PERSON, CAMERA_THIRD_PERSON } CameraMode; + // Vertex data definning a mesh +// NOTE: If using OpenGL 1.1, data loaded in CPU; if OpenGL 3.3+ data loaded in GPU (vaoId) typedef struct VertexData { int vertexCount; float *vertices; // 3 components per vertex float *texcoords; // 2 components per vertex float *normals; // 3 components per vertex unsigned char *colors; // 4 components per vertex + unsigned int vaoId; + unsigned int vboId[4]; } VertexData; +// Shader type +typedef struct Shader { + unsigned int id; // Shader program id + + unsigned int texDiffuseId; // Diffuse texture id + unsigned int texNormalId; // Normal texture id + unsigned int texSpecularId; // Specular texture id + + // Variable attributes + int vertexLoc; // Vertex attribute location point (vertex shader) + int texcoordLoc; // Texcoord attribute location point (vertex shader) + int normalLoc; // Normal attribute location point (vertex shader) + int colorLoc; // Color attibute location point (vertex shader) + + // Uniforms + int projectionLoc; // Projection matrix uniform location point (vertex shader) + int modelviewLoc; // ModeView matrix uniform location point (vertex shader) + + int tintColorLoc; // Color uniform location point (fragment shader) + + int mapDiffuseLoc; // Diffuse map texture uniform location point (fragment shader) + int mapNormalLoc; // Normal map texture uniform location point (fragment shader) + int mapSpecularLoc; // Specular map texture uniform location point (fragment shader) +} Shader; + // 3d Model type -// NOTE: If using OpenGL 1.1, loaded in CPU (mesh); if OpenGL 3.3+ loaded in GPU (vaoId) typedef struct Model { VertexData mesh; - unsigned int vaoId; - unsigned int vboId[4]; - unsigned int textureId; - //Matrix transform; + Matrix transform; + Texture2D texture; // Only for OpenGL 1.1, on newer versions this should be in the shader + Shader shader; } Model; +// Ray type (useful for raycast) +typedef struct Ray { + Vector3 position; + Vector3 direction; +} Ray; + // Sound source type typedef struct Sound { unsigned int source; @@ -285,6 +334,44 @@ typedef struct Wave { short channels; } Wave; +// Texture formats +// NOTE: Support depends on OpenGL version and platform +typedef enum { + UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + UNCOMPRESSED_GRAY_ALPHA, // 16 bpp (2 channels) + UNCOMPRESSED_R5G6B5, // 16 bpp + UNCOMPRESSED_R8G8B8, // 24 bpp + UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + UNCOMPRESSED_R8G8B8A8, // 32 bpp + COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + COMPRESSED_DXT3_RGBA, // 8 bpp + COMPRESSED_DXT5_RGBA, // 8 bpp + COMPRESSED_ETC1_RGB, // 4 bpp + COMPRESSED_ETC2_RGB, // 4 bpp + COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + COMPRESSED_PVRT_RGB, // 4 bpp + COMPRESSED_PVRT_RGBA, // 4 bpp + COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} TextureFormat; + +// Gestures type +typedef enum { + GESTURE_NONE = 0, + GESTURE_TAP, + GESTURE_DOUBLETAP, + GESTURE_HOLD, + GESTURE_DRAG, + GESTURE_SWIPE_RIGHT, + GESTURE_SWIPE_LEFT, + GESTURE_SWIPE_UP, + GESTURE_SWIPE_DOWN, + GESTURE_PINCH_IN, + GESTURE_PINCH_OUT +} Gestures; + #ifdef __cplusplus extern "C" { // Prevents name mangling of functions #endif @@ -312,7 +399,6 @@ void SetExitKey(int key); // Set a custom key #endif int GetScreenWidth(void); // Get current screen width int GetScreenHeight(void); // Get current screen height -int GetKeyPressed(void); // Get latest key pressed void ClearBackground(Color color); // Sets Background Color void BeginDrawing(void); // Setup drawing canvas to start drawing @@ -331,9 +417,35 @@ int GetHexValue(Color color); // Returns hexadecim int GetRandomValue(int min, int max); // Returns a random value between min and max (both included) Color Fade(Color color, float alpha); // Color fade-in or fade-out, alpha goes from 0.0f to 1.0f -void SetupFlags(char flags); // Enable some window configurations +void SetConfigFlags(char flags); // Enable some window configurations void ShowLogo(void); // Activates raylib logo at startup (can be done with flags) +void SetPostproShader(Shader shader); // Set fullscreen postproduction shader +void SetCustomShader(Shader shader); // Set custom shader to be used in batch draw +void SetDefaultShader(void); // Set default shader to be used in batch draw + +Ray GetMouseRay(Vector2 mousePosition, Camera camera); // Gives the rayTrace from mouse position + +// Camera modes setup and control functions (module: camera) +void SetCameraMode(int mode); // Select camera mode (multiple camera modes available) +Camera UpdateCamera(Vector3 *position); // Update camera with position + +void SetCameraControls(int front, int left, int back, int right, int up, int down); +void SetMouseSensitivity(float sensitivity); +void SetResetPosition(Vector3 resetPosition); +void SetResetControl(int resetKey); +void SetPawnControl(int pawnControlKey); +void SetFnControl(int fnControlKey); +void SetSmoothZoomControl(int smoothZoomControlKey); +void SetOrbitalTarget(Vector3 target); + +int GetShaderLocation(Shader shader, const char *uniformName); +void SetShaderValue(Shader shader, int uniformLoc, float *value, int size); + +void SetShaderMapDiffuse(Shader *shader, Texture2D texture); +void SetShaderMapNormal(Shader *shader, const char *uniformName, Texture2D texture); +void SetShaderMapSpecular(Shader *shader, const char *uniformName, Texture2D texture); + //------------------------------------------------------------------------------------ // Input Handling Functions (Module: core) //------------------------------------------------------------------------------------ @@ -342,6 +454,7 @@ bool IsKeyPressed(int key); // Detect if a key has b bool IsKeyDown(int key); // Detect if a key is being pressed bool IsKeyReleased(int key); // Detect if a key has been released once bool IsKeyUp(int key); // Detect if a key is NOT being pressed +int GetKeyPressed(void); // Get latest key pressed bool IsMouseButtonPressed(int button); // Detect if a mouse button has been pressed once bool IsMouseButtonDown(int button); // Detect if a mouse button is being pressed @@ -352,9 +465,13 @@ int GetMouseY(void); // Returns mouse positio Vector2 GetMousePosition(void); // Returns mouse position XY void SetMousePosition(Vector2 position); // Set mouse position XY int GetMouseWheelMove(void); // Returns mouse wheel movement Y + +void ShowCursor(void); // Shows cursor +void HideCursor(void); // Hides cursor +bool IsCursorHidden(void); // Returns true if cursor is not visible #endif -#if defined(PLATFORM_DESKTOP) +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) bool IsGamepadAvailable(int gamepad); // Detect if a gamepad is available Vector2 GetGamepadMovement(int gamepad); // Return axis movement vector for a gamepad bool IsGamepadButtonPressed(int gamepad, int button); // Detect if a gamepad button has been pressed once @@ -363,11 +480,20 @@ bool IsGamepadButtonReleased(int gamepad, int button); // Detect if a gamepad b bool IsGamepadButtonUp(int gamepad, int button); // Detect if a gamepad button is NOT being pressed #endif -#if defined(PLATFORM_ANDROID) -bool IsScreenTouched(void); // Detect screen touch event +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB) int GetTouchX(void); // Returns touch position X int GetTouchY(void); // Returns touch position Y Vector2 GetTouchPosition(void); // Returns touch position XY + +// Gestures System (module: gestures) +bool IsGestureDetected(void); +int GetGestureType(void); +float GetDragIntensity(void); +float GetDragAngle(void); +Vector2 GetDragVector(void); +int GetHoldDuration(void); // Hold time in frames +float GetPinchDelta(void); +float GetPinchAngle(void); #endif //------------------------------------------------------------------------------------ @@ -405,13 +531,15 @@ bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 //------------------------------------------------------------------------------------ Image LoadImage(const char *fileName); // Load an image into CPU memory (RAM) Image LoadImageFromRES(const char *rresName, int resId); // Load an image from rRES file (raylib Resource) +Image LoadImageFromData(Color *pixels, int width, int height, int format); // Load image from Color array data Texture2D LoadTexture(const char *fileName); // Load an image as texture into GPU memory +Texture2D LoadTextureEx(void *data, int width, int height, int textureFormat, int mipmapCount, bool genMipmaps); // Load a texture from raw data into GPU memory Texture2D LoadTextureFromRES(const char *rresName, int resId); // Load an image as texture from rRES file (raylib Resource) Texture2D LoadTextureFromImage(Image image, bool genMipmaps); // Load a texture from image data (and generate mipmaps) -Texture2D CreateTexture(Image image, bool genMipmaps); // [DEPRECATED] Same as LoadTextureFromImage() void UnloadImage(Image image); // Unload image from CPU memory (RAM) void UnloadTexture(Texture2D texture); // Unload texture from GPU memory void ConvertToPOT(Image *image, Color fillColor); // Convert image to POT (power-of-two) +Color *GetPixelData(Image image); // Get pixel data from image as a Color struct array void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 @@ -448,31 +576,40 @@ void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires -void DrawQuad(Vector3 vertices[4], Vector2 textcoords[4], Vector3 normals[4], Color colors[4]); // Draw a quad -void DrawPlane(Vector3 centerPos, Vector2 size, Vector3 rotation, Color color); // Draw a plane -void DrawPlaneEx(Vector3 centerPos, Vector2 size, Vector3 rotation, int slicesX, int slicesZ, Color color); // Draw a plane with divisions +void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ +void DrawQuad(Vector3 v1, Vector3 v2, Vector3 v3, Vector3 v4, Color color); // Draw a quad +void DrawRay(Ray ray, Color color); // Draw a ray line void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) void DrawGizmo(Vector3 position); // Draw simple gizmo -void DrawGizmoEx(Vector3 position, Vector3 rotation, float scale); // Draw gizmo with extended parameters //DrawTorus(), DrawTeapot() are useless... //------------------------------------------------------------------------------------ // Model 3d Loading and Drawing Functions (Module: models) //------------------------------------------------------------------------------------ Model LoadModel(const char *fileName); // Load a 3d model (.OBJ) +Model LoadModelEx(VertexData data); // Load a 3d model (from vertex data) //Model LoadModelFromRES(const char *rresName, int resId); // TODO: Load a 3d model from rRES file (raylib Resource) Model LoadHeightmap(Image heightmap, float maxHeight); // Load a heightmap image as a 3d model Model LoadCubicmap(Image cubicmap); // Load a map image as a 3d model (cubes based) void UnloadModel(Model model); // Unload 3d model from memory void SetModelTexture(Model *model, Texture2D texture); // Link a texture to a model +void SetModelShader(Model *model, Shader shader); // Link a shader to a model (not available on OpenGL 1.1) void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) -void DrawModelEx(Model model, Vector3 position, Vector3 rotation, Vector3 scale, Color tint); // Draw a model with extended parameters +void DrawModelEx(Model model, Vector3 position, float rotationAngle, Vector3 rotationAxis, Vector3 scale, Color tint); // Draw a model with extended parameters void DrawModelWires(Model model, Vector3 position, float scale, Color color); // Draw a model wires (with texture if set) void DrawBillboard(Camera camera, Texture2D texture, Vector3 center, float size, Color tint); // Draw a billboard texture void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle sourceRec, Vector3 center, float size, Color tint); // Draw a billboard texture defined by sourceRec +Shader LoadShader(char *vsFileName, char *fsFileName); // Load a custom shader (vertex shader + fragment shader) +void UnloadShader(Shader shader); // Unload a custom shader from memory + +bool CheckCollisionSpheres(Vector3 centerA, float radiusA, Vector3 centerB, float radiusB); +bool CheckCollisionBoxes(Vector3 minBBox1, Vector3 maxBBox1, Vector3 minBBox2, Vector3 maxBBox2); +bool CheckCollisionBoxSphere(Vector3 minBBox, Vector3 maxBBox, Vector3 centerSphere, float radiusSphere); +Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *playerPosition, float radius); // Return the normal vector of the impacted surface + //------------------------------------------------------------------------------------ // Audio Loading and Playing Functions (Module: audio) //------------------------------------------------------------------------------------ diff --git a/src/raymath.c b/src/raymath.c index ed45ee92..f5e30833 100644 --- a/src/raymath.c +++ b/src/raymath.c @@ -346,8 +346,6 @@ void MatrixInvert(Matrix *mat) temp.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; temp.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; - PrintMatrix(temp); - *mat = temp; } @@ -433,109 +431,16 @@ Matrix MatrixSubstract(Matrix left, Matrix right) } // Returns translation matrix -// TODO: Review this function Matrix MatrixTranslate(float x, float y, float z) { -/* - For OpenGL - 1, 0, 0, 0 - 0, 1, 0, 0 - 0, 0, 1, 0 - x, y, z, 1 - Is the correct Translation Matrix. Why? Opengl Uses column-major matrix ordering. - Which is the Transpose of the Matrix you initially presented, which is in row-major ordering. - Row major is used in most math text-books and also DirectX, so it is a common - point of confusion for those new to OpenGL. - - * matrix notation used in opengl documentation does not describe in-memory layout for OpenGL matrices - - Translation matrix should be laid out in memory like this: - { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, trabsX, transY, transZ, 1 } - - - 9.005 Are OpenGL matrices column-major or row-major? - - For programming purposes, OpenGL matrices are 16-value arrays with base vectors laid out - contiguously in memory. The translation components occupy the 13th, 14th, and 15th elements - of the 16-element matrix, where indices are numbered from 1 to 16 as described in section - 2.11.2 of the OpenGL 2.1 Specification. - - Column-major versus row-major is purely a notational convention. Note that post-multiplying - with column-major matrices produces the same result as pre-multiplying with row-major matrices. - The OpenGL Specification and the OpenGL Reference Manual both use column-major notation. - You can use any notation, as long as it's clearly stated. - - Sadly, the use of column-major format in the spec and blue book has resulted in endless confusion - in the OpenGL programming community. Column-major notation suggests that matrices - are not laid out in memory as a programmer would expect. -*/ - Matrix result = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, z, 1 }; return result; } -// Returns rotation matrix -// TODO: Review this function -Matrix MatrixRotate(float angleX, float angleY, float angleZ) -{ - Matrix result; - - Matrix rotX = MatrixRotateX(angleX); - Matrix rotY = MatrixRotateY(angleY); - Matrix rotZ = MatrixRotateZ(angleZ); - - result = MatrixMultiply(MatrixMultiply(rotX, rotY), rotZ); - - return result; -} - -/* -Matrix MatrixRotate(float angle, float x, float y, float z) -{ - Matrix result = MatrixIdentity(); - - float c = cosf(angle*DEG2RAD); // cosine - float s = sinf(angle*DEG2RAD); // sine - float c1 = 1.0f - c; // 1 - c - - float m0 = result.m0, m4 = result.m4, m8 = result.m8, m12 = result.m12, - m1 = result.m1, m5 = result.m5, m9 = result.m9, m13 = result.m13, - m2 = result.m2, m6 = result.m6, m10 = result.m10, m14 = result.m14; - - // build rotation matrix - float r0 = x * x * c1 + c; - float r1 = x * y * c1 + z * s; - float r2 = x * z * c1 - y * s; - float r4 = x * y * c1 - z * s; - float r5 = y * y * c1 + c; - float r6 = y * z * c1 + x * s; - float r8 = x * z * c1 + y * s; - float r9 = y * z * c1 - x * s; - float r10= z * z * c1 + c; - - // multiply rotation matrix - result.m0 = r0*m0 + r4*m1 + r8*m2; - result.m1 = r1*m0 + r5*m1 + r9*m2; - result.m2 = r2*m0 + r6*m1 + r10*m2; - result.m4 = r0*m4 + r4*m5 + r8*m6; - result.m5 = r1*m4 + r5*m5 + r9*m6; - result.m6 = r2*m4 + r6*m5 + r10*m6; - result.m8 = r0*m8 + r4*m9 + r8*m10; - result.m9 = r1*m8 + r5*m9 + r9*m10; - result.m10 = r2*m8 + r6*m9 + r10*m10; - result.m12 = r0*m12+ r4*m13 + r8*m14; - result.m13 = r1*m12+ r5*m13 + r9*m14; - result.m14 = r2*m12+ r6*m13 + r10*m14; - - return result; -} -*/ - // Create rotation matrix from axis and angle -// TODO: Test this function -// NOTE: NO prototype defined! -Matrix MatrixFromAxisAngle(Vector3 axis, float angle) +// NOTE: Angle should be provided in radians +Matrix MatrixRotate(float angle, Vector3 axis) { Matrix result; @@ -547,15 +452,15 @@ Matrix MatrixFromAxisAngle(Vector3 axis, float angle) if ((length != 1) && (length != 0)) { - length = 1 / length; + length = 1/length; x *= length; y *= length; z *= length; } - float s = sin(angle); - float c = cos(angle); - float t = 1-c; + float s = sinf(angle); + float c = cosf(angle); + float t = 1.0f - c; // Cache some matrix values (speed optimization) float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; @@ -586,69 +491,50 @@ Matrix MatrixFromAxisAngle(Vector3 axis, float angle) result.m15 = mat.m15; return result; -}; - -// Create rotation matrix from axis and angle (version 2) -// TODO: Test this function -// NOTE: NO prototype defined! -Matrix MatrixFromAxisAngle2(Vector3 axis, float angle) -{ - Matrix result; - - VectorNormalize(&axis); - float axisX = axis.x, axisY = axis.y, axisZ = axis.y; - - // Calculate angles - float cosres = (float)cos(angle); - float sinres = (float)sin(angle); - float t = 1.0f - cosres; - - // Do the conversion math once - float tXX = t * axisX * axisX; - float tXY = t * axisX * axisY; - float tXZ = t * axisX * axisZ; - float tYY = t * axisY * axisY; - float tYZ = t * axisY * axisZ; - float tZZ = t * axisZ * axisZ; - - float sinX = sinres * axisX; - float sinY = sinres * axisY; - float sinZ = sinres * axisZ; - - result.m0 = tXX + cosres; - result.m1 = tXY + sinZ; - result.m2 = tXZ - sinY; - result.m3 = 0; - result.m4 = tXY - sinZ; - result.m5 = tYY + cosres; - result.m6 = tYZ + sinX; - result.m7 = 0; - result.m8 = tXZ + sinY; - result.m9 = tYZ - sinX; - result.m10 = tZZ + cosres; - result.m11 = 0; - result.m12 = 0; - result.m13 = 0; - result.m14 = 0; - result.m15 = 1; - - return result; } -// Returns rotation matrix for a given quaternion -Matrix MatrixFromQuaternion(Quaternion q) +/* +// Another implementation for MatrixRotate... +Matrix MatrixRotate(float angle, float x, float y, float z) { Matrix result = MatrixIdentity(); - Vector3 axis; - float angle; + float c = cosf(angle); // cosine + float s = sinf(angle); // sine + float c1 = 1.0f - c; // 1 - c + + float m0 = result.m0, m4 = result.m4, m8 = result.m8, m12 = result.m12, + m1 = result.m1, m5 = result.m5, m9 = result.m9, m13 = result.m13, + m2 = result.m2, m6 = result.m6, m10 = result.m10, m14 = result.m14; - QuaternionToAxisAngle(q, &axis, &angle); + // build rotation matrix + float r0 = x * x * c1 + c; + float r1 = x * y * c1 + z * s; + float r2 = x * z * c1 - y * s; + float r4 = x * y * c1 - z * s; + float r5 = y * y * c1 + c; + float r6 = y * z * c1 + x * s; + float r8 = x * z * c1 + y * s; + float r9 = y * z * c1 - x * s; + float r10= z * z * c1 + c; - result = MatrixFromAxisAngle2(axis, angle); + // multiply rotation matrix + result.m0 = r0*m0 + r4*m1 + r8*m2; + result.m1 = r1*m0 + r5*m1 + r9*m2; + result.m2 = r2*m0 + r6*m1 + r10*m2; + result.m4 = r0*m4 + r4*m5 + r8*m6; + result.m5 = r1*m4 + r5*m5 + r9*m6; + result.m6 = r2*m4 + r6*m5 + r10*m6; + result.m8 = r0*m8 + r4*m9 + r8*m10; + result.m9 = r1*m8 + r5*m9 + r9*m10; + result.m10 = r2*m8 + r6*m9 + r10*m10; + result.m12 = r0*m12+ r4*m13 + r8*m14; + result.m13 = r1*m12+ r5*m13 + r9*m14; + result.m14 = r2*m12+ r6*m13 + r10*m14; return result; } +*/ // Returns x-rotation matrix (angle in radians) Matrix MatrixRotateX(float angle) @@ -671,8 +557,8 @@ Matrix MatrixRotateY(float angle) { Matrix result = MatrixIdentity(); - float cosres = (float)cos(angle); - float sinres = (float)sin(angle); + float cosres = cosf(angle); + float sinres = sinf(angle); result.m0 = cosres; result.m2 = sinres; @@ -706,22 +592,6 @@ Matrix MatrixScale(float x, float y, float z) return result; } -// Returns transformation matrix for a given translation, rotation and scale -// NOTE: Transformation order is rotation -> scale -> translation -// NOTE: Rotation angles should come in radians -Matrix MatrixTransform(Vector3 translation, Vector3 rotation, Vector3 scale) -{ - Matrix result = MatrixIdentity(); - - Matrix mRotation = MatrixRotate(rotation.x, rotation.y, rotation.z); - Matrix mScale = MatrixScale(scale.x, scale.y, scale.z); - Matrix mTranslate = MatrixTranslate(translation.x, translation.y, translation.z); - - result = MatrixMultiply(MatrixMultiply(mRotation, mScale), mTranslate); - - return result; -} - // Returns two matrix multiplication // NOTE: When multiplying matrices... the order matters! Matrix MatrixMultiply(Matrix left, Matrix right) @@ -876,7 +746,7 @@ void PrintMatrix(Matrix m) // Module Functions Definition - Quaternion math //---------------------------------------------------------------------------------- -// Calculates the length of a quaternion +// Computes the length of a quaternion float QuaternionLength(Quaternion quat) { return sqrt(quat.x*quat.x + quat.y*quat.y + quat.z*quat.z + quat.w*quat.w); @@ -922,13 +792,13 @@ Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; - if (abs(cosHalfTheta) >= 1.0f) result = q1; + if (fabs(cosHalfTheta) >= 1.0f) result = q1; else { float halfTheta = acos(cosHalfTheta); float sinHalfTheta = sqrt(1.0f - cosHalfTheta*cosHalfTheta); - if (abs(sinHalfTheta) < 0.001f) + if (fabs(sinHalfTheta) < 0.001f) { result.x = (q1.x*0.5f + q2.x*0.5f); result.y = (q1.y*0.5f + q2.y*0.5f); @@ -950,7 +820,7 @@ Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) return result; } -// Returns a quaternion from a given rotation matrix +// Returns a quaternion for a given rotation matrix Quaternion QuaternionFromMatrix(Matrix matrix) { Quaternion result; @@ -1006,29 +876,7 @@ Quaternion QuaternionFromMatrix(Matrix matrix) return result; } -// Returns rotation quaternion for an angle around an axis -// NOTE: angle must be provided in radians -Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) -{ - Quaternion result = { 0, 0, 0, 1 }; - - if (VectorLength(axis) != 0.0) - - angle *= 0.5; - - VectorNormalize(&axis); - - result.x = axis.x * (float)sin(angle); - result.y = axis.y * (float)sin(angle); - result.z = axis.z * (float)sin(angle); - result.w = (float)cos(angle); - - QuaternionNormalize(&result); - - return result; -} - -// Calculates the matrix from the given quaternion +// Returns a matrix for a given quaternion Matrix QuaternionToMatrix(Quaternion q) { Matrix result; @@ -1067,14 +915,36 @@ Matrix QuaternionToMatrix(Quaternion q) result.m13 = 0; result.m14 = 0; result.m15 = 1; + + return result; +} + +// Returns rotation quaternion for an angle and axis +// NOTE: angle must be provided in radians +Quaternion QuaternionFromAxisAngle(float angle, Vector3 axis) +{ + Quaternion result = { 0, 0, 0, 1 }; + + if (VectorLength(axis) != 0.0) + + angle *= 0.5; + + VectorNormalize(&axis); + + result.x = axis.x * (float)sin(angle); + result.y = axis.y * (float)sin(angle); + result.z = axis.z * (float)sin(angle); + result.w = (float)cos(angle); + + QuaternionNormalize(&result); return result; } -// Returns the axis and the angle for a given quaternion -void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) +// Returns the rotation angle and axis for a given quaternion +void QuaternionToAxisAngle(Quaternion q, float *outAngle, Vector3 *outAxis) { - if (abs(q.w) > 1.0f) QuaternionNormalize(&q); + if (fabs(q.w) > 1.0f) QuaternionNormalize(&q); Vector3 resAxis = { 0, 0, 0 }; float resAngle = 0; @@ -1097,4 +967,18 @@ void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) *outAxis = resAxis; *outAngle = resAngle; +} + +// Transform a quaternion given a transformation matrix +void QuaternionTransform(Quaternion *q, Matrix mat) +{ + float x = q->x; + float y = q->y; + float z = q->z; + float w = q->w; + + q->x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12*w; + q->y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13*w; + q->z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14*w; + q->w = mat.m3*x + mat.m7*y + mat.m11*z + mat.m15*w; }
\ No newline at end of file diff --git a/src/raymath.h b/src/raymath.h index c8c1a26c..d93c324c 100644 --- a/src/raymath.h +++ b/src/raymath.h @@ -53,15 +53,15 @@ float y; float z; } Vector3; -#endif -// Matrix type (OpenGL style 4x4 - right handed, column major) -typedef struct Matrix { - float m0, m4, m8, m12; - float m1, m5, m9, m13; - float m2, m6, m10, m14; - float m3, m7, m11, m15; -} Matrix; + // Matrix type (OpenGL style 4x4 - right handed, column major) + typedef struct Matrix { + float m0, m4, m8, m12; + float m1, m5, m9, m13; + float m2, m6, m10, m14; + float m3, m7, m11, m15; + } Matrix; +#endif // Quaternion type typedef struct Quaternion { @@ -91,7 +91,7 @@ void VectorNormalize(Vector3 *v); // Normalize provided ve float VectorDistance(Vector3 v1, Vector3 v2); // Calculate distance between two points Vector3 VectorLerp(Vector3 v1, Vector3 v2, float amount); // Calculate linear interpolation between two vectors Vector3 VectorReflect(Vector3 vector, Vector3 normal); // Calculate reflected vector to normal -void VectorTransform(Vector3 *v, Matrix mat); // Transforms a Vector3 with a given Matrix +void VectorTransform(Vector3 *v, Matrix mat); // Transforms a Vector3 by a given Matrix Vector3 VectorZero(void); // Return a Vector3 init to zero //------------------------------------------------------------------------------------ @@ -107,15 +107,11 @@ Matrix MatrixIdentity(void); // Returns identity matr Matrix MatrixAdd(Matrix left, Matrix right); // Add two matrices Matrix MatrixSubstract(Matrix left, Matrix right); // Substract two matrices (left - right) Matrix MatrixTranslate(float x, float y, float z); // Returns translation matrix -Matrix MatrixRotate(float axisX, float axisY, float axisZ); // Returns rotation matrix -Matrix MatrixFromAxisAngle(Vector3 axis, float angle); // Returns rotation matrix for an angle around an specified axis -Matrix MatrixFromAxisAngle2(Vector3 axis, float angle); // Returns rotation matrix for an angle around an specified axis (test another implemntation) -Matrix MatrixFromQuaternion(Quaternion q); // Returns rotation matrix for a given quaternion +Matrix MatrixRotate(float angle, Vector3 axis); // Returns rotation matrix for an angle around an specified axis (angle in radians) Matrix MatrixRotateX(float angle); // Returns x-rotation matrix (angle in radians) Matrix MatrixRotateY(float angle); // Returns y-rotation matrix (angle in radians) Matrix MatrixRotateZ(float angle); // Returns z-rotation matrix (angle in radians) Matrix MatrixScale(float x, float y, float z); // Returns scaling matrix -Matrix MatrixTransform(Vector3 translation, Vector3 rotation, Vector3 scale); // Returns transformation matrix for a given translation, rotation and scale Matrix MatrixMultiply(Matrix left, Matrix right); // Returns two matrix multiplication Matrix MatrixFrustum(double left, double right, double bottom, double top, double near, double far); // Returns perspective projection matrix Matrix MatrixPerspective(double fovy, double aspect, double near, double far); // Returns perspective projection matrix @@ -126,14 +122,15 @@ void PrintMatrix(Matrix m); // Print matrix utility //------------------------------------------------------------------------------------ // Functions Declaration to work with Quaternions //------------------------------------------------------------------------------------ -float QuaternionLength(Quaternion quat); // Calculates the length of a quaternion +float QuaternionLength(Quaternion quat); // Compute the length of a quaternion void QuaternionNormalize(Quaternion *q); // Normalize provided quaternion Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2); // Calculate two quaternion multiplication Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float slerp); // Calculates spherical linear interpolation between two quaternions -Quaternion QuaternionFromMatrix(Matrix matrix); // Returns a quaternion from a given rotation matrix -Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle); // Returns rotation quaternion for an angle around an axis -Matrix QuaternionToMatrix(Quaternion q); // Calculates the matrix from the given quaternion -void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle); // Returns the axis and the angle for a given quaternion +Quaternion QuaternionFromMatrix(Matrix matrix); // Returns a quaternion for a given rotation matrix +Matrix QuaternionToMatrix(Quaternion q); // Returns a matrix for a given quaternion +Quaternion QuaternionFromAxisAngle(float angle, Vector3 axis); // Returns rotation quaternion for an angle and axis +void QuaternionToAxisAngle(Quaternion q, float *outAngle, Vector3 *outAxis); // Returns the rotation angle and axis for a given quaternion +void QuaternionTransform(Quaternion *q, Matrix mat); // Transform a quaternion given a transformation matrix #ifdef __cplusplus } @@ -30,22 +30,23 @@ #include <stdio.h> // Standard input / output lib #include <stdlib.h> // Declares malloc() and free() for memory management, rand() +#include <string.h> // Declares strcmp(), strlen(), strtok() #if defined(GRAPHICS_API_OPENGL_11) - #ifdef __APPLE__ // OpenGL include for OSX - #include <OpenGL/gl.h> - #else - #include <GL/gl.h> // Basic OpenGL include - #endif + #ifdef __APPLE__ // OpenGL include for OSX + #include <OpenGL/gl.h> + #else + #include <GL/gl.h> // Basic OpenGL include + #endif #endif #if defined(GRAPHICS_API_OPENGL_33) #define GLEW_STATIC - #ifdef __APPLE__ // OpenGL include for OSX + #ifdef __APPLE__ // OpenGL include for OSX #include <OpenGL/gl3.h> - #else - #include <GL/glew.h> // Extensions loading lib - //#include "glad.h" // TODO: Other extensions loading lib? --> REVIEW + #else + #include <GL/glew.h> // Extensions loading lib + //#include "glad.h" // TODO: Other extensions loading lib? --> REVIEW #endif #endif @@ -63,6 +64,49 @@ #define TEMP_VERTEX_BUFFER_SIZE 4096 // Temporal Vertex Buffer (required for vertex-transformations) // NOTE: Every vertex are 3 floats (12 bytes) +#ifndef GL_SHADING_LANGUAGE_VERSION + #define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#endif + +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif +#ifndef GL_ETC1_RGB8_OES + #define GL_ETC1_RGB8_OES 0x8D64 +#endif +#ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 +#endif +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#endif +#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#endif +#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR + #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR + #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#endif //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- @@ -121,6 +165,7 @@ typedef struct { typedef struct { GLuint textureId; int vertexCount; + // TODO: DrawState state -> Blending mode, shader } DrawCall; // pixel type (same as Color type) @@ -151,18 +196,9 @@ static VertexPositionColorBuffer lines; // No texture support static VertexPositionColorBuffer triangles; // No texture support static VertexPositionColorTextureIndexBuffer quads; -// Vetex-Fragment Shader Program ID -static GLuint defaultShaderProgram, simpleShaderProgram; - -// Default Shader program attibutes binding locations -static GLuint defaultVertexLoc, defaultTexcoordLoc, defaultColorLoc; -static GLuint defaultProjectionMatrixLoc, defaultModelviewMatrixLoc; -static GLuint defaultTextureLoc; - -// Simple Shader program attibutes binding locations -static GLuint simpleVertexLoc, simpleTexcoordLoc, simpleNormalLoc, simpleColorLoc; -static GLuint simpleProjectionMatrixLoc, simpleModelviewMatrixLoc; -static GLuint simpleTextureLoc; +// Shader Programs +static Shader defaultShader, simpleShader; +static Shader currentShader; // By default, defaultShader // Vertex Array Objects (VAO) static GLuint vaoLines, vaoTriangles, vaoQuads; @@ -180,17 +216,28 @@ static Vector3 *tempBuffer; static int tempBufferCount = 0; static bool useTempBuffer = false; -// Support for VAOs (OpenGL ES2 could not support VAO extensions) -static bool vaoSupported = false; +// Flags for supported extensions +static bool vaoSupported = false; // VAO support (OpenGL ES2 could not support VAO extension) +static bool npotSupported = false; // NPOT textures full support + +// Compressed textures support flags +static bool texCompDXTSupported = false; // DDS texture compression support +static bool texCompETC1Supported = false; // ETC1 texture compression support +static bool texCompETC2Supported = false; // ETC2/EAC texture compression support +static bool texCompPVRTSupported = false; // PVR texture compression support +static bool texCompASTCSupported = false; // ASTC texture compression support + +// Framebuffer object and texture +static GLuint fbo, fboColorTexture, fboDepthTexture; +static Model postproQuad; #endif #if defined(GRAPHICS_API_OPENGL_ES2) // NOTE: VAO functionality is exposed through extensions (OES) -// emscripten does not support VAOs static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays; static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray; static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays; -static PFNGLISVERTEXARRAYOESPROC glIsVertexArray; +//static PFNGLISVERTEXARRAYOESPROC glIsVertexArray; // NOTE: Fails in WebGL, omitted #endif // White texture useful for plain color polys (required by shader) @@ -201,15 +248,14 @@ unsigned int whiteTexture; // Module specific Functions Declaration //---------------------------------------------------------------------------------- #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -static GLuint LoadDefaultShader(void); -static GLuint LoadSimpleShader(void); +static Shader LoadDefaultShader(void); +static Shader LoadSimpleShader(void); static void InitializeBuffers(void); static void InitializeBuffersGPU(void); static void UpdateBuffers(void); - -// Custom shader files loading (external) -static GLuint LoadCustomShader(char *vertexFileName, char *fragmentFileName); static char *TextFileRead(char *fn); + +static void LoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compressedFormat); #endif #if defined(GRAPHICS_API_OPENGL_11) @@ -217,6 +263,8 @@ static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight); static pixel *GenNextMipmap(pixel *srcData, int srcWidth, int srcHeight); #endif +static char** StringSplit(char *baseString, const char delimiter, int *numExt); + //---------------------------------------------------------------------------------- // Module Functions Definition - Matrix operations //---------------------------------------------------------------------------------- @@ -310,27 +358,15 @@ void rlTranslatef(float x, float y, float z) // Multiply the current matrix by a rotation matrix void rlRotatef(float angleDeg, float x, float y, float z) { - // TODO: Support rotation in multiple axes - Matrix rot = MatrixIdentity(); - - // OPTION 1: It works... - if (x == 1) rot = MatrixRotateX(angleDeg*DEG2RAD); - else if (y == 1) rot = MatrixRotateY(angleDeg*DEG2RAD); - else if (z == 1) rot = MatrixRotateZ(angleDeg*DEG2RAD); - - // OPTION 2: Requires review... - //Vector3 vec = (Vector3){ 0, 1, 0 }; - //VectorNormalize(&vec); - //rot = MatrixFromAxisAngle(vec, angleDeg*DEG2RAD); // Working? - - // OPTION 3: TODO: Review, it doesn't work! - //Vector3 vec = (Vector3){ x, y, z }; - //VectorNormalize(&vec); - //rot = MatrixRotate(angleDeg*vec.x, angleDeg*vec.x, angleDeg*vec.x); - - MatrixTranspose(&rot); + Matrix rotation = MatrixIdentity(); + + Vector3 axis = (Vector3){ x, y, z }; + VectorNormalize(&axis); + rotation = MatrixRotate(angleDeg*DEG2RAD, axis); + + MatrixTranspose(&rotation); - *currentMatrix = MatrixMultiply(*currentMatrix, rot); + *currentMatrix = MatrixMultiply(*currentMatrix, rotation); } // Multiply the current matrix by a scaling matrix @@ -345,7 +381,7 @@ void rlScalef(float x, float y, float z) // Multiply the current matrix by another matrix void rlMultMatrixf(float *m) { - // TODO: review Matrix creation from array + // Matrix creation from array Matrix mat = { m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], @@ -503,7 +539,7 @@ void rlEnd(void) } } - // TODO: Make sure normals count match vertex count + // TODO: Make sure normals count match vertex count... if normals support is added in a future... :P } break; default: break; @@ -696,6 +732,22 @@ void rlDeleteTextures(unsigned int id) glDeleteTextures(1, &id); } +// Enable rendering to postprocessing FBO +void rlEnableFBO(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindFramebuffer(GL_FRAMEBUFFER, fbo); +#endif +} + +// Unload shader from GPU memory +void rlDeleteShader(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteProgram(id); +#endif +} + // Unload vertex data (VAO) from GPU memory void rlDeleteVertexArrays(unsigned int id) { @@ -750,93 +802,142 @@ int rlGetVersion(void) // Init OpenGL 3.3+ required data void rlglInit(void) { -#if defined(GRAPHICS_API_OPENGL_33) - // Loading extensions the hard way (Example) -/* - GLint numExt; - glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); - - for (int i = 0; i < numExt; i++) - { - const GLubyte *extensionName = glGetStringi(GL_EXTENSIONS, i); - if (strcmp(extensionName, (const GLubyte *)"GL_ARB_vertex_array_object") == 0) - { - // The extension is supported by our hardware and driver, try to get related functions popinters - glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)wglGetProcAddress("glGenVertexArrays"); - glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)wglGetProcAddress("glBindVertexArray"); - glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)wglGetProcAddress("glDeleteVertexArrays"); - glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)wglGetProcAddress("glIsVertexArray"); - } - } -*/ - - // Initialize extensions using GLEW - glewExperimental = 1; // Needed for core profile - - GLenum error = glewInit(); - - if (error != GLEW_OK) TraceLog(ERROR, "Failed to initialize GLEW - Error Code: %s\n", glewGetErrorString(error)); - - if (glewIsSupported("GL_VERSION_3_3")) TraceLog(INFO, "OpenGL 3.3 extensions supported"); - - // NOTE: GLEW is a big library that loads ALL extensions, using glad we can only load required ones... - //if (!gladLoadGL()) TraceLog("ERROR: Failed to initialize glad\n"); - - vaoSupported = true; -#endif - -#if defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... -#if !defined(PLATFORM_WEB) - glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)eglGetProcAddress("glGenVertexArraysOES"); - glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)eglGetProcAddress("glBindVertexArrayOES"); - glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)eglGetProcAddress("glDeleteVertexArraysOES"); - glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)eglGetProcAddress("glIsVertexArrayOES"); -#endif - - if (glGenVertexArrays == NULL) TraceLog(WARNING, "Could not initialize VAO extensions, VAOs not supported"); - else - { - vaoSupported = true; - TraceLog(INFO, "VAO extensions initialized successfully"); - } -#endif - + // Check OpenGL information and capabilities + //------------------------------------------------------------------------------ + // Print current OpenGL and GLSL version TraceLog(INFO, "GPU: Vendor: %s", glGetString(GL_VENDOR)); TraceLog(INFO, "GPU: Renderer: %s", glGetString(GL_RENDERER)); TraceLog(INFO, "GPU: Version: %s", glGetString(GL_VERSION)); - TraceLog(INFO, "GPU: GLSL: %s", glGetString(0x8B8C)); //GL_SHADING_LANGUAGE_VERSION + TraceLog(INFO, "GPU: GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); // NOTE: We can get a bunch of extra information about GPU capabilities (glGet*) //int maxTexSize; //glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize); //TraceLog(INFO, "GL_MAX_TEXTURE_SIZE: %i", maxTexSize); + + //GL_MAX_TEXTURE_IMAGE_UNITS + //GL_MAX_VIEWPORT_DIMS //int numAuxBuffers; //glGetIntegerv(GL_AUX_BUFFERS, &numAuxBuffers); //TraceLog(INFO, "GL_AUX_BUFFERS: %i", numAuxBuffers); + + //GLint numComp = 0; + //GLint format[32] = { 0 }; + //glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &numComp); + //glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, format); + //for (int i = 0; i < numComp; i++) TraceLog(INFO, "Supported compressed format: 0x%x", format[i]); - // Show supported extensions // NOTE: We don't need that much data on screen... right now... -/* -#if defined(GRAPHICS_API_OPENGL_33) + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Get supported extensions list GLint numExt; - glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + +#if defined(GRAPHICS_API_OPENGL_33) + // Initialize extensions using GLEW + glewExperimental = 1; // Needed for core profile + + GLenum error = glewInit(); + + if (error != GLEW_OK) TraceLog(ERROR, "Failed to initialize GLEW - Error Code: %s\n", glewGetErrorString(error)); - for (int i = 0; i < numExt; i++) + if (glewIsSupported("GL_VERSION_3_3")) { - TraceLog(INFO, "Supported extension: %s", glGetStringi(GL_EXTENSIONS, i)); + TraceLog(INFO, "OpenGL 3.3 Core profile"); + + vaoSupported = true; + npotSupported = true; } + + // NOTE: GLEW is a big library that loads ALL extensions, we can use some alternative to load only required ones + // Alternatives: glLoadGen, glad, libepoxy + //if (!gladLoadGL()) TraceLog("ERROR: Failed to initialize glad\n"); + + // With GLEW we can check if an extension has been loaded in two ways: + //if (GLEW_ARB_vertex_array_object) { } + //if (glewIsSupported("GL_ARB_vertex_array_object")) { } + + + // NOTE: We don't need to check again supported extensions but we do (in case GLEW is replaced sometime) + // We get a list of available extensions and we check for some of them (compressed textures) + glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + const char *ext[numExt]; + + for (int i = 0; i < numExt; i++) ext[i] = (char *)glGetStringi(GL_EXTENSIONS, i); + #elif defined(GRAPHICS_API_OPENGL_ES2) char *extensions = (char *)glGetString(GL_EXTENSIONS); // One big string - + // NOTE: String could be splitted using strtok() function (string.h) - TraceLog(INFO, "Supported extension: %s", extensions); + char **ext = StringSplit(extensions, ' ', &numExt); #endif -*/ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + TraceLog(INFO, "Number of supported extensions: %i", numExt); + + // Show supported extensions + //for (int i = 0; i < numExt; i++) TraceLog(INFO, "Supported extension: %s", ext[i]); + + // Check required extensions + for (int i = 0; i < numExt; i++) + { +#if defined(GRAPHICS_API_OPENGL_ES2) + // Check VAO support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature + if (strcmp(ext[i], (const char *)"GL_OES_vertex_array_object") == 0) + { + vaoSupported = true; + + // The extension is supported by our hardware and driver, try to get related functions pointers + // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... + glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)eglGetProcAddress("glGenVertexArraysOES"); + glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)eglGetProcAddress("glBindVertexArrayOES"); + glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)eglGetProcAddress("glDeleteVertexArraysOES"); + //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)eglGetProcAddress("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted + } + + // Check NPOT textures support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature + if (strcmp(ext[i], (const char *)"GL_OES_texture_npot") == 0) npotSupported = true; +#endif + + // DDS texture compression support + if (strcmp(ext[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) texCompDXTSupported = true; + + // ETC1 texture compression support + if (strcmp(ext[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) texCompETC1Supported = true; + + // ETC2/EAC texture compression support + if (strcmp(ext[i], (const char *)"GL_ARB_ES3_compatibility") == 0) texCompETC2Supported = true; + + // PVR texture compression support + if (strcmp(ext[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) texCompPVRTSupported = true; + + // ASTC texture compression support + if (strcmp(ext[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) texCompASTCSupported = true; + } + +#if defined(GRAPHICS_API_OPENGL_ES2) + if (vaoSupported) TraceLog(INFO, "[EXTENSION] VAO extension detected, VAO functions initialized successfully"); + else TraceLog(WARNING, "[EXTENSION] VAO extension not found, VAO usage not supported"); + + if (npotSupported) TraceLog(INFO, "[EXTENSION] NPOT textures extension detected, full NPOT textures supported"); + else TraceLog(WARNING, "[EXTENSION] NPOT textures extension not found, NPOT textures not supported"); + + // Once supported extensions have been checked, we should free strings memory + free(ext); +#endif + + if (texCompDXTSupported) TraceLog(INFO, "[EXTENSION] DXT compressed textures supported"); + if (texCompETC1Supported) TraceLog(INFO, "[EXTENSION] ETC1 compressed textures supported"); + if (texCompETC2Supported) TraceLog(INFO, "[EXTENSION] ETC2/EAC compressed textures supported"); + if (texCompPVRTSupported) TraceLog(INFO, "[EXTENSION] PVRT compressed textures supported"); + if (texCompASTCSupported) TraceLog(INFO, "[EXTENSION] ASTC compressed textures supported"); + + // Initialize buffers, default shaders and default textures + //---------------------------------------------------------- + // Set default draw mode currentDrawMode = RL_TRIANGLES; @@ -849,38 +950,11 @@ void rlglInit(void) for (int i = 0; i < MATRIX_STACK_SIZE; i++) stack[i] = MatrixIdentity(); // Init default Shader (GLSL 110) -> Common for GL 3.3+ and ES2 - defaultShaderProgram = LoadDefaultShader(); - simpleShaderProgram = LoadSimpleShader(); - //customShaderProgram = LoadShaders("simple150.vert", "simple150.frag"); - - // Get handles to GLSL input vars locations for defaultShaderProgram - //------------------------------------------------------------------- - defaultVertexLoc = glGetAttribLocation(defaultShaderProgram, "vertexPosition"); - defaultTexcoordLoc = glGetAttribLocation(defaultShaderProgram, "vertexTexCoord"); - defaultColorLoc = glGetAttribLocation(defaultShaderProgram, "vertexColor"); - - // Get handles to GLSL uniform vars locations (vertex-shader) - defaultModelviewMatrixLoc = glGetUniformLocation(defaultShaderProgram, "modelviewMatrix"); - defaultProjectionMatrixLoc = glGetUniformLocation(defaultShaderProgram, "projectionMatrix"); - - // Get handles to GLSL uniform vars locations (fragment-shader) - defaultTextureLoc = glGetUniformLocation(defaultShaderProgram, "texture0"); - //-------------------------------------------------------------------- + defaultShader = LoadDefaultShader(); + simpleShader = LoadSimpleShader(); + //customShader = rlglLoadShader("custom.vs", "custom.fs"); // Works ok - // Get handles to GLSL input vars locations for simpleShaderProgram - //------------------------------------------------------------------- - simpleVertexLoc = glGetAttribLocation(simpleShaderProgram, "vertexPosition"); - simpleTexcoordLoc = glGetAttribLocation(simpleShaderProgram, "vertexTexCoord"); - simpleNormalLoc = glGetAttribLocation(defaultShaderProgram, "vertexNormal"); - - // Get handles to GLSL uniform vars locations (vertex-shader) - simpleModelviewMatrixLoc = glGetUniformLocation(simpleShaderProgram, "modelviewMatrix"); - simpleProjectionMatrixLoc = glGetUniformLocation(simpleShaderProgram, "projectionMatrix"); - - // Get handles to GLSL uniform vars locations (fragment-shader) - simpleTextureLoc = glGetUniformLocation(simpleShaderProgram, "texture0"); - simpleColorLoc = glGetUniformLocation(simpleShaderProgram, "fragColor"); - //-------------------------------------------------------------------- + currentShader = defaultShader; InitializeBuffers(); // Init vertex arrays InitializeBuffersGPU(); // Init VBO and VAO @@ -893,7 +967,7 @@ void rlglInit(void) // Create default white texture for plain colors (required by shader) unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) - whiteTexture = rlglLoadTexture(pixels, 1, 1, false); + whiteTexture = rlglLoadTexture(pixels, 1, 1, UNCOMPRESSED_R8G8B8A8, 1, false); if (whiteTexture != 0) TraceLog(INFO, "[TEX ID %i] Base white texture loaded successfully", whiteTexture); else TraceLog(WARNING, "Base white texture could not be loaded"); @@ -912,6 +986,88 @@ void rlglInit(void) #endif } +// Init postpro system +void rlglInitPostpro(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Create the texture that will serve as the color attachment for the framebuffer + glGenTextures(1, &fboColorTexture); + glBindTexture(GL_TEXTURE_2D, fboColorTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, GetScreenWidth(), GetScreenHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + // Create the texture that will serve as the depth attachment for the framebuffer. + glGenTextures(1, &fboDepthTexture); + glBindTexture(GL_TEXTURE_2D, fboDepthTexture); + 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); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GetScreenWidth(), GetScreenHeight(), 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); + glBindTexture(GL_TEXTURE_2D, 0); + + // Create the framebuffer object + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + // Attach color texture and depth texture to FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fboColorTexture, 0); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, fboDepthTexture, 0); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) TraceLog(WARNING, "Framebuffer object could not be created..."); + else TraceLog(INFO, "[FBO ID %i] Framebuffer object created successfully", fbo); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Create a simple quad model to render fbo texture + VertexData quadData; + + quadData.vertexCount = 6; + + float w = GetScreenWidth(); + float h = GetScreenHeight(); + + float quadPositions[6*3] = { w, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, h, 0.0, 0, h, 0.0, w, h, 0.0, w, 0.0, 0.0 }; + float quadTexcoords[6*2] = { 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0 }; + float quadNormals[6*3] = { 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0 }; + unsigned char quadColors[6*4] = { 255 }; + + quadData.vertices = quadPositions; + quadData.texcoords = quadTexcoords; + quadData.normals = quadNormals; + quadData.colors = quadColors; + + postproQuad = rlglLoadModel(quadData); + + // NOTE: fboColorTexture id must be assigned to postproQuad model shader +#endif +} + +// Set postprocessing shader +void rlglSetPostproShader(Shader shader) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlglSetModelShader(&postproQuad, shader); + + Texture2D texture; + texture.id = fboColorTexture; + texture.width = GetScreenWidth(); + texture.height = GetScreenHeight(); + + SetShaderMapDiffuse(&postproQuad.shader, texture); + + //TraceLog(INFO, "Postproquad texture id: %i", postproQuad.texture.id); + //TraceLog(INFO, "Postproquad shader diffuse map id: %i", postproQuad.shader.texDiffuseId); + //TraceLog(INFO, "Shader diffuse map id: %i", shader.texDiffuseId); +#endif +} + // Vertex Buffer Object deinitialization (memory free) void rlglClose(void) { @@ -949,7 +1105,8 @@ void rlglClose(void) //glDetachShader(defaultShaderProgram, f); //glDeleteShader(v); //glDeleteShader(f); - glDeleteProgram(defaultShaderProgram); + glDeleteProgram(defaultShader.id); + glDeleteProgram(simpleShader.id); // Free vertex arrays memory free(lines.vertices); @@ -966,10 +1123,18 @@ void rlglClose(void) // Free GPU texture glDeleteTextures(1, &whiteTexture); + if (fbo != 0) + { + glDeleteFramebuffers(1, &fbo); + + UnloadModel(postproQuad); + } + free(draws); #endif } +// Drawing batches: triangles, quads, lines void rlglDraw(void) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) @@ -977,13 +1142,13 @@ void rlglDraw(void) if ((lines.vCounter > 0) || (triangles.vCounter > 0) || (quads.vCounter > 0)) { - glUseProgram(defaultShaderProgram); // Use our shader + glUseProgram(currentShader.id); - glUniformMatrix4fv(defaultProjectionMatrixLoc, 1, false, GetMatrixVector(projection)); - glUniformMatrix4fv(defaultModelviewMatrixLoc, 1, false, GetMatrixVector(modelview)); - glUniform1i(defaultTextureLoc, 0); + glUniformMatrix4fv(currentShader.projectionLoc, 1, false, GetMatrixVector(projection)); + glUniformMatrix4fv(currentShader.modelviewLoc, 1, false, GetMatrixVector(modelview)); + glUniform1i(currentShader.mapDiffuseLoc, 0); } - + // NOTE: We draw in this order: triangle shapes, textured quads and lines if (triangles.vCounter > 0) @@ -997,12 +1162,15 @@ void rlglDraw(void) else { glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultVertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); - glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(defaultColorLoc); + if (currentShader.colorLoc != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + } } glDrawArrays(GL_TRIANGLES, 0, triangles.vCounter); @@ -1025,17 +1193,20 @@ void rlglDraw(void) { // Enable vertex attributes glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultVertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]); - glVertexAttribPointer(defaultTexcoordLoc, 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultTexcoordLoc); - - glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(defaultColorLoc); + glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.texcoordLoc); + if (currentShader.colorLoc != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + } + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadsBuffer[3]); } @@ -1082,12 +1253,15 @@ void rlglDraw(void) else { glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultVertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); - glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(defaultColorLoc); + if (currentShader.colorLoc != -1) + { + glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + } } glDrawArrays(GL_LINES, 0, lines.vCounter); @@ -1098,6 +1272,8 @@ void rlglDraw(void) if (vaoSupported) glBindVertexArray(0); // Unbind VAO + glUseProgram(0); // Unbind shader program + // Reset draws counter drawsCounter = 1; draws[0].textureId = whiteTexture; @@ -1116,8 +1292,19 @@ void rlglDraw(void) #endif } +// Draw with postprocessing shader +void rlglDrawPostpro(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + rlglDrawModel(postproQuad, (Vector3){0,0,0}, 0.0f, (Vector3){0,0,0}, (Vector3){1.0f, 1.0f, 1.0f}, WHITE, false); +#endif +} + // Draw a 3d model -void rlglDrawModel(Model model, Vector3 position, Vector3 rotation, Vector3 scale, Color color, bool wires) +// NOTE: Model transform can come within model struct +void rlglDrawModel(Model model, Vector3 position, float rotationAngle, Vector3 rotationAxis, Vector3 scale, Color color, bool wires) { #if defined (GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) // NOTE: glPolygonMode() not available on OpenGL ES @@ -1126,7 +1313,7 @@ void rlglDrawModel(Model model, Vector3 position, Vector3 rotation, Vector3 scal #if defined(GRAPHICS_API_OPENGL_11) glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, model.textureId); + glBindTexture(GL_TEXTURE_2D, model.texture.id); // NOTE: On OpenGL 1.1 we use Vertex Arrays to draw model glEnableClientState(GL_VERTEX_ARRAY); // Enable vertex array @@ -1141,9 +1328,7 @@ void rlglDrawModel(Model model, Vector3 position, Vector3 rotation, Vector3 scal rlPushMatrix(); rlTranslatef(position.x, position.y, position.z); rlScalef(scale.x, scale.y, scale.z); - rlRotatef(rotation.y, 0, 1, 0); - - // TODO: If rotate in multiple axis, get rotation matrix and use rlMultMatrix() + rlRotatef(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z); rlColor4ub(color.r, color.g, color.b, color.a); @@ -1159,55 +1344,67 @@ void rlglDrawModel(Model model, Vector3 position, Vector3 rotation, Vector3 scal #endif #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glUseProgram(simpleShaderProgram); // Use our simple shader + glUseProgram(model.shader.id); - VectorScale(&rotation, DEG2RAD); - + // Apply transformation provided in model.transform matrix + Matrix modelviewworld = MatrixMultiply(model.transform, modelview); // World-space transformation + + // Apply transformations provided in function // Get transform matrix (rotation -> scale -> translation) - Matrix transform = MatrixTransform(position, rotation, scale); - Matrix modelviewworld = MatrixMultiply(transform, modelview); + Matrix rotation = MatrixRotate(rotationAngle*DEG2RAD, rotationAxis); + Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); + Matrix translation = MatrixTranslate(position.x, position.y, position.z); - // NOTE: Drawing in OpenGL 3.3+, transform is passed to shader - glUniformMatrix4fv(simpleProjectionMatrixLoc, 1, false, GetMatrixVector(projection)); - glUniformMatrix4fv(simpleModelviewMatrixLoc, 1, false, GetMatrixVector(modelviewworld)); - glUniform1i(simpleTextureLoc, 0); + Matrix transform = MatrixMultiply(MatrixMultiply(rotation, matScale), translation); // Object-space transformation matrix + modelviewworld = MatrixMultiply(transform, modelview); // World-space transformation + // Projection: Screen-space transformation + + // NOTE: Drawing in OpenGL 3.3+, transform is passed to shader + glUniformMatrix4fv(model.shader.projectionLoc, 1, false, GetMatrixVector(projection)); + glUniformMatrix4fv(model.shader.modelviewLoc, 1, false, GetMatrixVector(modelviewworld)); + // Apply color tinting to model // NOTE: Just update one uniform on fragment shader float vColor[4] = { (float)color.r/255, (float)color.g/255, (float)color.b/255, (float)color.a/255 }; - glUniform4fv(simpleColorLoc, 1, vColor); + glUniform4fv(model.shader.tintColorLoc, 1, vColor); + + // Set shader textures (diffuse, normal, specular) + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, model.shader.texDiffuseId); + //glUniform1i(model.shader.mapDiffuseLoc, 0); // Diffuse texture fits in texture unit 0 if (vaoSupported) { - glBindVertexArray(model.vaoId); + glBindVertexArray(model.mesh.vaoId); } else { // Bind model VBOs data - glBindBuffer(GL_ARRAY_BUFFER, model.vboId[0]); - glVertexAttribPointer(simpleVertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(simpleVertexLoc); + glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[0]); + glVertexAttribPointer(model.shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(model.shader.vertexLoc); - glBindBuffer(GL_ARRAY_BUFFER, model.vboId[1]); - glVertexAttribPointer(simpleTexcoordLoc, 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(simpleTexcoordLoc); + glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[1]); + glVertexAttribPointer(model.shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(model.shader.texcoordLoc); // Add normals support - glBindBuffer(GL_ARRAY_BUFFER, model.vboId[2]); - glVertexAttribPointer(simpleNormalLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(simpleNormalLoc); + glBindBuffer(GL_ARRAY_BUFFER, model.mesh.vboId[2]); + glVertexAttribPointer(model.shader.normalLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(model.shader.normalLoc); } - glBindTexture(GL_TEXTURE_2D, model.textureId); - + // Draw call! glDrawArrays(GL_TRIANGLES, 0, model.mesh.vertexCount); glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures + glActiveTexture(GL_TEXTURE0); // Set shader active texture to default 0 if (vaoSupported) glBindVertexArray(0); // Unbind VAO else glBindBuffer(GL_ARRAY_BUFFER, 0); // Unbind VBOs - - glUseProgram(0); + + glUseProgram(0); // Unbind shader program #endif #if defined (GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) @@ -1243,7 +1440,7 @@ void rlglInitGraphics(int offsetX, int offsetY, int width, int height) rlMatrixMode(RL_PROJECTION); // Switch to PROJECTION matrix rlLoadIdentity(); // Reset current matrix (PROJECTION) - rlOrtho(0, width - offsetX, height - offsetY, 0, 0, 1); // Config orthographic mode: top-left corner --> (0,0) + rlOrtho(0, width - offsetX, height - offsetY, 0, 0, 1); // Config orthographic mode: top-left corner --> (0,0) rlMatrixMode(RL_MODELVIEW); // Switch back to MODELVIEW matrix rlLoadIdentity(); // Reset current matrix (MODELVIEW) @@ -1262,64 +1459,331 @@ void rlglInitGraphics(int offsetX, int offsetY, int width, int height) TraceLog(INFO, "OpenGL Graphics initialized successfully"); } +// Get world coordinates from screen coordinates +// TODO: It doesn't work! It drives me crazy! +Vector3 rlglUnproject(Vector3 source, Matrix proj, Matrix view) +{ + //GLint viewport[4]; + //glGetIntegerv(GL_VIEWPORT, viewport); + + // Viewport data +/* + int x = 0; + int y = 0; + int width = GetScreenWidth(); + int height = GetScreenHeight(); + float minDepth = 0.0f; + float maxDepth = 1.0f; +*/ +/* + Matrix modelviewprojection = MatrixMultiply(modelview, projection); + MatrixInvert(&modelviewprojection); + + Vector3 vector; + + vector.x = (((source.x - x) / ((float)width)) * 2.0f) - 1.0f; + vector.y = -((((source.y - y) / ((float)height)) * 2.0f) - 1.0f); + vector.z = (source.z - minDepth) / (maxDepth - minDepth); + + //float a = (((vector.x * matrix.M14) + (vector.y * matrix.M24)) + (vector.z * matrix.M34)) + matrix.M44; + //float a = (((vector.x * modelviewprojection.m3) + (vector.y * modelviewprojection.m7)) + (vector.z * modelviewprojection.m11)) + modelviewprojection.m15; + VectorTransform(&vector, modelviewprojection); + + //if (!MathUtil.IsOne(a)) vector = (vector / a); + //VectorScale(&vector, 1/a); + + return vector; +*/ +/* + Vector3 worldPoint; + + // Transformation matrices + Matrix modelviewprojection = MatrixIdentity(); + Quaternion quat; + + // Calculation for inverting a matrix, compute projection x modelview + modelviewprojection = MatrixMultiply(proj, view); + MatrixInvert(&modelviewprojection); + + // Transformation of normalized coordinates between -1 and 1 + quat.x = ((source.x - (float)x)/(float)width*2.0) - 1.0f; + quat.y = ((source.y - (float)y)/(float)height*2.0) - 1.0f; + quat.z = 2.0*source.z - 1.0; + quat.w = 1.0; + + // Objects coordinates + QuaternionTransform(&quat, modelviewprojection); + + //if (quat.w == 0.0) return 0; + + worldPoint.x = quat.x/quat.w; + worldPoint.y = quat.y/quat.w; + worldPoint.z = quat.z/quat.w; + + return worldPoint; + */ +/* + Quaternion quat; + Vector3 vec; + + quat.x = 2.0f * GetMousePosition().x / (float)width - 1; + quat.y = -(2.0f * GetMousePosition().y / (float)height - 1); + quat.z = 0; + quat.w = 1; + + Matrix invView; + MatrixInvert(&view); + Matrix invProj; + MatrixInvert(&proj); + + quat.x = invProj.m0 * quat.x + invProj.m4 * quat.y + invProj.m8 * quat.z + invProj.m12 * quat.w; + quat.y = invProj.m1 * quat.x + invProj.m5 * quat.y + invProj.m9 * quat.z + invProj.m13 * quat.w; + quat.z = invProj.m2 * quat.x + invProj.m6 * quat.y + invProj.m10 * quat.z + invProj.m14 * quat.w; + quat.w = invProj.m3 * quat.x + invProj.m7 * quat.y + invProj.m11 * quat.z + invProj.m15 * quat.w; + + quat.x = invView.m0 * quat.x + invView.m4 * quat.y + invView.m8 * quat.z + invView.m12 * quat.w; + quat.y = invView.m1 * quat.x + invView.m5 * quat.y + invView.m9 * quat.z + invView.m13 * quat.w; + quat.z = invView.m2 * quat.x + invView.m6 * quat.y + invView.m10 * quat.z + invView.m14 * quat.w; + quat.w = invView.m3 * quat.x + invView.m7 * quat.y + invView.m11 * quat.z + invView.m15 * quat.w; + + vec.x /= quat.w; + vec.y /= quat.w; + vec.z /= quat.w; + + return vec; + */ +/* + Vector3 worldPoint; + + // Transformation matrices + Matrix modelviewprojection; + Quaternion quat; + + // Calculation for inverting a matrix, compute projection x modelview + modelviewprojection = MatrixMultiply(view, proj); + + // Now compute the inverse of matrix A + MatrixInvert(&modelviewprojection); + + // Transformation of normalized coordinates between -1 and 1 + quat.x = ((source.x - (float)x)/(float)width*2.0) - 1.0f; + quat.y = ((source.y - (float)y)/(float)height*2.0) - 1.0f; + quat.z = 2.0*source.z - 1.0; + quat.w = 1.0; + + // Traspose quaternion and multiply + Quaternion result; + result.x = modelviewprojection.m0 * quad.x + modelviewprojection.m4 * quad.y + modelviewprojection.m8 * quad.z + modelviewprojection.m12 * quad.w; + result.y = modelviewprojection.m1 * quad.x + modelviewprojection.m5 * quad.y + modelviewprojection.m9 * quad.z + modelviewprojection.m13 * quad.w; + result.z = modelviewprojection.m2 * quad.x + modelviewprojection.m6 * quad.y + modelviewprojection.m10 * quad.z + modelviewprojection.m14 * quad.w; + result.w = modelviewprojection.m3 * quad.x + modelviewprojection.m7 * quad.y + modelviewprojection.m11 * quad.z + modelviewprojection.m15 * quad.w; + + // Invert + result.w = 1.0f / result.w; + + //if (quat.w == 0.0) return 0; + + worldPoint.x = quat.x * quat.w; + worldPoint.y = quat.y * quat.w; + worldPoint.z = quat.z * quat.w; + + return worldPoint; + */ +/* + // Needed Vectors + Vector3 normalDeviceCoordinates; + Quaternion rayClip; + Quaternion rayEye; + Vector3 rayWorld; + + // Getting normal device coordinates + float x = (2.0 * mousePosition.x) / GetScreenWidth() - 1.0; + float y = 1.0 - (2.0 * mousePosition.y) / GetScreenHeight(); + float z = 1.0; + normalDeviceCoordinates = (Vector3){ x, y, z }; + + // Getting clip vector + rayClip = (Quaternion){ normalDeviceCoordinates.x, normalDeviceCoordinates.y, -1, 1 }; + + Matrix invProjection = projection; + MatrixInvert(&invProjection); + + rayEye = MatrixQuaternionMultiply(invProjection, rayClip); + rayEye = (Quaternion){ rayEye.x, rayEye.y, -1, 0 }; + + Matrix invModelview = modelview; + MatrixInvert(&invModelview); + + rayWorld = MatrixVector3Multiply(invModelview, (Vector3){rayEye.x, rayEye.y, rayEye.z} ); + VectorNormalize(&rayWorld); + + return rayWorld; +*/ + return (Vector3){ 0, 0, 0 }; +} + // Convert image data to OpenGL texture (returns OpenGL valid Id) -unsigned int rlglLoadTexture(unsigned char *data, int width, int height, bool genMipmaps) +unsigned int rlglLoadTexture(void *data, int width, int height, int textureFormat, int mipmapCount, bool genMipmaps) { - glBindTexture(GL_TEXTURE_2D,0); // Free any old binding + glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding - GLuint id; - glGenTextures(1, &id); // Generate Pointer to the texture + GLuint id = 0; + + // Check texture format support by OpenGL 1.1 (compressed textures not supported) + if ((rlGetVersion() == OPENGL_11) && (textureFormat >= 8)) + { + TraceLog(WARNING, "OpenGL 1.1 does not support GPU compressed texture formats"); + return id; + } + + if ((!texCompDXTSupported) && ((textureFormat == COMPRESSED_DXT1_RGB) || (textureFormat == COMPRESSED_DXT1_RGBA) || + (textureFormat == COMPRESSED_DXT3_RGBA) || (textureFormat == COMPRESSED_DXT5_RGBA))) + { + TraceLog(WARNING, "DXT compressed texture format not supported"); + return id; + } + + if ((!texCompETC1Supported) && (textureFormat == COMPRESSED_ETC1_RGB)) + { + TraceLog(WARNING, "ETC1 compressed texture format not supported"); + return id; + } + + if ((!texCompETC2Supported) && ((textureFormat == COMPRESSED_ETC2_RGB) || (textureFormat == COMPRESSED_ETC2_EAC_RGBA))) + { + TraceLog(WARNING, "ETC2 compressed texture format not supported"); + return id; + } + + if ((!texCompPVRTSupported) && ((textureFormat == COMPRESSED_PVRT_RGB) || (textureFormat == COMPRESSED_PVRT_RGBA))) + { + TraceLog(WARNING, "PVRT compressed texture format not supported"); + return id; + } + + if ((!texCompASTCSupported) && ((textureFormat == COMPRESSED_ASTC_4x4_RGBA) || (textureFormat == COMPRESSED_ASTC_8x8_RGBA))) + { + TraceLog(WARNING, "ASTC compressed texture format not supported"); + return id; + } + + glGenTextures(1, &id); // Generate Pointer to the texture + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + //glActiveTexture(GL_TEXTURE0); // If not defined, using GL_TEXTURE0 by default (shader texture) +#endif - //glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, id); - // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used! - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repead on x-axis - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repead on y-axis +#if defined(GRAPHICS_API_OPENGL_33) + // NOTE: We define internal (GPU) format as GL_RGBA8 (probably BGRA8 in practice, driver takes care) + // NOTE: On embedded systems, we let the driver choose the best internal format + // Support for multiple color modes (16bit color modes and grayscale) + // (sized)internalFormat format type + // GL_R GL_RED GL_UNSIGNED_BYTE + // GL_RGB565 GL_RGB GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_5_6_5 + // GL_RGB5_A1 GL_RGBA GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_5_5_5_1 + // GL_RGBA4 GL_RGBA GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_4_4_4_4 + // GL_RGBA8 GL_RGBA GL_UNSIGNED_BYTE + // GL_RGB8 GL_RGB GL_UNSIGNED_BYTE + + switch (textureFormat) + { + case UNCOMPRESSED_GRAYSCALE: + { + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, (unsigned char *)data); + + // With swizzleMask we define how a one channel texture will be mapped to RGBA + // Required GL >= 3.3 or EXT_texture_swizzle/ARB_texture_swizzle + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + + TraceLog(INFO, "[TEX ID %i] Grayscale texture loaded and swizzled", id); + } break; + case UNCOMPRESSED_GRAY_ALPHA: + { + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, width, height, 0, GL_RG, GL_UNSIGNED_BYTE, (unsigned char *)data); + + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } break; + + case UNCOMPRESSED_R5G6B5: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB565, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (unsigned short *)data); break; + case UNCOMPRESSED_R8G8B8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, (unsigned char *)data); break; + case UNCOMPRESSED_R5G5B5A1: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB5_A1, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (unsigned short *)data); break; + case UNCOMPRESSED_R4G4B4A4: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA4, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (unsigned short *)data); break; + case UNCOMPRESSED_R8G8B8A8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)data); break; + case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_S3TC_DXT1_EXT); break; + case COMPRESSED_DXT1_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT); break; + case COMPRESSED_DXT3_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT); break; + case COMPRESSED_DXT5_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT); break; + case COMPRESSED_ETC1_RGB: if (texCompETC1Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_ETC1_RGB8_OES); break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case COMPRESSED_ETC2_RGB: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB8_ETC2); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case COMPRESSED_ETC2_EAC_RGBA: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA8_ETC2_EAC); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case COMPRESSED_PVRT_RGB: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU + case COMPRESSED_PVRT_RGBA: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU + case COMPRESSED_ASTC_4x4_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_4x4_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case COMPRESSED_ASTC_8x8_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_8x8_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + default: TraceLog(WARNING, "Texture format not recognized"); break; + } +#elif defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA + switch (textureFormat) + { + case UNCOMPRESSED_GRAYSCALE: glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, (unsigned char *)data); break; + case UNCOMPRESSED_GRAY_ALPHA: glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (unsigned char *)data); break; + case UNCOMPRESSED_R5G6B5: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, (unsigned short *)data); break; + case UNCOMPRESSED_R8G8B8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, (unsigned char *)data); break; + case UNCOMPRESSED_R5G5B5A1: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, (unsigned short *)data); break; + case UNCOMPRESSED_R4G4B4A4: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, (unsigned short *)data); break; + case UNCOMPRESSED_R8G8B8A8: glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (unsigned char *)data); break; +#if defined(GRAPHICS_API_OPENGL_ES2) + case COMPRESSED_DXT1_RGB: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_S3TC_DXT1_EXT); break; + case COMPRESSED_DXT1_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT); break; + case COMPRESSED_DXT3_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT); break; // NOTE: Not supported by WebGL + case COMPRESSED_DXT5_RGBA: if (texCompDXTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT); break; // NOTE: Not supported by WebGL + case COMPRESSED_ETC1_RGB: if (texCompETC1Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_ETC1_RGB8_OES); break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case COMPRESSED_ETC2_RGB: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB8_ETC2); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case COMPRESSED_ETC2_EAC_RGBA: if (texCompETC2Supported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA8_ETC2_EAC); break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case COMPRESSED_PVRT_RGB: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU + case COMPRESSED_PVRT_RGBA: if (texCompPVRTSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG); break; // NOTE: Requires PowerVR GPU + case COMPRESSED_ASTC_4x4_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_4x4_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case COMPRESSED_ASTC_8x8_RGBA: if (texCompASTCSupported) LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_8x8_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 +#endif + default: TraceLog(WARNING, "Texture format not supported"); break; + } +#endif + + // Check if texture is power-of-two (POT) to enable mipmap generation bool texIsPOT = false; - // Check if width and height are power-of-two (POT) if (((width > 0) && ((width & (width - 1)) == 0)) && ((height > 0) && ((height & (height - 1)) == 0))) texIsPOT = true; if (genMipmaps && !texIsPOT) { TraceLog(WARNING, "[TEX ID %i] Texture is not power-of-two, mipmaps can not be generated", id); - genMipmaps = false; } - // If mipmaps are being used, we configure mag-min filters accordingly - if (genMipmaps) - { - // Trilinear filtering with mipmaps - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate use of mipmaps (must be available) - } - else - { - // Not using mipmappings - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR - } - + // Generate mipmaps if required + // TODO: Improve mipmaps support #if defined(GRAPHICS_API_OPENGL_11) if (genMipmaps) { - TraceLog(WARNING, "[TEX ID %i] Mipmaps generated manually on CPU side", id); - // Compute required mipmaps // NOTE: data size is reallocated to fit mipmaps data int mipmapCount = GenerateMipmaps(data, width, height); - int offset = 0; - int size = 0; + // TODO: Adjust mipmap size depending on texture format! + int size = width*height*4; + int offset = size; - int mipWidth = width; - int mipHeight = height; + int mipWidth = width/2; + int mipHeight = height/2; // Load the mipmaps - for (int level = 0; level < mipmapCount; level++) + for (int level = 1; level < mipmapCount; level++) { glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, mipWidth, mipHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, data + offset); @@ -1329,33 +1793,47 @@ unsigned int rlglLoadTexture(unsigned char *data, int width, int height, bool ge mipWidth /= 2; mipHeight /= 2; } + + TraceLog(WARNING, "[TEX ID %i] Mipmaps generated manually on CPU side", id); + } +#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((mipmapCount == 1) && (genMipmaps)) + { + glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically + TraceLog(INFO, "[TEX ID %i] Mipmaps generated automatically for new texture", id); } - else glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); #endif -#if defined(GRAPHICS_API_OPENGL_33) - // NOTE: We define internal (GPU) format as GL_RGBA8 (probably BGRA8 in practice, driver takes care) - //glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); // OpenGL - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); // WebGL -#elif defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: On embedded systems, we let the driver choose the best internal format - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + // Texture parameters configuration + // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis +#else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis #endif -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (genMipmaps) + // Magnification and minification filters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR + +#if defined(GRAPHICS_API_OPENGL_33) + if ((mipmapCount > 1) || (genMipmaps)) { - glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically - TraceLog(INFO, "[TEX ID %i] Mipmaps generated automatically for new texture", id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate Trilinear filtering for mipmaps (must be available) } #endif - // At this point we have the image converted to texture and uploaded to GPU + // At this point we have the texture loaded in GPU, with mipmaps generated (if desired) and texture parameters configured // Unbind current texture glBindTexture(GL_TEXTURE_2D, 0); - TraceLog(INFO, "[TEX ID %i] Texture created successfully (%ix%i)", id, width, height); + if (id > 0) TraceLog(INFO, "[TEX ID %i] Texture created successfully (%ix%i)", id, width, height); + else TraceLog(WARNING, "Texture could not be created"); return id; } @@ -1366,19 +1844,23 @@ Model rlglLoadModel(VertexData mesh) Model model; model.mesh = mesh; + model.transform = MatrixIdentity(); #if defined(GRAPHICS_API_OPENGL_11) - model.textureId = 0; // No texture required - model.vaoId = 0; // Vertex Array Object - model.vboId[0] = 0; // Vertex position VBO - model.vboId[1] = 0; // Texcoords VBO - model.vboId[2] = 0; // Normals VBO + model.mesh.vaoId = 0; // Vertex Array Object + model.mesh.vboId[0] = 0; // Vertex position VBO + model.mesh.vboId[1] = 0; // Texcoords VBO + model.mesh.vboId[2] = 0; // Normals VBO + model.texture.id = 0; // No texture required + model.shader.id = 0; // No shader used #elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - model.textureId = 1; // Default whiteTexture + model.texture.id = whiteTexture; // Default whiteTexture + model.texture.width = 1; // Default whiteTexture width + model.texture.height = 1; // Default whiteTexture height - GLuint vaoModel = 0; // Vertex Array Objects (VAO) - GLuint vertexBuffer[3]; // Vertex Buffer Objects (VBO) + GLuint vaoModel = 0; // Vertex Array Objects (VAO) + GLuint vertexBuffer[3]; // Vertex Buffer Objects (VBO) if (vaoSupported) { @@ -1393,121 +1875,145 @@ Model rlglLoadModel(VertexData mesh) // Enable vertex attributes: position glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh.vertexCount, mesh.vertices, GL_STATIC_DRAW); - glEnableVertexAttribArray(simpleVertexLoc); - glVertexAttribPointer(simpleVertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(simpleShader.vertexLoc); + glVertexAttribPointer(simpleShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); // Enable vertex attributes: texcoords glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*mesh.vertexCount, mesh.texcoords, GL_STATIC_DRAW); - glEnableVertexAttribArray(simpleTexcoordLoc); - glVertexAttribPointer(simpleTexcoordLoc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(simpleShader.texcoordLoc); + glVertexAttribPointer(simpleShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); // Enable vertex attributes: normals glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer[2]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*mesh.vertexCount, mesh.normals, GL_STATIC_DRAW); - glEnableVertexAttribArray(simpleNormalLoc); - glVertexAttribPointer(simpleNormalLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(simpleShader.normalLoc); + glVertexAttribPointer(simpleShader.normalLoc, 3, GL_FLOAT, 0, 0, 0); + + model.shader = simpleShader; // By default, simple shader will be used + + model.mesh.vboId[0] = vertexBuffer[0]; // Vertex position VBO + model.mesh.vboId[1] = vertexBuffer[1]; // Texcoords VBO + model.mesh.vboId[2] = vertexBuffer[2]; // Normals VBO - model.vboId[0] = vertexBuffer[0]; // Vertex position VBO - model.vboId[1] = vertexBuffer[1]; // Texcoords VBO - model.vboId[2] = vertexBuffer[2]; // Normals VBO - if (vaoSupported) { if (vaoModel > 0) { - model.vaoId = vaoModel; + model.mesh.vaoId = vaoModel; TraceLog(INFO, "[VAO ID %i] Model uploaded successfully to VRAM (GPU)", vaoModel); } else TraceLog(WARNING, "Model could not be uploaded to VRAM (GPU)"); } else { - TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i] Model uploaded successfully to VRAM (GPU)", model.vboId[0], model.vboId[1], model.vboId[2]); + TraceLog(INFO, "[VBO ID %i][VBO ID %i][VBO ID %i] Model uploaded successfully to VRAM (GPU)", model.mesh.vboId[0], model.mesh.vboId[1], model.mesh.vboId[2]); } #endif return model; } -// Convert image data to OpenGL texture (returns OpenGL valid Id) -// NOTE: Expected compressed image data and POT image -unsigned int rlglLoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compFormat) + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Load a shader (vertex shader + fragment shader) from text data +unsigned int rlglLoadShaderFromText(char *vShaderStr, char *fShaderStr) { - GLuint id; + unsigned int program; + GLuint vertexShader; + GLuint fragmentShader; -#if defined(GRAPHICS_API_OPENGL_11) - id = 0; - TraceLog(WARNING, "GPU compressed textures not supported on OpenGL 1.1"); + vertexShader = glCreateShader(GL_VERTEX_SHADER); + fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); -#elif defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - TraceLog(DEBUG, "Compressed texture width: %i", width); - TraceLog(DEBUG, "Compressed texture height: %i", height); - TraceLog(DEBUG, "Compressed texture mipmap levels: %i", mipmapCount); - TraceLog(DEBUG, "Compressed texture format: 0x%x", compFormat); + const char *pvs = vShaderStr; + const char *pfs = fShaderStr; - if (compFormat == 0) + glShaderSource(vertexShader, 1, &pvs, NULL); + glShaderSource(fragmentShader, 1, &pfs, NULL); + + GLint success = 0; + + glCompileShader(vertexShader); + + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); + + if (success != GL_TRUE) { - id = 0; - TraceLog(WARNING, "Texture compressed format not recognized", id); + TraceLog(WARNING, "[VSHDR ID %i] Failed to compile vertex shader...", vertexShader); + + int maxLength = 0; + int length; + + glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &maxLength); + + char log[maxLength]; + + glGetShaderInfoLog(vertexShader, maxLength, &length, log); + + TraceLog(INFO, "%s", log); } - else + else TraceLog(INFO, "[VSHDR ID %i] Vertex shader compiled successfully", vertexShader); + + glCompileShader(fragmentShader); + + glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); + + if (success != GL_TRUE) { - glGenTextures(1, &id); + TraceLog(WARNING, "[FSHDR ID %i] Failed to compile fragment shader...", fragmentShader); - // Bind the texture - glBindTexture(GL_TEXTURE_2D, id); - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + int maxLength = 0; + int length; - // Set texture parameters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &maxLength); - // If mipmaps are being used, we configure mag-min filters accordingly - if (mipmapCount > 1) - { - // Trilinear filtering with mipmaps - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Activate use of mipmaps (must be available) - } - else - { - // Not using mipmappings - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Filter for pixel-perfect drawing, alternative: GL_LINEAR - } + char log[maxLength]; - int blockSize = 0; - int offset = 0; + glGetShaderInfoLog(fragmentShader, maxLength, &length, log); - if (compFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) blockSize = 8; - else blockSize = 16; + TraceLog(INFO, "%s", log); + } + else TraceLog(INFO, "[FSHDR ID %i] Fragment shader compiled successfully", fragmentShader); - // Load the mipmaps - for (int level = 0; level < mipmapCount && (width || height); level++) - { - unsigned int size = 0; + program = glCreateProgram(); - // NOTE: size specifies the number of bytes of image data (S3TC/DXTC) - if (compFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) size = ((width + 3)/4)*((height + 3)/4)*blockSize; // S3TC/DXTC -#if defined(GRAPHICS_API_OPENGL_ES2) - else if (compFormat == GL_ETC1_RGB8_OES) size = 8*((width + 3) >> 2)*((height + 3) >> 2); // ETC1 -#endif - glCompressedTexImage2D(GL_TEXTURE_2D, level, compFormat, width, height, 0, size, data + offset); + glAttachShader(program, vertexShader); + glAttachShader(program, fragmentShader); - offset += size; - width /= 2; - height /= 2; + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links - // Security check for NPOT textures - if (width < 1) width = 1; - if (height < 1) height = 1; - } + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TraceLog(WARNING, "[SHDR ID %i] Failed to link shader program...", program); + + int maxLength = 0; + int length; + + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + char log[maxLength]; + + glGetProgramInfoLog(program, maxLength, &length, log); + + TraceLog(INFO, "%s", log); + + glDeleteProgram(program); + + program = 0; } -#endif + else TraceLog(INFO, "[SHDR ID %i] Shader program loaded successfully", program); - return id; + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + return program; } +#endif // Read screen pixel data (color buffer) unsigned char *rlglReadScreenPixels(int width, int height) @@ -1533,18 +2039,216 @@ unsigned char *rlglReadScreenPixels(int width, int height) return imgData; // NOTE: image data should be freed } +// Load a shader (vertex shader + fragment shader) from files +Shader rlglLoadShader(char *vsFileName, char *fsFileName) +{ + Shader shader; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Shaders loading from external text file + char *vShaderStr = TextFileRead(vsFileName); + char *fShaderStr = TextFileRead(fsFileName); + + shader.id = rlglLoadShaderFromText(vShaderStr, fShaderStr); + + if (shader.id != 0) TraceLog(INFO, "[SHDR ID %i] Custom shader loaded successfully", shader.id); + else TraceLog(WARNING, "[SHDR ID %i] Custom shader could not be loaded", shader.id); + + // Shader strings must be freed + free(vShaderStr); + free(fShaderStr); + + // Set shader textures ids (all 0 by default) + shader.texDiffuseId = 0; + shader.texNormalId = 0; + shader.texSpecularId = 0; + + // Get handles to GLSL input attibute locations + //------------------------------------------------------------------- + shader.vertexLoc = glGetAttribLocation(shader.id, "vertexPosition"); + shader.texcoordLoc = glGetAttribLocation(shader.id, "vertexTexCoord"); + shader.normalLoc = glGetAttribLocation(shader.id, "vertexNormal"); + // NOTE: custom shader does not use colorLoc + shader.colorLoc = -1; + + // Get handles to GLSL uniform locations (vertex shader) + shader.modelviewLoc = glGetUniformLocation(shader.id, "modelviewMatrix"); + shader.projectionLoc = glGetUniformLocation(shader.id, "projectionMatrix"); + + // Get handles to GLSL uniform locations (fragment shader) + shader.tintColorLoc = glGetUniformLocation(shader.id, "tintColor"); + shader.mapDiffuseLoc = glGetUniformLocation(shader.id, "texture0"); + shader.mapNormalLoc = -1; // It can be set later + shader.mapSpecularLoc = -1; // It can be set later + //-------------------------------------------------------------------- +#endif + + return shader; +} + +// Link shader to model +void rlglSetModelShader(Model *model, Shader shader) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + model->shader = shader; + + if (vaoSupported) glBindVertexArray(model->mesh.vaoId); + + // Enable vertex attributes: position + glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[0]); + glEnableVertexAttribArray(shader.vertexLoc); + glVertexAttribPointer(shader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + + // Enable vertex attributes: texcoords + glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[1]); + glEnableVertexAttribArray(shader.texcoordLoc); + glVertexAttribPointer(shader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + + // Enable vertex attributes: normals + glBindBuffer(GL_ARRAY_BUFFER, model->mesh.vboId[2]); + glEnableVertexAttribArray(shader.normalLoc); + glVertexAttribPointer(shader.normalLoc, 3, GL_FLOAT, 0, 0, 0); + + if (vaoSupported) glBindVertexArray(0); // Unbind VAO + + //if (model->texture.id > 0) model->shader.texDiffuseId = model->texture.id; +#endif +} + +// Set custom shader to be used on batch draw +void rlglSetCustomShader(Shader shader) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (currentShader.id != shader.id) + { + rlglDraw(); + + currentShader = shader; +/* + if (vaoSupported) glBindVertexArray(vaoQuads); + + // Enable vertex attributes: position + glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]); + glEnableVertexAttribArray(currentShader.vertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + + // Enable vertex attributes: texcoords + glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]); + glEnableVertexAttribArray(currentShader.texcoordLoc); + glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + + // Enable vertex attributes: colors + glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); + glEnableVertexAttribArray(currentShader.colorLoc); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + if (vaoSupported) glBindVertexArray(0); // Unbind VAO +*/ + } +#endif +} + +// Set default shader to be used on batch draw +void rlglSetDefaultShader(void) +{ #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlglSetCustomShader(defaultShader); + rlglSetPostproShader(defaultShader); +#endif +} + +int GetShaderLocation(Shader shader, const char *uniformName) +{ + int location = glGetUniformLocation(shader.id, uniformName); + + if (location == -1) TraceLog(WARNING, "[SHDR ID %i] Shader location for %s could not be found", shader.id, uniformName); + + return location; +} + +void SetShaderValue(Shader shader, int uniformLoc, float *value, int size) +{ + glUseProgram(shader.id); + + if (size == 1) glUniform1fv(uniformLoc, 1, value); // Shader uniform type: float + else if (size == 2) glUniform2fv(uniformLoc, 1, value); // Shader uniform type: vec2 + else if (size == 3) glUniform3fv(uniformLoc, 1, value); // Shader uniform type: vec3 + else if (size == 4) glUniform4fv(uniformLoc, 1, value); // Shader uniform type: vec4 + else TraceLog(WARNING, "Shader value float array size not recognized"); + + glUseProgram(0); +} -void PrintProjectionMatrix() +void SetShaderMapDiffuse(Shader *shader, Texture2D texture) +{ + shader->texDiffuseId = texture.id; + + glUseProgram(shader->id); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, shader->texDiffuseId); + + glUniform1i(shader->mapDiffuseLoc, 0); // Texture fits in active texture unit 0 + + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glUseProgram(0); +} + +void SetShaderMapNormal(Shader *shader, const char *uniformName, Texture2D texture) +{ + shader->mapNormalLoc = glGetUniformLocation(shader->id, uniformName); + + if (shader->mapNormalLoc == -1) TraceLog(WARNING, "[SHDR ID %i] Shader location for %s could not be found", shader->id, uniformName); + else + { + shader->texNormalId = texture.id; + + glUseProgram(shader->id); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, shader->texNormalId); + + glUniform1i(shader->mapNormalLoc, 1); // Texture fits in active texture unit 1 + + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glUseProgram(0); + } +} + +void SetShaderMapSpecular(Shader *shader, const char *uniformName, Texture2D texture) +{ + shader->mapSpecularLoc = glGetUniformLocation(shader->id, uniformName); + + if (shader->mapSpecularLoc == -1) TraceLog(WARNING, "[SHDR ID %i] Shader location for %s could not be found", shader->id, uniformName); + else + { + shader->texSpecularId = texture.id; + + glUseProgram(shader->id); + + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, shader->texSpecularId); + + glUniform1i(shader->mapSpecularLoc, 2); // Texture fits in active texture unit 0 + + glBindTexture(GL_TEXTURE_2D, 0); + glActiveTexture(GL_TEXTURE0); + glUseProgram(0); + } +} + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +void PrintProjectionMatrix(void) { PrintMatrix(projection); } -void PrintModelviewMatrix() +void PrintModelviewMatrix(void) { PrintMatrix(modelview); } - #endif //---------------------------------------------------------------------------------- @@ -1552,257 +2256,206 @@ void PrintModelviewMatrix() //---------------------------------------------------------------------------------- #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Convert image data to OpenGL texture (returns OpenGL valid Id) +// NOTE: Expected compressed image data and POT image +static void LoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int compressedFormat) +{ + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + int blockSize = 0; // Bytes every block + int offset = 0; + + if ((compressedFormat == GL_COMPRESSED_RGB_S3TC_DXT1_EXT) || + (compressedFormat == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) || +#if defined(GRAPHICS_API_OPENGL_ES2) + (compressedFormat == GL_ETC1_RGB8_OES) || +#endif + (compressedFormat == GL_COMPRESSED_RGB8_ETC2)) blockSize = 8; + else blockSize = 16; + + // Load the mipmap levels + for (int level = 0; level < mipmapCount && (width || height); level++) + { + unsigned int size = 0; + + size = ((width + 3)/4)*((height + 3)/4)*blockSize; + + glCompressedTexImage2D(GL_TEXTURE_2D, level, compressedFormat, width, height, 0, size, data + offset); + + offset += size; + width /= 2; + height /= 2; + + // Security check for NPOT textures + if (width < 1) width = 1; + if (height < 1) height = 1; + } +} // Load Shader (Vertex and Fragment) // NOTE: This shader program is used only for batch buffers (lines, triangles, quads) -static GLuint LoadDefaultShader(void) +static Shader LoadDefaultShader(void) { + Shader shader; + // NOTE: Shaders are written using GLSL 110 (desktop), that is equivalent to GLSL 100 on ES2 + // NOTE: Detected an error on ATI cards if defined #version 110 while OpenGL 3.3+ + // Just defined #version 330 despite shader is #version 110 // Vertex shader directly defined, no external file required #if defined(GRAPHICS_API_OPENGL_33) - char vShaderStr[] = " #version 110 \n" // NOTE: Equivalent to version 100 on ES2 + char vShaderStr[] = " #version 330 \n" + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 tintColor; \n" #elif defined(GRAPHICS_API_OPENGL_ES2) - char vShaderStr[] = " #version 100 \n" // NOTE: Must be defined this way! 110 doesn't work! -#endif - "uniform mat4 projectionMatrix; \n" - "uniform mat4 modelviewMatrix; \n" + char vShaderStr[] = " #version 100 \n" "attribute vec3 vertexPosition; \n" "attribute vec2 vertexTexCoord; \n" "attribute vec4 vertexColor; \n" "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" + "varying vec4 tintColor; \n" +#endif + "uniform mat4 projectionMatrix; \n" + "uniform mat4 modelviewMatrix; \n" "void main() \n" "{ \n" " fragTexCoord = vertexTexCoord; \n" - " fragColor = vertexColor; \n" - " gl_Position = projectionMatrix * modelviewMatrix * vec4(vertexPosition, 1.0); \n" + " tintColor = vertexColor; \n" + " gl_Position = projectionMatrix*modelviewMatrix*vec4(vertexPosition, 1.0); \n" "} \n"; // Fragment shader directly defined, no external file required #if defined(GRAPHICS_API_OPENGL_33) - char fShaderStr[] = " #version 110 \n" // NOTE: Equivalent to version 100 on ES2 + char fShaderStr[] = " #version 330 \n" + "in vec2 fragTexCoord; \n" + "in vec4 tintColor; \n" #elif defined(GRAPHICS_API_OPENGL_ES2) - char fShaderStr[] = " #version 100 \n" // NOTE: Must be defined this way! 110 doesn't work! - "precision mediump float; \n" // WebGL, required for emscripten + char fShaderStr[] = " #version 100 \n" + "precision mediump float; \n" // precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" + "varying vec4 tintColor; \n" #endif "uniform sampler2D texture0; \n" - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" "void main() \n" "{ \n" - " gl_FragColor = texture2D(texture0, fragTexCoord) * fragColor; \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" // NOTE: texture2D() is deprecated on OpenGL 3.3 and ES 3.0, use texture() instead + " gl_FragColor = texelColor*tintColor; \n" "} \n"; - GLuint program; - GLuint vertexShader; - GLuint fragmentShader; - - vertexShader = glCreateShader(GL_VERTEX_SHADER); - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + shader.id = rlglLoadShaderFromText(vShaderStr, fShaderStr); - const char *pvs = vShaderStr; - const char *pfs = fShaderStr; - - glShaderSource(vertexShader, 1, &pvs, NULL); - glShaderSource(fragmentShader, 1, &pfs, NULL); + if (shader.id != 0) TraceLog(INFO, "[SHDR ID %i] Default shader loaded successfully", shader.id); + else TraceLog(WARNING, "[SHDR ID %i] Default shader could not be loaded", shader.id); - GLint success = 0; - - glCompileShader(vertexShader); - - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); - - if (success != GL_TRUE) TraceLog(WARNING, "[VSHDR ID %i] Failed to compile default vertex shader...", vertexShader); - else TraceLog(INFO, "[VSHDR ID %i] Default vertex shader compiled successfully", vertexShader); - - glCompileShader(fragmentShader); - - glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); - - if (success != GL_TRUE) TraceLog(WARNING, "[FSHDR ID %i] Failed to compile default fragment shader...", fragmentShader); - else TraceLog(INFO, "[FSHDR ID %i] Default fragment shader compiled successfully", fragmentShader); - - program = glCreateProgram(); - - glAttachShader(program, vertexShader); - glAttachShader(program, fragmentShader); - - glLinkProgram(program); - - glGetProgramiv(program, GL_LINK_STATUS, &success); - - if (success == GL_FALSE) - { - int maxLength; - int length; - - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); - - char log[maxLength]; - - glGetProgramInfoLog(program, maxLength, &length, log); - - TraceLog(INFO, "Shader program fail log: %s", log); - } - else TraceLog(INFO, "[SHDR ID %i] Default shader program loaded successfully", program); - - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); + // Get handles to GLSL input attibute locations + //------------------------------------------------------------------- + shader.vertexLoc = glGetAttribLocation(shader.id, "vertexPosition"); + shader.texcoordLoc = glGetAttribLocation(shader.id, "vertexTexCoord"); + shader.colorLoc = glGetAttribLocation(shader.id, "vertexColor"); + // NOTE: default shader does not use normalLoc + shader.normalLoc = -1; + + // Get handles to GLSL uniform locations (vertex shader) + shader.modelviewLoc = glGetUniformLocation(shader.id, "modelviewMatrix"); + shader.projectionLoc = glGetUniformLocation(shader.id, "projectionMatrix"); + + // Get handles to GLSL uniform locations (fragment shader) + shader.tintColorLoc = -1; + shader.mapDiffuseLoc = glGetUniformLocation(shader.id, "texture0"); + shader.mapNormalLoc = -1; // It can be set later + shader.mapSpecularLoc = -1; // It can be set later + //-------------------------------------------------------------------- - return program; + return shader; } // Load Simple Shader (Vertex and Fragment) // NOTE: This shader program is used to render models -static GLuint LoadSimpleShader(void) +static Shader LoadSimpleShader(void) { + Shader shader; + // NOTE: Shaders are written using GLSL 110 (desktop), that is equivalent to GLSL 100 on ES2 + // NOTE: Detected an error on ATI cards if defined #version 110 while OpenGL 3.3+ + // Just defined #version 330 despite shader is #version 110 // Vertex shader directly defined, no external file required #if defined(GRAPHICS_API_OPENGL_33) - char vShaderStr[] = " #version 110 \n" // NOTE: Equivalent to version 100 on ES2 + char vShaderStr[] = " #version 330 \n" + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec3 vertexNormal; \n" + "out vec2 fragTexCoord; \n" #elif defined(GRAPHICS_API_OPENGL_ES2) - char vShaderStr[] = " #version 100 \n" // NOTE: Must be defined this way! 110 doesn't work! -#endif - "uniform mat4 projectionMatrix; \n" - "uniform mat4 modelviewMatrix; \n" + char vShaderStr[] = " #version 100 \n" "attribute vec3 vertexPosition; \n" "attribute vec2 vertexTexCoord; \n" "attribute vec3 vertexNormal; \n" "varying vec2 fragTexCoord; \n" +#endif + "uniform mat4 projectionMatrix; \n" + "uniform mat4 modelviewMatrix; \n" "void main() \n" "{ \n" " fragTexCoord = vertexTexCoord; \n" - " gl_Position = projectionMatrix * modelviewMatrix * vec4(vertexPosition, 1.0); \n" + " gl_Position = projectionMatrix*modelviewMatrix*vec4(vertexPosition, 1.0); \n" "} \n"; // Fragment shader directly defined, no external file required #if defined(GRAPHICS_API_OPENGL_33) - char fShaderStr[] = " #version 110 \n" // NOTE: Equivalent to version 100 on ES2 + char fShaderStr[] = " #version 330 \n" + "in vec2 fragTexCoord; \n" #elif defined(GRAPHICS_API_OPENGL_ES2) - char fShaderStr[] = " #version 100 \n" // NOTE: Must be defined this way! 110 doesn't work! - "precision mediump float; \n" // WebGL, required for emscripten + char fShaderStr[] = " #version 100 \n" + "precision mediump float; \n" // precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" #endif "uniform sampler2D texture0; \n" - "varying vec2 fragTexCoord; \n" - "uniform vec4 fragColor; \n" + "uniform vec4 tintColor; \n" "void main() \n" "{ \n" - " gl_FragColor = texture2D(texture0, fragTexCoord) * fragColor; \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" // NOTE: texture2D() is deprecated on OpenGL 3.3 and ES 3.0, use texture() instead + " gl_FragColor = texelColor*tintColor; \n" "} \n"; - GLuint program; - GLuint vertexShader; - GLuint fragmentShader; + shader.id = rlglLoadShaderFromText(vShaderStr, fShaderStr); - vertexShader = glCreateShader(GL_VERTEX_SHADER); - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - - const char *pvs = vShaderStr; - const char *pfs = fShaderStr; - - glShaderSource(vertexShader, 1, &pvs, NULL); - glShaderSource(fragmentShader, 1, &pfs, NULL); - - GLint success = 0; - - glCompileShader(vertexShader); - - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success); + if (shader.id != 0) TraceLog(INFO, "[SHDR ID %i] Simple shader loaded successfully", shader.id); + else TraceLog(WARNING, "[SHDR ID %i] Simple shader could not be loaded", shader.id); - if (success != GL_TRUE) TraceLog(WARNING, "[VSHDR ID %i] Failed to compile simple vertex shader...", vertexShader); - else TraceLog(INFO, "[VSHDR ID %i] Simple vertex shader compiled successfully", vertexShader); - - glCompileShader(fragmentShader); - - glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); - - if (success != GL_TRUE) TraceLog(WARNING, "[FSHDR ID %i] Failed to compile simple fragment shader...", fragmentShader); - else TraceLog(INFO, "[FSHDR ID %i] Simple fragment shader compiled successfully", fragmentShader); - - program = glCreateProgram(); - - glAttachShader(program, vertexShader); - glAttachShader(program, fragmentShader); - - glLinkProgram(program); - - glGetProgramiv(program, GL_LINK_STATUS, &success); - - if (success == GL_FALSE) - { - int maxLength; - int length; - - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); - - char log[maxLength]; - - glGetProgramInfoLog(program, maxLength, &length, log); - - TraceLog(INFO, "Shader program fail log: %s", log); - } - else TraceLog(INFO, "[SHDR ID %i] Simple shader program loaded successfully", program); - - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); - - return program; -} - -// Load shaders from text files -static GLuint LoadCustomShader(char *vertexFileName, char *fragmentFileName) -{ - // Shaders loading from external text file - char *vShaderStr = TextFileRead(vertexFileName); - char *fShaderStr = TextFileRead(fragmentFileName); - - GLuint program; - GLuint vertexShader; - GLuint fragmentShader; - - vertexShader = glCreateShader(GL_VERTEX_SHADER); - fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); - - const char *pvs = vShaderStr; - const char *pfs = fShaderStr; - - glShaderSource(vertexShader, 1, &pvs, NULL); - glShaderSource(fragmentShader, 1, &pfs, NULL); - - glCompileShader(vertexShader); - glCompileShader(fragmentShader); - - TraceLog(INFO, "[VSHDR ID %i] Vertex shader compiled successfully", vertexShader); - TraceLog(INFO, "[FSHDR ID %i] Fragment shader compiled successfully", fragmentShader); - - program = glCreateProgram(); - - glAttachShader(program, vertexShader); - glAttachShader(program, fragmentShader); - - glLinkProgram(program); - - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); - - free(vShaderStr); - free(fShaderStr); - - TraceLog(INFO, "[SHDR ID %i] Shader program loaded successfully", program); + // Get handles to GLSL input attibute locations + //------------------------------------------------------------------- + shader.vertexLoc = glGetAttribLocation(shader.id, "vertexPosition"); + shader.texcoordLoc = glGetAttribLocation(shader.id, "vertexTexCoord"); + shader.normalLoc = glGetAttribLocation(shader.id, "vertexNormal"); + // NOTE: simple shader does not use colorLoc + shader.colorLoc = -1; + + // Get handles to GLSL uniform locations (vertex shader) + shader.modelviewLoc = glGetUniformLocation(shader.id, "modelviewMatrix"); + shader.projectionLoc = glGetUniformLocation(shader.id, "projectionMatrix"); + + // Get handles to GLSL uniform locations (fragment shader) + shader.tintColorLoc = glGetUniformLocation(shader.id, "tintColor"); + shader.mapDiffuseLoc = glGetUniformLocation(shader.id, "texture0"); + shader.mapNormalLoc = -1; // It can be set later + shader.mapSpecularLoc = -1; // It can be set later + //-------------------------------------------------------------------- - return program; + return shader; } -// Read shader text file +// Read text file // NOTE: text chars array should be freed manually static char *TextFileRead(char *fileName) { FILE *textFile; char *text = NULL; - int count=0; + int count = 0; if (fileName != NULL) { @@ -1816,7 +2469,7 @@ static char *TextFileRead(char *fileName) if (count > 0) { - text = (char *)malloc(sizeof(char) * (count+1)); + text = (char *)malloc(sizeof(char) * (count + 1)); count = fread(text, sizeof(char), count, textFile); text[count] = '\0'; } @@ -1825,7 +2478,7 @@ static char *TextFileRead(char *fileName) } else TraceLog(WARNING, "[%s] Text file could not be opened", fileName); } - + return text; } @@ -1890,6 +2543,7 @@ static void InitializeBuffers(void) } // Initialize Vertex Array Objects (Contain VBO) +// NOTE: lines, triangles and quads buffers use currentShader static void InitializeBuffersGPU(void) { if (vaoSupported) @@ -1905,14 +2559,14 @@ static void InitializeBuffersGPU(void) // Lines - Vertex positions buffer binding and attributes enable glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*2*MAX_LINES_BATCH, lines.vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultVertexLoc); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); // Lines - colors buffer glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*2*MAX_LINES_BATCH, lines.colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultColorLoc); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Lines VAO initialized successfully", vaoLines); else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Lines VBOs initialized successfully", linesBuffer[0], linesBuffer[1]); @@ -1924,20 +2578,20 @@ static void InitializeBuffersGPU(void) glGenVertexArrays(1, &vaoTriangles); glBindVertexArray(vaoTriangles); } - + // Create buffers for our vertex data glGenBuffers(2, trianglesBuffer); // Enable vertex attributes glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*3*MAX_TRIANGLES_BATCH, triangles.vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultVertexLoc); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*3*MAX_TRIANGLES_BATCH, triangles.colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultColorLoc); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); if (vaoSupported) TraceLog(INFO, "[VAO ID %i] Triangles VAO initialized successfully", vaoTriangles); else TraceLog(INFO, "[VBO ID %i][VBO ID %i] Triangles VBOs initialized successfully", trianglesBuffer[0], trianglesBuffer[1]); @@ -1949,25 +2603,25 @@ static void InitializeBuffersGPU(void) glGenVertexArrays(1, &vaoQuads); glBindVertexArray(vaoQuads); } - + // Create buffers for our vertex data glGenBuffers(4, quadsBuffer); // Enable vertex attributes glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*MAX_QUADS_BATCH, quads.vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultVertexLoc); - glVertexAttribPointer(defaultVertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]); glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*MAX_QUADS_BATCH, quads.texcoords, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultTexcoordLoc); - glVertexAttribPointer(defaultTexcoordLoc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.texcoordLoc); + glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); glBufferData(GL_ARRAY_BUFFER, sizeof(unsigned char)*4*4*MAX_QUADS_BATCH, quads.colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(defaultColorLoc); - glVertexAttribPointer(defaultColorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); // Fill index buffer glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadsBuffer[3]); @@ -1985,15 +2639,15 @@ static void InitializeBuffersGPU(void) } // Update VBOs with vertex array data -// TODO: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) -// TODO: If no data changed on the CPU arrays --> No need to update GPU arrays every frame! +// NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) +// TODO: If no data changed on the CPU arrays --> No need to update GPU arrays static void UpdateBuffers(void) { if (lines.vCounter > 0) { // Activate Lines VAO if (vaoSupported) glBindVertexArray(vaoLines); - + // Lines - vertex positions buffer glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]); //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*2*MAX_LINES_BATCH, lines.vertices, GL_DYNAMIC_DRAW); @@ -2010,7 +2664,7 @@ static void UpdateBuffers(void) { // Activate Triangles VAO if (vaoSupported) glBindVertexArray(vaoTriangles); - + // Triangles - vertex positions buffer glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]); //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*3*MAX_TRIANGLES_BATCH, triangles.vertices, GL_DYNAMIC_DRAW); @@ -2053,11 +2707,9 @@ static void UpdateBuffers(void) // Unbind the current VAO if (vaoSupported) glBindVertexArray(0); } - #endif //defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) #if defined(GRAPHICS_API_OPENGL_11) - // Mipmaps data is generated after image data static int GenerateMipmaps(unsigned char *data, int baseWidth, int baseHeight) { @@ -2182,7 +2834,6 @@ static pixel *GenNextMipmap(pixel *srcData, int srcWidth, int srcHeight) return mipmap; } - #endif #if defined(RLGL_STANDALONE) @@ -2212,4 +2863,54 @@ void TraceLog(int msgType, const char *text, ...) if (msgType == ERROR) exit(1); } -#endif
\ No newline at end of file +#endif + +static char **StringSplit(char *baseString, const char delimiter, int *numExt) +{ + char **result = 0; + int count = 0; + char *tmp = baseString; + char *lastComma = 0; + char delim[2]; + + delim[0] = delimiter; + delim[1] = 0; + + // Count how many elements will be extracted + while (*tmp) + { + if (delimiter == *tmp) + { + count++; + lastComma = tmp; + } + + tmp++; + } + + // Add space for trailing token + count += lastComma < (baseString + strlen(baseString) - 1); + + // Add space for terminating null string + count++; + + result = malloc(sizeof(char *)*count); + + if (result) + { + int idx = 0; + char *token = strtok(baseString, delim); + + while (token) + { + *(result + idx++) = token; + token = strtok(0, delim); + } + + *(result + idx) = 0; + } + + *numExt = (count - 1); + + return result; +}
\ No newline at end of file @@ -32,7 +32,7 @@ //#define RLGL_STANDALONE // NOTE: To use rlgl as standalone lib, just uncomment this line #ifndef RLGL_STANDALONE - #include "raylib.h" // Required for typedef: Model + #include "raylib.h" // Required for typedef(s): Model, Shader, Texture2D #include "utils.h" // Required for function TraceLog() #endif @@ -89,21 +89,69 @@ typedef enum { RL_LINES, RL_TRIANGLES, RL_QUADS } DrawMode; typedef enum { OPENGL_11 = 1, OPENGL_33, OPENGL_ES_20 } GlVersion; #ifdef RLGL_STANDALONE - typedef struct { + // Texture formats (support depends on OpenGL version) + typedef enum { + UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + UNCOMPRESSED_R5G6B5, // 16 bpp + UNCOMPRESSED_R8G8B8, // 24 bpp + UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + UNCOMPRESSED_R8G8B8A8, // 32 bpp + COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + COMPRESSED_DXT3_RGBA, // 8 bpp + COMPRESSED_DXT5_RGBA, // 8 bpp + COMPRESSED_ETC1_RGB, // 4 bpp + COMPRESSED_ETC2_RGB, // 4 bpp + COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + COMPRESSED_PVRT_RGB, // 4 bpp + COMPRESSED_PVRT_RGBA, // 4 bpp + /*COMPRESSED_ASTC_RGBA_4x4*/ // 8 bpp + } TextureFormat; + + // VertexData type + // NOTE: If using OpenGL 1.1, data loaded in CPU; if OpenGL 3.3+ data loaded in GPU (vaoId) + typedef struct VertexData { int vertexCount; float *vertices; // 3 components per vertex float *texcoords; // 2 components per vertex float *normals; // 3 components per vertex unsigned char *colors; + unsigned int vaoId; + unsigned int vboId[4]; } VertexData; + // Shader type + typedef struct Shader { + unsigned int id; // Shader program id + + // Variable attributes + unsigned int vertexLoc; // Vertex attribute location point (vertex shader) + unsigned int texcoordLoc; // Texcoord attribute location point (vertex shader) + unsigned int normalLoc; // Normal attribute location point (vertex shader) + unsigned int colorLoc; // Color attibute location point (vertex shader) + + // Uniforms + unsigned int projectionLoc; // Projection matrix uniform location point (vertex shader) + unsigned int modelviewLoc; // ModeView matrix uniform location point (vertex shader) + unsigned int textureLoc; // Texture uniform location point (fragment shader) + unsigned int tintColorLoc; // Color uniform location point (fragment shader) + } Shader; + + // 3d Model type typedef struct Model { VertexData mesh; - unsigned int vaoId; - unsigned int vboId[4]; - unsigned int textureId; - //Matrix transform; + Matrix transform; + Texture2D texture; + Shader shader; } Model; + + // Texture2D type + typedef struct Texture2D { + unsigned int id; // Texture id + int width; + int height; + } Texture2D; #endif #ifdef __cplusplus @@ -145,11 +193,13 @@ void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) void rlEnableTexture(unsigned int id); // Enable texture usage void rlDisableTexture(void); // Disable texture usage void rlDeleteTextures(unsigned int id); // Delete OpenGL texture from GPU +void rlDeleteShader(unsigned int id); // Delete OpenGL shader program from GPU void rlDeleteVertexArrays(unsigned int id); // Unload vertex data (VAO) from GPU memory void rlDeleteBuffers(unsigned int id); // Unload vertex data (VBO) from GPU memory void rlClearColor(byte r, byte g, byte b, byte a); // Clear color buffer with color void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) int rlGetVersion(void); // Returns current OpenGL version +void rlEnableFBO(void); // Enable rendering to postprocessing FBO //------------------------------------------------------------------------------------ // Functions Declaration - rlgl functionality @@ -159,11 +209,20 @@ void rlglClose(void); // De-init rlgl void rlglDraw(void); // Draw VAO/VBO void rlglInitGraphics(int offsetX, int offsetY, int width, int height); // Initialize Graphics (OpenGL stuff) -unsigned int rlglLoadTexture(unsigned char *data, int width, int height, bool genMipmaps); // Load in GPU OpenGL texture -unsigned int rlglLoadCompressedTexture(unsigned char *data, int width, int height, int mipmapCount, int format); +unsigned int rlglLoadTexture(void *data, int width, int height, int textureFormat, int mipmapCount, bool genMipmaps); // Load in GPU OpenGL texture +Shader rlglLoadShader(char *vsFileName, char *fsFileName); // Load a shader (vertex shader + fragment shader) from files +unsigned int rlglLoadShaderFromText(char *vShaderStr, char *fShaderStr); // Load a shader from text data +void rlglInitPostpro(void); // Initialize postprocessing system +void rlglDrawPostpro(void); // Draw with postprocessing shader +void rlglSetPostproShader(Shader shader); // Set postprocessing shader +void rlglSetModelShader(Model *model, Shader shader); // Set shader for a model +void rlglSetCustomShader(Shader shader); // Set custom shader to be used on batch draw +void rlglSetDefaultShader(void); // Set default shader to be used on batch draw Model rlglLoadModel(VertexData mesh); // Upload vertex data into GPU and provided VAO/VBO ids -void rlglDrawModel(Model model, Vector3 position, Vector3 rotation, Vector3 scale, Color color, bool wires); +void rlglDrawModel(Model model, Vector3 position, float rotationAngle, Vector3 rotationAxis, Vector3 scale, Color color, bool wires); + +Vector3 rlglUnproject(Vector3 source, Matrix proj, Matrix view); // Get world coordinates from screen coordinates byte *rlglReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) diff --git a/src/shapes.c b/src/shapes.c index d872eacf..6e6c9dd7 100644 --- a/src/shapes.c +++ b/src/shapes.c @@ -98,7 +98,7 @@ void DrawLineV(Vector2 startPos, Vector2 endPos, Color color) // Draw a color-filled circle void DrawCircle(int centerX, int centerY, float radius, Color color) { - DrawPoly((Vector2){centerX, centerY}, 360, radius, 0, color); + DrawPoly((Vector2){ centerX, centerY }, 36, radius, 0, color); } // Draw a gradient-filled circle @@ -106,14 +106,14 @@ void DrawCircle(int centerX, int centerY, float radius, Color color) void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2) { rlBegin(RL_TRIANGLES); - for (int i=0; i < 360; i += 2) + for (int i = 0; i < 360; i += 10) { rlColor4ub(color1.r, color1.g, color1.b, color1.a); rlVertex2i(centerX, centerY); rlColor4ub(color2.r, color2.g, color2.b, color2.a); - rlVertex2f(centerX + sin(DEG2RAD*i) * radius, centerY + cos(DEG2RAD*i) * radius); + rlVertex2f(centerX + sin(DEG2RAD*i)*radius, centerY + cos(DEG2RAD*i)*radius); rlColor4ub(color2.r, color2.g, color2.b, color2.a); - rlVertex2f(centerX + sin(DEG2RAD*(i+2)) * radius, centerY + cos(DEG2RAD*(i+2)) * radius); + rlVertex2f(centerX + sin(DEG2RAD*(i + 10)) * radius, centerY + cos(DEG2RAD*(i + 10))*radius); } rlEnd(); } @@ -122,12 +122,12 @@ void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Co void DrawCircleV(Vector2 center, float radius, Color color) { rlBegin(RL_TRIANGLES); - for (int i=0; i < 360; i += 2) + for (int i = 0; i < 360; i += 10) { rlColor4ub(color.r, color.g, color.b, color.a); rlVertex2i(center.x, center.y); - rlVertex2f(center.x + sin(DEG2RAD*i) * radius, center.y + cos(DEG2RAD*i) * radius); - rlVertex2f(center.x + sin(DEG2RAD*(i+2)) * radius, center.y + cos(DEG2RAD*(i+2)) * radius); + rlVertex2f(center.x + sin(DEG2RAD*i)*radius, center.y + cos(DEG2RAD*i)*radius); + rlVertex2f(center.x + sin(DEG2RAD*(i + 10)) * radius, center.y + cos(DEG2RAD*(i + 10)) * radius); } rlEnd(); } @@ -139,10 +139,10 @@ void DrawCircleLines(int centerX, int centerY, float radius, Color color) rlColor4ub(color.r, color.g, color.b, color.a); // NOTE: Circle outline is drawn pixel by pixel every degree (0 to 360) - for (int i=0; i < 360; i++) + for (int i = 0; i < 360; i += 10) { - rlVertex2f(centerX + sin(DEG2RAD*i) * radius, centerY + cos(DEG2RAD*i) * radius); - rlVertex2f(centerX + sin(DEG2RAD*(i+1)) * radius, centerY + cos(DEG2RAD*(i+1)) * radius); + rlVertex2f(centerX + sin(DEG2RAD*i)*radius, centerY + cos(DEG2RAD*i)*radius); + rlVertex2f(centerX + sin(DEG2RAD*(i + 10)) * radius, centerY + cos(DEG2RAD*(i + 10))*radius); } rlEnd(); } @@ -201,7 +201,7 @@ void DrawRectangleV(Vector2 position, Vector2 size, Color color) rlBegin(RL_QUADS); rlColor4ub(color.r, color.g, color.b, color.a); - rlNormal3f(0.0f, 0.0f, 1.0f); // Normal Pointing Towards Viewer + rlNormal3f(0.0f, 0.0f, 1.0f); rlTexCoord2f(0.0f, 0.0f); rlVertex2f(position.x, position.y); @@ -275,13 +275,13 @@ void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color col rlRotatef(rotation, 0, 0, 1); rlBegin(RL_TRIANGLES); - for (int i=0; i < 360; i += 360/sides) + for (int i = 0; i < 360; i += 360/sides) { rlColor4ub(color.r, color.g, color.b, color.a); rlVertex2i(0, 0); - rlVertex2f(sin(DEG2RAD*i) * radius, cos(DEG2RAD*i) * radius); - rlVertex2f(sin(DEG2RAD*(i+360/sides)) * radius, cos(DEG2RAD*(i+360/sides)) * radius); + rlVertex2f(sin(DEG2RAD*i)*radius, cos(DEG2RAD*i)*radius); + rlVertex2f(sin(DEG2RAD*(i + 360/sides))*radius, cos(DEG2RAD*(i + 360/sides))*radius); } rlEnd(); rlPopMatrix(); @@ -299,8 +299,8 @@ void DrawPolyEx(Vector2 *points, int numPoints, Color color) for (int i = 0; i < numPoints - 2; i++) { rlVertex2f(points[i].x, points[i].y); - rlVertex2f(points[i+1].x, points[i+1].y); - rlVertex2f(points[i+2].x, points[i+2].y); + rlVertex2f(points[i + 1].x, points[i + 1].y); + rlVertex2f(points[i + 2].x, points[i + 2].y); } rlEnd(); } @@ -318,7 +318,7 @@ void DrawPolyExLines(Vector2 *points, int numPoints, Color color) for (int i = 0; i < numPoints - 1; i++) { rlVertex2f(points[i].x, points[i].y); - rlVertex2f(points[i+1].x, points[i+1].y); + rlVertex2f(points[i + 1].x, points[i + 1].y); } rlEnd(); } @@ -367,10 +367,10 @@ bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2) { bool collision = false; - int dx = abs((rec1.x + rec1.width / 2) - (rec2.x + rec2.width / 2)); - int dy = abs((rec1.y + rec1.height / 2) - (rec2.y + rec2.height / 2)); + int dx = abs((rec1.x + rec1.width/2) - (rec2.x + rec2.width/2)); + int dy = abs((rec1.y + rec1.height/2) - (rec2.y + rec2.height/2)); - if ((dx <= (rec1.width / 2 + rec2.width / 2)) && ((dy <= (rec1.height / 2 + rec2.height / 2)))) collision = true; + if ((dx <= (rec1.width/2 + rec2.width/2)) && ((dy <= (rec1.height/2 + rec2.height/2)))) collision = true; return collision; } @@ -395,10 +395,10 @@ bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec) { bool collision = false; - float dx = abs((rec.x + rec.width / 2) - center.x); - float dy = abs((rec.y + rec.height / 2) - center.y); + float dx = fabs((rec.x + rec.width/2) - center.x); + float dy = fabs((rec.y + rec.height/2) - center.y); - if ((dx <= (rec.width / 2 + radius)) && (dy <= (rec.height / 2 + radius))) collision = true; + if ((dx <= (rec.width/2 + radius)) && (dy <= (rec.height/2 + radius))) collision = true; return collision; } diff --git a/src/simple150.frag b/src/simple150.frag deleted file mode 100644 index 74a727dd..00000000 --- a/src/simple150.frag +++ /dev/null @@ -1,14 +0,0 @@ -#version 150 - -uniform sampler2D texture0; - -in vec2 fragTexCoord; -in vec4 fragColor; - -out vec4 pixelColor; - -void main() -{ - // Output pixel color - pixelColor = texture(texture0, fragTexCoord) * fragColor; -}
\ No newline at end of file diff --git a/src/simple150.vert b/src/simple150.vert deleted file mode 100644 index 8c23b731..00000000 --- a/src/simple150.vert +++ /dev/null @@ -1,21 +0,0 @@ -#version 150 - -uniform mat4 projectionMatrix; -uniform mat4 modelviewMatrix; - -in vec3 vertexPosition; -in vec2 vertexTexCoord; -in vec4 vertexColor; - -out vec2 fragTexCoord; -out vec4 fragColor; - -void main() -{ - // Pass some variables to the fragment shader - fragTexCoord = vertexTexCoord; - fragColor = vertexColor; - - // Apply all matrix transformations to vertex - gl_Position = projectionMatrix * modelviewMatrix * vec4(vertexPosition, 1.0); -}
\ No newline at end of file diff --git a/src/stb_image.h b/src/stb_image.h index 39cbb7ad..1249c303 100644 --- a/src/stb_image.h +++ b/src/stb_image.h @@ -1,4 +1,4 @@ -/* stb_image - v2.00b - public domain image loader - http://nothings.org/stb_image.h +/* stb_image - v2.05 - public domain image loader - http://nothings.org/stb_image.h no warranty implied; use at your own risk Do this: @@ -143,6 +143,13 @@ Latest revision history: + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) additional corruption checking + stbi_set_flip_vertically_on_load + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD progressive JPEG @@ -154,8 +161,6 @@ 1.47 (2014-12-14) 1/2/4-bit PNG support (both grayscale and paletted) optimize PNG fix bug in interlaced PNG with user-specified channel count - 1.46 (2014-08-26) fix broken tRNS chunk in non-paletted PNG - 1.45 (2014-08-16) workaround MSVC-ARM internal compiler error by wrapping malloc See end of file for full revision history. @@ -178,7 +183,7 @@ James "moose2000" Brown (iPhone PNG) Roy Eltham Ben "Disch" Wenger (io callbacks) Luke Graham Omar Cornut (1/2/4-bit PNG) Thomas Ruf - John Bartholomew + Nicolas Guillemot (vertical flip) John Bartholomew Ken Hamada Optimizations & bugfixes Cort Stratton Fabian "ryg" Giesen Blazej Dariusz Roszkowski @@ -191,6 +196,12 @@ Ronny Chevalier Michal Cichon Tero Hanninen + Sergio Gonzalez + Cass Everitt + Engin Manap + Martins Mozeiko + Joseph Thomson + Phil Jordan License: This software is in the public domain. Where that dedication is not @@ -371,6 +382,7 @@ License: // and only if iPhone convert-to-rgb processing is on). // + #define STBI_NO_HDR // RaySan: not required by raylib #define STBI_NO_SIMD // RaySan: issues when compiling with GCC 4.7.2 @@ -489,6 +501,8 @@ STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultipl // or just pass them through "as-is" STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); // ZLIB client - used by PNG, available for other purposes @@ -626,7 +640,38 @@ typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; #define STBI_FREE(p) free(p) #endif -#if !defined(STBI_NO_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(__i386) || defined(_M_IX86)) +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && defined(STBI__X86_TARGET) #define STBI_SSE2 #include <emmintrin.h> @@ -879,7 +924,14 @@ static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); #endif -static unsigned char *stbi_load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) { #ifndef STBI_NO_JPEG if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); @@ -919,6 +971,53 @@ static unsigned char *stbi_load_main(stbi__context *s, int *x, int *y, int *comp return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); } +static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result = stbi__load_main(s, x, y, comp, req_comp); + + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + stbi_uc temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } + + return result; +} + +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } +} + + #ifndef STBI_NO_STDIO static FILE *stbi__fopen(char const *filename, char const *mode) @@ -949,7 +1048,7 @@ STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req unsigned char *result; stbi__context s; stbi__start_file(&s,f); - result = stbi_load_main(&s,x,y,comp,req_comp); + result = stbi__load_flip(&s,x,y,comp,req_comp); if (result) { // need to 'unget' all the characters in the IO buffer fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); @@ -962,25 +1061,29 @@ STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, i { stbi__context s; stbi__start_mem(&s,buffer,len); - return stbi_load_main(&s,x,y,comp,req_comp); + return stbi__load_flip(&s,x,y,comp,req_comp); } STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi_load_main(&s,x,y,comp,req_comp); + return stbi__load_flip(&s,x,y,comp,req_comp); } #ifndef STBI_NO_LINEAR -static float *stbi_loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) { unsigned char *data; #ifndef STBI_NO_HDR - if (stbi__hdr_test(s)) - return stbi__hdr_load(s,x,y,comp,req_comp); + if (stbi__hdr_test(s)) { + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } #endif - data = stbi_load_main(s, x, y, comp, req_comp); + data = stbi__load_flip(s, x, y, comp, req_comp); if (data) return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); @@ -990,14 +1093,14 @@ STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, in { stbi__context s; stbi__start_mem(&s,buffer,len); - return stbi_loadf_main(&s,x,y,comp,req_comp); + return stbi__loadf_main(&s,x,y,comp,req_comp); } STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) { stbi__context s; stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); - return stbi_loadf_main(&s,x,y,comp,req_comp); + return stbi__loadf_main(&s,x,y,comp,req_comp); } #ifndef STBI_NO_STDIO @@ -1015,7 +1118,7 @@ STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_ { stbi__context s; stbi__start_file(&s,f); - return stbi_loadf_main(&s,x,y,comp,req_comp); + return stbi__loadf_main(&s,x,y,comp,req_comp); } #endif // !STBI_NO_STDIO @@ -1138,6 +1241,10 @@ stbi_inline static int stbi__at_eof(stbi__context *s) static void stbi__skip(stbi__context *s, int n) { + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } if (s->io.read) { int blen = (int) (s->img_buffer_end - s->img_buffer); if (blen < n) { @@ -1546,6 +1653,7 @@ stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n) sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); j->code_buffer = k & ~stbi__bmask[n]; k &= stbi__bmask[n]; j->code_bits -= n; @@ -1730,15 +1838,12 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__ short *p = &data[stbi__jpeg_dezigzag[k]]; if (*p != 0) if (stbi__jpeg_get_bit(j)) - { - if ((*p & bit)==0) - { + if ((*p & bit)==0) { if (*p > 0) *p += bit; else *p -= bit; } - } } } else { k = j->spec_start; @@ -1754,8 +1859,11 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__ if (r) j->eob_run += stbi__jpeg_get_bits(j, r); r = 64; // force end of block - } else - r = 16; // r=15 is the code for 16 0s + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } } else { if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); // sign bit @@ -1767,27 +1875,21 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__ // advance by r while (k <= j->spec_end) { - short *p = &data[stbi__jpeg_dezigzag[k]]; + short *p = &data[stbi__jpeg_dezigzag[k++]]; if (*p != 0) { if (stbi__jpeg_get_bit(j)) - { - if ((*p & bit)==0) - { + if ((*p & bit)==0) { if (*p > 0) *p += bit; else *p -= bit; } - } - ++k; } else { if (r == 0) { - if (s) - data[stbi__jpeg_dezigzag[k++]] = s; + *p = (short) s; break; } --r; - ++k; } } } while (k <= j->spec_end); @@ -2207,7 +2309,7 @@ static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) // pass 1 dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 dct_trn16(row2, row3); - dct_trn16(row4, row5); + dct_trn16(row4, row5); dct_trn16(row6, row7); // pass 2 @@ -2434,7 +2536,6 @@ static int stbi__parse_entropy_coded_data(stbi__jpeg *z) for (x=0; x < z->img_comp[n].h; ++x) { int x2 = (i*z->img_comp[n].h + x); int y2 = (j*z->img_comp[n].v + y); - //int ha = z->img_comp[n].ha; // RaySan: Unused, commented to avoid warning short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) return 0; @@ -2701,6 +2802,10 @@ static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) static int stbi__decode_jpeg_image(stbi__jpeg *j) { int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } j->restart_interval = 0; if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; m = stbi__get_marker(j); @@ -3013,7 +3118,7 @@ static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc cons __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); - __m128i y_bias = _mm_set1_epi8((char) 128); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); __m128i xw = _mm_set1_epi16(255); // alpha channel for (; i+7 < count; i += 8) { @@ -3380,7 +3485,8 @@ static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) ++sizes[sizelist[i]]; sizes[0] = 0; for (i=1; i < 16; ++i) - STBI_ASSERT(sizes[i] <= (1 << i)); + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); code = 0; for (i=1; i < 16; ++i) { next_code[i] = code; @@ -3388,7 +3494,7 @@ static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) z->firstsymbol[i] = (stbi__uint16) k; code = (code + sizes[i]); if (sizes[i]) - if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt JPEG"); + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); z->maxcode[i] = code << (16-i); // preshift for inner loop code <<= 1; k += sizes[i]; @@ -3557,9 +3663,9 @@ static int stbi__parse_huffman_block(stbi__zbuf *a) p = (stbi_uc *) (zout - dist); if (dist == 1) { // run of one byte; common in images. stbi_uc v = *p; - do *zout++ = v; while (--len); + if (len) { do *zout++ = v; while (--len); } } else { - do *zout++ = *p++; while (--len); + if (len) { do *zout++ = *p++; while (--len); } } } } @@ -3587,7 +3693,7 @@ static int stbi__compute_huffman_codes(stbi__zbuf *a) n = 0; while (n < hlit + hdist) { int c = stbi__zhuffman_decode(a, &z_codelength); - STBI_ASSERT(c >= 0 && c < 19); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); if (c < 16) lencodes[n++] = (stbi_uc) c; else if (c == 16) { @@ -4019,7 +4125,7 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r cur[i*2+0] = cur[i]; } } else { - assert(img_n == 3); + STBI_ASSERT(img_n == 3); for (i=x-1; i >= 0; --i) { cur[i*4+3] = 255; cur[i*4+2] = cur[i*3+2]; @@ -4284,6 +4390,7 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) if (first) return stbi__err("first not IHDR", "Corrupt PNG"); if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; if (ioff + c.length > idata_limit) { stbi_uc *p; if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; @@ -4643,7 +4750,7 @@ static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int } } else { for (i=0; i < (int) s->img_x; ++i) { - stbi__uint32 v = (stbi__uint32) (bpp == 16 ? stbi__get16le(s) : stbi__get32le(s)); + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); int a; out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); @@ -4800,7 +4907,7 @@ static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int *y = tga_height; if (comp) *comp = tga_comp; - tga_data = (unsigned char*)stbi__malloc( tga_width * tga_height * tga_comp ); + tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp ); if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); // skip to the data's starting position (offset usually = 0) @@ -5461,6 +5568,7 @@ static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) stbi__gif_lzw *p; lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; clear = 1 << lzw_cs; first = 1; codesize = lzw_cs + 1; @@ -6130,7 +6238,7 @@ static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) #ifndef STBI_NO_PSD if (stbi__psd_info(s, x, y, comp)) return 1; #endif - + #ifndef STBI_NO_PIC if (stbi__pic_info(s, x, y, comp)) return 1; #endif @@ -6192,6 +6300,13 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int /* revision history: + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) progressive JPEG (stb) @@ -6276,7 +6391,7 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int 1.21 fix use of 'stbi_uc' in header (reported by jon blow) 1.20 added support for Softimage PIC, by Tom Seddon 1.19 bug in interlaced PNG corruption check (found by ryg) - 1.18 2008-08-02 + 1.18 (2008-08-02) fix a threading bug (local mutable static) 1.17 support interlaced PNG 1.16 major bugfix - stbi__convert_format converted one too many pixels @@ -6321,5 +6436,6 @@ STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments 0.51 obey req_comp requests, 1-component jpegs return as 1-component, on 'test' only check type, not whether we support this variant - 0.50 first released version + 0.50 (2006-11-19) + first released version */ diff --git a/src/stb_image_write.h b/src/stb_image_write.h index da3f7e22..10489707 100644 --- a/src/stb_image_write.h +++ b/src/stb_image_write.h @@ -1,16 +1,15 @@ -/* stb_image_write - v0.95 - public domain - http://nothings.org/stb/stb_image_write.h +/* stb_image_write - v0.98 - public domain - http://nothings.org/stb/stb_image_write.h writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010 no warranty implied; use at your own risk -Before including, + Before #including, - #define STB_IMAGE_WRITE_IMPLEMENTATION + #define STB_IMAGE_WRITE_IMPLEMENTATION -in the file that you want to have the implementation. - -Will probably not work correctly with strict-aliasing optimizations. + in the file that you want to have the implementation. + Will probably not work correctly with strict-aliasing optimizations. ABOUT: @@ -22,16 +21,24 @@ ABOUT: for source code compactness and simplicitly, not optimal image file size or run-time performance. +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can define STBIW_MEMMOVE() to replace memmove() + USAGE: - There are three functions, one for each image file format: + There are four functions, one for each image file format: int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const void *data); Each function returns 0 on failure and non-0 on success. - + The functions create an image file defined by the parameters. The image is a rectangle of pixels stored from left-to-right, top-to-bottom. Each pixel contains 'comp' channels of data stored interleaved with 8-bits @@ -44,13 +51,30 @@ USAGE: PNG creates output files with the same number of components as the input. The BMP format expands Y to RGB in the file format and does not output alpha. - + PNG supports writing rectangles of data even when the bytes storing rows of data are not consecutive in memory (e.g. sub-rectangles of a larger image), by supplying the stride between the beginning of adjacent rows. The other formats do not. (Thus you cannot write a native-format BMP through the BMP writer, both because it is in BGR order and because it may have padding at the end of the line.) + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + +CREDITS: + + PNG/BMP/TGA + Sean Barrett + HDR + Baldur Karlsson + TGA monochrome: + Jean-Sebastien Guay + misc enhancements: + Tim Kelsey + bugfixes: + github:Chribba */ #ifndef INCLUDE_STB_IMAGE_WRITE_H @@ -60,9 +84,10 @@ USAGE: extern "C" { #endif -extern int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); -extern int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); -extern int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +extern int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +extern int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +extern int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +extern int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); #ifdef __cplusplus } @@ -76,7 +101,30 @@ extern int stbi_write_tga(char const *filename, int w, int h, int comp, const vo #include <stdlib.h> #include <stdio.h> #include <string.h> +#include <math.h> + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && defined(STBIW_REALLOC) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,sz) realloc(p,sz) +#define STBIW_FREE(p) free(p) +#endif +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT #include <assert.h> +#define STBIW_ASSERT(x) assert(x) +#endif typedef unsigned int stbiw_uint32; typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; @@ -95,7 +143,7 @@ static void writefv(FILE *f, const char *fmt, va_list v) b[2]=(unsigned char)(x>>16); b[3]=(unsigned char)(x>>24); fwrite(b,4,1,f); break; } default: - assert(0); + STBIW_ASSERT(0); return; } } @@ -108,7 +156,7 @@ static void write3(FILE *f, unsigned char a, unsigned char b, unsigned char c) fwrite(arr, 3, 1, f); } -static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad) +static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) { unsigned char bg[3] = { 255, 0, 255}, px[3]; stbiw_uint32 zero = 0; @@ -117,7 +165,7 @@ static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, if (y <= 0) return; - if (vdir < 0) + if (vdir < 0) j_end = -1, j = y-1; else j_end = y, j = 0; @@ -128,8 +176,12 @@ static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, if (write_alpha < 0) fwrite(&d[comp-1], 1, 1, f); switch (comp) { - case 1: - case 2: fwrite(d, 1, 1, f); + case 1: fwrite(d, 1, 1, f); + break; + case 2: if (expand_mono) + write3(f, d[0],d[0],d[0]); // monochrome bmp + else + fwrite(d, 1, 1, f); // monochrome TGA break; case 4: if (!write_alpha) { @@ -151,7 +203,7 @@ static void write_pixels(FILE *f, int rgb_dir, int vdir, int x, int y, int comp, } } -static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, void *data, int alpha, int pad, const char *fmt, ...) +static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) { FILE *f; if (y < 0 || x < 0) return 0; @@ -161,7 +213,7 @@ static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, in va_start(v, fmt); writefv(f, fmt, v); va_end(v); - write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad); + write_pixels(f,rgb_dir,vdir,x,y,comp,data,alpha,pad,expand_mono); fclose(f); } return f != NULL; @@ -170,7 +222,7 @@ static int outfile(char const *filename, int rgb_dir, int vdir, int x, int y, in int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) { int pad = (-x*3) & 3; - return outfile(filename,-1,-1,x,y,comp,(void *) data,0,pad, + return outfile(filename,-1,-1,x,y,comp,1,(void *) data,0,pad, "11 4 22 4" "4 44 22 444444", 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header @@ -181,10 +233,159 @@ int stbi_write_tga(char const *filename, int x, int y, int comp, const void *dat int has_alpha = (comp == 2 || comp == 4); int colorbytes = has_alpha ? comp-1 : comp; int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 - return outfile(filename, -1,-1, x, y, comp, (void *) data, has_alpha, 0, + return outfile(filename, -1,-1, x, y, comp, 0, (void *) data, has_alpha, 0, "111 221 2222 11", 0,0,format, 0,0,0, 0,0,x,y, (colorbytes+has_alpha)*8, has_alpha*8); } +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +void stbiw__write_run_data(FILE *f, int length, unsigned char databyte) +{ + unsigned char lengthbyte = (unsigned char) (length+128); + STBIW_ASSERT(length+128 <= 255); + fwrite(&lengthbyte, 1, 1, f); + fwrite(&databyte, 1, 1, f); +} + +void stbiw__write_dump_data(FILE *f, int length, unsigned char *data) +{ + unsigned char lengthbyte = (unsigned char )(length & 0xff); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + fwrite(&lengthbyte, 1, 1, f); + fwrite(data, length, 1, f); +} + +void stbiw__write_hdr_scanline(FILE *f, int width, int comp, unsigned char *scratch, const float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (comp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*comp + 2]; + linear[1] = scanline[x*comp + 1]; + linear[0] = scanline[x*comp + 0]; + break; + case 2: /* fallthrough */ + case 1: linear[0] = linear[1] = linear[2] = scanline[x*comp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + fwrite(rgbe, 4, 1, f); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(comp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*comp + 2]; + linear[1] = scanline[x*comp + 1]; + linear[0] = scanline[x*comp + 0]; + break; + case 2: /* fallthrough */ + case 1: linear[0] = linear[1] = linear[2] = scanline[x*comp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + fwrite(scanlineheader, 4, 1, f); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(f, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(f, len, comp[x]); + x += len; + } + } + } + } + } +} + +int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + int i; + FILE *f; + if (y <= 0 || x <= 0 || data == NULL) return 0; + f = fopen(filename, "wb"); + if (f) { + /* Each component is stored separately. Allocate scratch space for full output scanline. */ + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + fprintf(f, "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n" ); + fprintf(f, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n" , y, x); + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(f, x, comp, scratch, data + comp*i*x); + STBIW_FREE(scratch); + fclose(f); + } + return f != NULL; +} + +///////////////////////////////////////////////////////// +// PNG + // stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() #define stbiw__sbraw(a) ((int *) (a) - 2) #define stbiw__sbm(a) stbiw__sbraw(a)[0] @@ -196,13 +397,13 @@ int stbi_write_tga(char const *filename, int x, int y, int comp, const void *dat #define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) #define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) -#define stbiw__sbfree(a) ((a) ? free(stbiw__sbraw(a)),0 : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) { int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; - void *p = realloc(*arr ? stbiw__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2); - assert(p); + void *p = STBIW_REALLOC(*arr ? stbiw__sbraw(*arr) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); if (p) { if (!*arr) ((int *) p)[1] = 0; *arr = (void *) ((int *) p + 2); @@ -287,7 +488,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l i=0; while (i < data_len-3) { - // hash next 3 bytes of data to be compressed + // hash next 3 bytes of data to be compressed int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; unsigned char *bestloc = 0; unsigned char **hlist = hash_table[h]; @@ -300,7 +501,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l } // when hash table entry is too long, delete half the entries if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { - memcpy(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); stbiw__sbn(hash_table[h]) = quality; } stbiw__sbpush(hash_table[h],data+i); @@ -323,7 +524,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l if (bestloc) { int d = (int) (data+i - bestloc); // distance back - assert(d <= 32767 && best <= 258); + STBIW_ASSERT(d <= 32767 && best <= 258); for (j=0; best > lengthc[j+1]-1; ++j); stbiw__zlib_huff(j+257); if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); @@ -364,7 +565,7 @@ unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_l } *out_len = stbiw__sbn(out); // make returned pointer freeable - memmove(stbiw__sbraw(out), out, *out_len); + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); return (unsigned char *) stbiw__sbraw(out); } @@ -411,8 +612,8 @@ unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, in if (stride_bytes == 0) stride_bytes = x * n; - filt = (unsigned char *) malloc((x*n+1) * y); if (!filt) return 0; - line_buffer = (signed char *) malloc(x * n); if (!line_buffer) { free(filt); return 0; } + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } for (j=0; j < y; ++j) { static int mapping[] = { 0,1,2,3,4 }; static int firstmap[] = { 0,1,0,5,6 }; @@ -451,20 +652,20 @@ unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, in } // when we get here, best contains the filter type, and line_buffer contains the data filt[j*(x*n+1)] = (unsigned char) best; - memcpy(filt+j*(x*n+1)+1, line_buffer, x*n); + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); } - free(line_buffer); + STBIW_FREE(line_buffer); zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory - free(filt); + STBIW_FREE(filt); if (!zlib) return 0; // each tag requires 12 bytes of overhead - out = (unsigned char *) malloc(8 + 12+13 + 12+zlen + 12); + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); if (!out) return 0; *out_len = 8 + 12+13 + 12+zlen + 12; o=out; - memcpy(o,sig,8); o+= 8; + STBIW_MEMMOVE(o,sig,8); o+= 8; stbiw__wp32(o, 13); // header length stbiw__wptag(o, "IHDR"); stbiw__wp32(o, x); @@ -478,14 +679,16 @@ unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, in stbiw__wp32(o, zlen); stbiw__wptag(o, "IDAT"); - memcpy(o, zlib, zlen); o += zlen; free(zlib); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); stbiw__wpcrc(&o, zlen); stbiw__wp32(o,0); stbiw__wptag(o, "IEND"); stbiw__wpcrc(&o,0); - assert(o == out + *out_len); + STBIW_ASSERT(o == out + *out_len); return out; } @@ -497,16 +700,22 @@ int stbi_write_png(char const *filename, int x, int y, int comp, const void *dat unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); if (!png) return 0; f = fopen(filename, "wb"); - if (!f) { free(png); return 0; } + if (!f) { STBIW_FREE(png); return 0; } fwrite(png, 1, len, f); fclose(f); - free(png); + STBIW_FREE(png); return 1; } #endif // STB_IMAGE_WRITE_IMPLEMENTATION /* Revision history - + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP 0.95 (2014-08-17) add monochrome TGA output 0.94 (2014-05-31) diff --git a/src/stb_rect_pack.h b/src/stb_rect_pack.h new file mode 100644 index 00000000..63a5b159 --- /dev/null +++ b/src/stb_rect_pack.h @@ -0,0 +1,560 @@ +// stb_rect_pack.h - v0.06 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// Bugfixes / warning fixes +// [your name could be here] +// +// Version history: +// +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#ifndef STB_INCLUDE_STB_RECT_PACK_H +#define STB_INCLUDE_STB_RECT_PACK_H + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +#ifdef STBRP_LARGE_RECTS +typedef int stbrp_coord; +#else +typedef unsigned short stbrp_coord; +#endif + +STBRP_DEF void stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight, +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include <stdlib.h> +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include <assert.h> +#define STBRP_ASSERT assert +#endif + +enum +{ + STBRP__INIT_skyline = 1, +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; +#ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(width <= 0xffff && height <= 0xffff); +#endif + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; +#ifdef STBRP_LARGE_RECTS + context->extra[1].y = (1<<30); +#else + context->extra[1].y = 65535; +#endif + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visted + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height < c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + stbrp_node *L1 = NULL, *L2 = NULL; + int count=0; + cur = context->active_head; + while (cur) { + L1 = cur; + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + L2 = cur; + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int rect_height_compare(const void *a, const void *b) +{ + stbrp_rect *p = (stbrp_rect *) a; + stbrp_rect *q = (stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int rect_width_compare(const void *a, const void *b) +{ + stbrp_rect *p = (stbrp_rect *) a; + stbrp_rect *q = (stbrp_rect *) b; + if (p->w > q->w) + return -1; + if (p->w < q->w) + return 1; + return (p->h > q->h) ? -1 : (p->h < q->h); +} + +static int rect_original_order(const void *a, const void *b) +{ + stbrp_rect *p = (stbrp_rect *) a; + stbrp_rect *q = (stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +#ifdef STBRP_LARGE_RECTS +#define STBRP__MAXVAL 0xffffffff +#else +#define STBRP__MAXVAL 0xffff +#endif + +STBRP_DEF void stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + #ifndef STBRP_LARGE_RECTS + STBRP_ASSERT(rects[i].w <= 0xffff && rects[i].h <= 0xffff); + #endif + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags + for (i=0; i < num_rects; ++i) + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); +} +#endif diff --git a/src/stb_truetype.h b/src/stb_truetype.h new file mode 100644 index 00000000..ae60212e --- /dev/null +++ b/src/stb_truetype.h @@ -0,0 +1,2632 @@ +// stb_truetype.h - v1.05 - public domain +// authored from 2009-2014 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket (with fix) +// Cass Everitt +// stoiko (Haemimont Games) +// Brian Hook +// Walter van Niftrik +// David Gow +// David Given +// Ivan-Assen Ivanov +// Anthony Pesch +// Johan Duparc +// Hou Qiming +// Fabian "ryg" Giesen +// Martins Mozeiko +// Cap Petschulat +// Omar Cornut +// github:aloucks +// Peter LaValle +// +// Misc other: +// Ryan Gordon +// +// VERSION HISTORY +// +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// +// LICENSE +// +// This software is in the public domain. Where that dedication is not +// recognized, you are granted a perpetual, irrevokable license to copy +// and modify this file as you see fit. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversample() -- for improved quality on small fonts +// stbtt_PackFontRanges() +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- use for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetCodepointKernAdvance() +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since they different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// ADVANCED USAGE +// +// Quality: +// +// - Use the functions with Subpixel at the end to allow your characters +// to have subpixel positioning. Since the font is anti-aliased, not +// hinted, this is very import for quality. (This is not possible with +// baked fonts.) +// +// - Kerning is now supported, and if you're supporting subpixel rendering +// then kerning is worth using to give your text a polished look. +// +// Performance: +// +// - Convert Unicode codepoints to glyph indexes and operate on the glyphs; +// if you don't do this, stb_truetype is forced to do the conversion on +// every call. +// +// - There are a lot of memory allocations. We should modify it to take +// a temp buffer and allocate from the temp buffer (without freeing), +// should help performance a lot. +// +// NOTES +// +// The system uses the raw data found in the .ttf file without changing it +// and without building auxiliary data structures. This is a bit inefficient +// on little-endian systems (the data is big-endian), but assuming you're +// caching the bitmaps or glyph shapes this shouldn't be a big deal. +// +// It appears to be very hard to programmatically determine what font a +// given file is in a general way. I provide an API for this, but I don't +// recommend it. +// +// +// SOURCE STATISTICS (based on v0.6c, 2050 LOC) +// +// Documentation & header file 520 LOC \___ 660 LOC documentation +// Sample code 140 LOC / +// Truetype parsing 620 LOC ---- 620 LOC TrueType +// Software rasterization 240 LOC \ . +// Curve tesselation 120 LOC \__ 550 LOC Bitmap creation +// Bitmap management 100 LOC / +// Baked bitmap interface 70 LOC / +// Font name matching & access 150 LOC ---- 150 +// C runtime library abstraction 60 LOC ---- 60 + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// SAMPLE PROGRAMS +//// +// +// Incomplete text-in-3d-api example, which draws quads properly aligned to be lossless +// +#if 0 +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +unsigned char ttf_buffer[1<<20]; +unsigned char temp_bitmap[512*512]; + +stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs +GLuint ftex; + +void my_stbtt_initfont(void) +{ + fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb")); + stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits! + // can free ttf_buffer at this point + glGenTextures(1, &ftex); + glBindTexture(GL_TEXTURE_2D, ftex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap); + // can free temp_bitmap at this point + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +void my_stbtt_print(float x, float y, char *text) +{ + // assume orthographic projection with units = screen pixels, origin at top left + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, ftex); + glBegin(GL_QUADS); + while (*text) { + if (*text >= 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include <stdio.h> +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype. + +#ifdef STB_TRUETYPE_IMPLEMENTATION + // #define your own (u)stbtt_int8/16/32 before including to override this + #ifndef stbtt_uint8 + typedef unsigned char stbtt_uint8; + typedef signed char stbtt_int8; + typedef unsigned short stbtt_uint16; + typedef signed short stbtt_int16; + typedef unsigned int stbtt_uint32; + typedef signed int stbtt_int32; + #endif + + typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; + typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + + // #define your own STBTT_sort() to override this to avoid qsort + #ifndef STBTT_sort + #include <stdlib.h> + #define STBTT_sort(data,num_items,item_size,compare_func) qsort(data,num_items,item_size,compare_func) + #endif + + // #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h + #ifndef STBTT_ifloor + #include <math.h> + #define STBTT_ifloor(x) ((int) floor(x)) + #define STBTT_iceil(x) ((int) ceil(x)) + #endif + + #ifndef STBTT_sqrt + #include <math.h> + #define STBTT_sqrt(x) sqrt(x) + #endif + + // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h + #ifndef STBTT_malloc + #include <stdlib.h> + #define STBTT_malloc(x,u) ((void)(u),malloc(x)) + #define STBTT_free(x,u) ((void)(u),free(x)) + #endif + + #ifndef STBTT_assert + #include <assert.h> + #define STBTT_assert(x) assert(x) + #endif + + #ifndef STBTT_strlen + #include <string.h> + #define STBTT_strlen(x) strlen(x) + #endif + + #ifndef STBTT_memcpy + #include <memory.h> + #define STBTT_memcpy memcpy + #define STBTT_memset memset + #endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// TEXTURE BAKING API +// +// If you use this API, you only have to call two functions ever. +// + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; +} stbtt_bakedchar; + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long +// if return is positive, the first unused row of the bitmap +// if return is negative, returns the negative of the number of characters that fit +// if return is 0, no characters fit and no rows were used +// This uses a very crappy packing. + +typedef struct +{ + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right +} stbtt_aligned_quad; + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier +// Call GetBakedQuad with char_index = 'character - first_char', and it +// creates the quad you need to draw and advances the current position. +// +// The coordinate system used assumes y increases downwards. +// +// Characters will extend both above and below the current position; +// see discussion of "BASELINE" above. +// +// It's inefficient; you might want to c&p it and optimize it. + + + +////////////////////////////////////////////////////////////////////////////// +// +// NEW TEXTURE BAKING API +// +// This provides options for packing multiple fonts into one atlas, not +// perfectly but better than nothing. + +typedef struct +{ + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; +} stbtt_packedchar; + +typedef struct stbtt_pack_context stbtt_pack_context; + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); +// Initializes a packing context stored in the passed-in stbtt_pack_context. +// Future calls using this context will pack characters into the bitmap passed +// in here: a 1-channel bitmap that is weight x height. stride_in_bytes is +// the distance from one row to the next (or 0 to mean they are packed tightly +// together). "padding" is // the amount of padding to leave between each +// character (normally you want '1' for bitmaps you'll use as textures with +// bilinear filtering). +// +// Returns 0 on failure, 1 on success. + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); +// Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); +// Creates character bitmaps from the font_index'th font found in fontdata (use +// font_index=0 if you don't know what that is). It creates num_chars_in_range +// bitmaps for characters with unicode values starting at first_unicode_char_in_range +// and increasing. Data for how to render them is stored in chardata_for_range; +// pass these to stbtt_GetPackedQuad to get back renderable quads. +// +// font_size is the full height of the character from ascender to descender, +// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed +// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() +// and pass that result as 'font_size': +// ..., 20 , ... // font max minus min y is 20 pixels tall +// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + +typedef struct +{ + float font_size; + int first_unicode_char_in_range; + int num_chars_in_range; + stbtt_packedchar *chardata_for_range; // output +} stbtt_pack_range; + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); +// Creates character bitmaps from multiple ranges of characters stored in +// ranges. This will usually create a better-packed bitmap than multiple +// calls to stbtt_PackFontRange. + + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); +// Oversampling a font increases the quality by allowing higher-quality subpixel +// positioning, and is especially valuable at smaller text sizes. +// +// This function sets the amount of oversampling for all following calls to +// stbtt_PackFontRange(s). The default (no oversampling) is achieved by +// h_oversample=1, v_oversample=1. The total number of pixels required is +// h_oversample*v_oversample larger than the default; for example, 2x2 +// oversampling requires 4x the storage of 1x1. For best results, render +// oversampled textures with bilinear filtering. Look at the readme in +// stb/tests/oversample for information about oversampled fonts + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + +// this is an opaque structure that you shouldn't mess with which holds +// all the context needed from PackBegin to PackEnd. +struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; +}; + +////////////////////////////////////////////////////////////////////////////// +// +// FONT LOADING +// +// + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); +// Each .ttf/.ttc file may have more than one font. Each font has a sequential +// index number starting from 0. Call this function to get the font offset for +// a given index; it returns -1 if the index is out of range. A regular .ttf +// file will only define one font and it always be at offset 0, so it will +// return '0' for index 0, and -1 for all other indices. You can just skip +// this step if you know it's that kind of font. + + +// The following structure is defined publically so you can declare one on +// the stack or as a global or etc, but you should treat it as opaque. +typedef struct stbtt_fontinfo +{ + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph +} stbtt_fontinfo; + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); +// Given an offset into the file that defines a font, this function builds +// the necessary cached info for the rest of the system. You must allocate +// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't +// need to do anything special to free it, because the contents are pure +// value data with no additional data structures. Returns 0 on failure. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER TO GLYPH-INDEX CONVERSIOn + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); +// If you're going to perform multiple operations on the same character +// and you want a speed-up, call this function with the character you're +// going to process, then use glyph-based functions instead of the +// codepoint-based functions. + + +////////////////////////////////////////////////////////////////////////////// +// +// CHARACTER PROPERTIES +// + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose "height" is 'pixels' tall. +// Height is measured as the distance from the highest ascender to the lowest +// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics +// and computing: +// scale = pixels / (ascent - descent) +// so if you prefer to measure height by the ascent only, use a similar calculation. + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); +// computes a scale factor to produce a font whose EM size is mapped to +// 'pixels' tall. This is probably what traditional APIs compute, but +// I'm not positive. + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); +// ascent is the coordinate above the baseline the font extends; descent +// is the coordinate below the baseline the font extends (i.e. it is typically negative) +// lineGap is the spacing between one row's descent and the next row's ascent... +// so you should advance the vertical position by "*ascent - *descent + *lineGap" +// these are expressed in unscaled coordinates, so you must multiply by +// the scale factor for a given size + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); +// the bounding box around all possible characters + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); +// leftSideBearing is the offset from the current horizontal position to the left edge of the character +// advanceWidth is the offset from the current horizontal position to the next horizontal position +// these are expressed in unscaled coordinates + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); +// an additional amount to add to the 'advance' value between ch1 and ch2 + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); +// Gets the bounding box of the visible part of the glyph, in unscaled coordinates + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); +// as above, but takes one or more glyph indices for greater efficiency + + +////////////////////////////////////////////////////////////////////////////// +// +// GLYPH SHAPES (you probably don't need these, but they have to go before +// the bitmaps for C declaration-order reasons) +// + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) + #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy; + unsigned char type,padding; + } stbtt_vertex; +#endif + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); +// returns non-zero if nothing is drawn for this glyph + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); +// returns # of vertices and fills *vertices with the pointer to them +// these are expressed in "unscaled" coordinates +// +// The shape is a series of countours. Each one starts with +// a STBTT_moveto, then consists of a series of mixed +// STBTT_lineto and STBTT_curveto segments. A lineto +// draws a line from previous endpoint to its x,y; a curveto +// draws a quadratic bezier from previous endpoint to +// its x,y, using cx,cy as the bezier control point. + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); +// frees the data allocated above + +////////////////////////////////////////////////////////////////////////////// +// +// BITMAP RENDERING +// + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); +// frees the bitmap allocated below + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// allocates a large-enough single-channel 8bpp bitmap and renders the +// specified character/glyph at the specified scale into it, with +// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). +// *width & *height are filled out with the width & height of the bitmap, +// which is stored left-to-right, top-to-bottom. +// +// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); +// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); +// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap +// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap +// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the +// width and height and positioning info for it first. + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); +// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel +// shift for the character + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +// get the bbox of the bitmap centered around the glyph origin; so the +// bitmap width is ix1-ix0, height is iy1-iy0, and location to place +// the bitmap top left is (leftSideBearing*scale,iy0). +// (Note that the bitmap uses y-increases-down, but the shape uses +// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); +// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel +// shift for the character + +// the following functions are equivalent to the above functions, but operate +// on glyph indices instead of Unicode codepoints (for efficiency) +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + +// @TODO: don't expose this structure +typedef struct +{ + int w,h,stride; + unsigned char *pixels; +} stbtt__bitmap; + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata); + +////////////////////////////////////////////////////////////////////////////// +// +// Finding the right font... +// +// You should really just solve this offline, keep your own tables +// of what font is what, and don't try to get it out of the .ttf file. +// That's because getting it out of the .ttf file is really hard, because +// the names in the file can appear in many possible encodings, in many +// possible languages, and e.g. if you need a case-insensitive comparison, +// the details of that depend on the encoding & language in a complex way +// (actually underspecified in truetype, but also gigantic). +// +// But you can use the provided functions in two possible ways: +// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on +// unicode-encoded names to try to find the font you want; +// you can run this before calling stbtt_InitFont() +// +// stbtt_GetFontNameString() lets you get any of the various strings +// from the file yourself and do your own comparisons on them. +// You have to have called stbtt_InitFont() first. + + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); +// returns the offset (not index) of the font that matches, or -1 if none +// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". +// if you use any other flag, use a font name like "Arial"; this checks +// the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); +// returns 1/0 whether the first string interpreted as utf8 is identical to +// the second string interpreted as big-endian utf16... useful for strings from next func + +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); +// returns the string (which may be big-endian double byte, e.g. for unicode) +// and puts the length in bytes in *length. +// +// some of the values for the IDs are below; for more see the truetype spec: +// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html +// http://www.microsoft.com/typography/otspec/name.htm + +enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 +}; + +enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 +}; + +enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D +}; + +enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 +}; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +#if defined(STB_TRUETYPE_BIGENDIAN) && !defined(ALLOW_UNALIGNED_TRUETYPE) + + #define ttUSHORT(p) (* (stbtt_uint16 *) (p)) + #define ttSHORT(p) (* (stbtt_int16 *) (p)) + #define ttULONG(p) (* (stbtt_uint32 *) (p)) + #define ttLONG(p) (* (stbtt_int32 *) (p)) + +#else + + static stbtt_uint16 ttUSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + static stbtt_int16 ttSHORT(const stbtt_uint8 *p) { return p[0]*256 + p[1]; } + static stbtt_uint32 ttULONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + static stbtt_int32 ttLONG(const stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#endif + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(const stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*14); + } + } + return -1; +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data2, int fontstart) +{ + stbtt_uint8 *data = (stbtt_uint8 *) data2; + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + if (!cmap || !info->loca || !info->head || !info->glyf || !info->hhea || !info->hmtx) + return 0; + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0,y0,x1,y1; + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + +typedef struct stbtt__active_edge +{ + int x,dx; + float ey; + struct stbtt__active_edge *next; + int valid; +} stbtt__active_edge; + +#define FIXSHIFT 10 +#define FIX (1 << FIXSHIFT) +#define FIXMASK (FIX-1) + +static stbtt__active_edge *new_active(stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) STBTT_malloc(sizeof(*z), userdata); // @TODO: make a pool of these!!! + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(e->y0 <= start_point); + if (!z) return z; + // round dx down to avoid going too far + if (dxdy < 0) + z->dx = -STBTT_ifloor(FIX * -dxdy); + else + z->dx = STBTT_ifloor(FIX * dxdy); + z->x = STBTT_ifloor(FIX * (e->x0 + dxdy * (start_point - e->y0))); + z->x -= off_x * FIX; + z->ey = e->y1; + z->next = 0; + z->valid = e->invert ? 1 : -1; + return z; +} + +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->valid; + } else { + int x1 = e->x; w += e->valid; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> FIXSHIFT; + int j = x1 >> FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((FIX - (x0 & FIXMASK)) * max_weight) >> FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & FIXMASK) * max_weight) >> FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->valid); + z->valid = 0; + STBTT_free(z, userdata); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = new_active(e, off_x, scan_y, userdata); + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + while (active) { + stbtt__active_edge *z = active; + active = active->next; + STBTT_free(z, userdata); + } + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +static int stbtt__edge_compare(const void *p, const void *q) +{ + stbtt__edge *a = (stbtt__edge *) p; + stbtt__edge *b = (stbtt__edge *) q; + + if (a->y0 < b->y0) return -1; + if (a->y0 > b->y0) return 1; + return 0; +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; + int vsubsample = result->h < 8 ? 15 : 5; + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; +error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count, *winding_lengths; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +typedef struct +{ + stbrp_coord x,y; + int id,w,h,was_packed; +} stbrp_rect; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + float recip_h = 1.0f / spc->h_oversample; + float recip_v = 1.0f / spc->v_oversample; + float sub_x = stbtt__oversample_shift(spc->h_oversample); + float sub_y = stbtt__oversample_shift(spc->v_oversample); + int i,j,k,n, return_value = 1; + stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars_in_range; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars_in_range; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(&info, fh) : stbtt_ScaleForMappingEmToPixels(&info, -fh); + for (j=0; j < ranges[i].num_chars_in_range; ++j) { + int x0,y0,x1,y1; + stbtt_GetCodepointBitmapBoxSubpixel(&info, ranges[i].first_unicode_char_in_range + j, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + stbrp_pack_rects(context, rects, k); + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(&info, fh) : stbtt_ScaleForMappingEmToPixels(&info, -fh); + for (j=0; j < ranges[i].num_chars_in_range; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int glyph = stbtt_FindGlyphIndex(&info, ranges[i].first_unicode_char_in_range + j); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(&info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(&info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_char_in_range = first_unicode_char_in_range; + range.num_chars_in_range = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(const stbtt_uint8 *s1, stbtt_int32 len1, const stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((const stbtt_uint8*) s1, len1, (const stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *font_collection, const char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#endif // STB_TRUETYPE_IMPLEMENTATION diff --git a/src/stb_vorbis.c b/src/stb_vorbis.c index 8bee7e1a..e2700157 100644 --- a/src/stb_vorbis.c +++ b/src/stb_vorbis.c @@ -1,4 +1,3 @@ - #include "stb_vorbis.h" #ifndef STB_VORBIS_HEADER_ONLY @@ -180,7 +179,7 @@ #define NULL 0 #endif -#ifndef _MSC_VER +#if !defined(_MSC_VER) && !(defined(__MINGW32__) && defined(__forceinline)) #if __GNUC__ #define __forceinline inline #else @@ -3562,7 +3561,7 @@ static int start_decoder(vorb *f) g->sorted_order[j] = (uint8) p[j].y; // precompute the neighbors for (j=2; j < g->values; ++j) { - int low = 0,hi = 0; + int low,hi; neighbors(g->Xlist, j, &low,&hi); g->neighbors[j][0] = low; g->neighbors[j][1] = hi; @@ -5024,6 +5023,7 @@ int stb_vorbis_get_samples_float(stb_vorbis *f, int channels, float **buffer, in #endif // STB_VORBIS_NO_PULLDATA_API /* Version history + 1.05 - 2015/04/19 - don't define __forceinline if it's redundant 1.04 - 2014/08/27 - fix missing const-correct case in API 1.03 - 2014/08/07 - Warning fixes 1.02 - 2014/07/09 - Declare qsort compare function _cdecl on windows diff --git a/src/stb_vorbis.h b/src/stb_vorbis.h index eb3afe3b..2a1460e1 100644 --- a/src/stb_vorbis.h +++ b/src/stb_vorbis.h @@ -1,4 +1,4 @@ -// Ogg Vorbis audio decoder - v1.04 - public domain +// Ogg Vorbis audio decoder - v1.05 - public domain // http://nothings.org/stb_vorbis/ // // Written by Sean Barrett in 2007, last updated in 2014 @@ -24,12 +24,13 @@ // Casey Muratori John Bolton Gargaj // Laurent Gomila Marc LeBlanc Ronny Chevalier // Bernhard Wodo Evan Balster "alxprd"@github -// Tom Beaumont Ingo Leitgeb +// Tom Beaumont Ingo Leitgeb Nicolas Guillemot // (If you reported a bug but do not appear in this list, it is because // someone else reported the bug before you. There were too many of you to // list them all because I was lax about updating for a long time, sorry.) // // Partial history: +// 1.05 - 2015/04/19 - don't define __forceinline if it's redundant // 1.04 - 2014/08/27 - fix missing const-correct case in API // 1.03 - 2014/08/07 - warning fixes // 1.02 - 2014/07/09 - declare qsort comparison as explicitly _cdecl in Windows @@ -58,6 +59,12 @@ #include <stdio.h> #endif +// NOTE: Added to work with raylib on Android +#if defined(PLATFORM_ANDROID) + #include "utils.h" // Android fopen function map +#endif + +// RaySan: Added for Linux #ifdef __linux #include <alloca.h> #endif @@ -30,15 +30,20 @@ #include <stdarg.h> // Used for functions with variable number of parameters (FormatText()) #include <stdio.h> // Standard input / output lib -#include "rlgl.h" // raylib OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 -#include "utils.h" // Required for function GetExtendion() +#include "utils.h" // Required for function GetExtension() + +// Following libs are used on LoadTTF() +#define STB_TRUETYPE_IMPLEMENTATION +#define STB_RECT_PACK_IMPLEMENTATION +#include "stb_rect_pack.h" +#include "stb_truetype.h" //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- #define FONT_FIRST_CHAR 32 #define MAX_FONTCHARS 128 -#define MAX_FORMATTEXT_LENGTH 50 +#define MAX_FORMATTEXT_LENGTH 64 #define BIT_CHECK(a,b) ((a) & (1<<(b))) @@ -64,6 +69,7 @@ static SpriteFont defaultFont; // Default font provided by raylib static bool PixelIsMagenta(Color p); // Check if a pixel is magenta static int ParseImageData(Color *imgDataPixel, int imgWidth, int imgHeight, Character **charSet); // Parse image pixel data to obtain character set measures static SpriteFont LoadRBMF(const char *fileName); // Load a rBMF font file (raylib BitMap Font) +static SpriteFont LoadTTF(const char *fileName, int fontSize); // Generate a sprite font image from TTF data (font size required) extern void LoadDefaultFont(void); extern void UnloadDefaultFont(void); @@ -73,16 +79,15 @@ extern void UnloadDefaultFont(void); //---------------------------------------------------------------------------------- extern void LoadDefaultFont(void) { - defaultFont.numChars = 96; // We know our default font has 94 chars + // NOTE: Using UTF8 encoding table for Unicode U+0000..U+00FF Basic Latin + Latin-1 Supplement + // http://www.utf8-chartable.de/unicode-utf8-table.pl - Image image; - image.width = 128; // We know our default font image is 128 pixels width - image.height = 64; // We know our default font image is 64 pixels height + defaultFont.numChars = 224; // Number of chars included in our default font // Default font is directly defined here (data generated from a sprite font image) // This way, we reconstruct SpriteFont without creating large global variables // This data is automatically allocated to Stack and automatically deallocated at the end of this function - int defaultFontData[256] = { + int defaultFontData[512] = { 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200020, 0x0001b000, 0x00000000, 0x00000000, 0x8ef92520, 0x00020a00, 0x7dbe8000, 0x1f7df45f, 0x4a2bf2a0, 0x0852091e, 0x41224000, 0x10041450, 0x2e292020, 0x08220812, 0x41222000, 0x10041450, 0x10f92020, 0x3efa084c, 0x7d22103c, 0x107df7de, 0xe8a12020, 0x08220832, 0x05220800, 0x10450410, 0xa4a3f000, 0x08520832, 0x05220400, 0x10450410, 0xe2f92020, 0x0002085e, 0x7d3e0281, 0x107df41f, @@ -98,42 +103,78 @@ extern void LoadDefaultFont(void) 0x04000404, 0x4100203c, 0x00000000, 0x00000800, 0xf7df7df0, 0x514bef85, 0xbefbefbe, 0x04513bef, 0x14414500, 0x494a2885, 0xa28a28aa, 0x04510820, 0xf44145f0, 0x474a289d, 0xa28a28aa, 0x04510be0, 0x14414510, 0x494a2884, 0xa28a28aa, 0x02910a00, 0xf7df7df0, 0xd14a2f85, 0xbefbe8aa, 0x011f7be0, 0x00000000, 0x00400804, 0x20080000, 0x00000000, 0x00000000, 0x00600f84, 0x20080000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0xac000000, 0x00000f01, 0x00000000, 0x00000000, 0x24000000, 0x00000901, 0x00000000, 0x00000000, 0x24000000, 0x00000901, 0x00000000, 0x00000000, - 0x24fa28a2, 0x00000901, 0x00000000, 0x00000000, 0x2242252a, 0x00000952, 0x00000000, 0x00000000, 0x2422222a, 0x00000929, 0x00000000, 0x00000000, - 0x2412252a, 0x00000901, 0x00000000, 0x00000000, 0x24fbe8be, 0x00000901, 0x00000000, 0x00000000, 0xac020000, 0x00000f01, 0x00000000, 0x00000000, - 0x0003e000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xac000000, 0x00000f01, 0x00000000, 0x00000000, 0x24000000, 0x00000901, 0x00000000, 0x06000000, 0x24000000, 0x00000901, 0x00000000, 0x09108000, + 0x24fa28a2, 0x00000901, 0x00000000, 0x013e0000, 0x2242252a, 0x00000952, 0x00000000, 0x038a8000, 0x2422222a, 0x00000929, 0x00000000, 0x010a8000, + 0x2412252a, 0x00000901, 0x00000000, 0x010a8000, 0x24fbe8be, 0x00000901, 0x00000000, 0x0ebe8000, 0xac020000, 0x00000f01, 0x00000000, 0x00048000, + 0x0003e000, 0x00000000, 0x00000000, 0x00008000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000038, 0x8443b80e, 0x00203a03, + 0x02bea080, 0xf0000020, 0xc452208a, 0x04202b02, 0xf8029122, 0x07f0003b, 0xe44b388e, 0x02203a02, 0x081e8a1c, 0x0411e92a, 0xf4420be0, 0x01248202, + 0xe8140414, 0x05d104ba, 0xe7c3b880, 0x00893a0a, 0x283c0e1c, 0x04500902, 0xc4400080, 0x00448002, 0xe8208422, 0x04500002, 0x80400000, 0x05200002, + 0x083e8e00, 0x04100002, 0x804003e0, 0x07000042, 0xf8008400, 0x07f00003, 0x80400000, 0x04000022, 0x00000000, 0x00000000, 0x80400000, 0x04000002, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00800702, 0x1848a0c2, 0x84010000, 0x02920921, 0x01042642, 0x00005121, 0x42023f7f, 0x00291002, + 0xefc01422, 0x7efdfbf7, 0xefdfa109, 0x03bbbbf7, 0x28440f12, 0x42850a14, 0x20408109, 0x01111010, 0x28440408, 0x42850a14, 0x2040817f, 0x01111010, + 0xefc78204, 0x7efdfbf7, 0xe7cf8109, 0x011111f3, 0x2850a932, 0x42850a14, 0x2040a109, 0x01111010, 0x2850b840, 0x42850a14, 0xefdfbf79, 0x03bbbbf7, + 0x001fa020, 0x00000000, 0x00001000, 0x00000000, 0x00002070, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x08022800, 0x00012283, 0x02430802, 0x01010001, 0x8404147c, 0x20000144, 0x80048404, 0x00823f08, 0xdfbf4284, 0x7e03f7ef, 0x142850a1, 0x0000210a, + 0x50a14684, 0x528a1428, 0x142850a1, 0x03efa17a, 0x50a14a9e, 0x52521428, 0x142850a1, 0x02081f4a, 0x50a15284, 0x4a221428, 0xf42850a1, 0x03efa14b, + 0x50a16284, 0x4a521428, 0x042850a1, 0x0228a17a, 0xdfbf427c, 0x7e8bf7ef, 0xf7efdfbf, 0x03efbd0b, 0x00000000, 0x04000000, 0x00000000, 0x00000008, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00200508, 0x00840400, 0x11458122, 0x00014210, + 0x00514294, 0x51420800, 0x20a22a94, 0x0050a508, 0x00200000, 0x00000000, 0x00050000, 0x08000000, 0xfefbefbe, 0xfbefbefb, 0xfbeb9114, 0x00fbefbe, + 0x20820820, 0x8a28a20a, 0x8a289114, 0x3e8a28a2, 0xfefbefbe, 0xfbefbe0b, 0x8a289114, 0x008a28a2, 0x228a28a2, 0x08208208, 0x8a289114, 0x088a28a2, + 0xfefbefbe, 0xfbefbefb, 0xfa2f9114, 0x00fbefbe, 0x00000000, 0x00000040, 0x00000000, 0x00000000, 0x00000000, 0x00000020, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00210100, 0x00000004, 0x00000000, 0x00000000, 0x14508200, 0x00001402, 0x00000000, 0x00000000, + 0x00000010, 0x00000020, 0x00000000, 0x00000000, 0xa28a28be, 0x00002228, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, + 0xa28a28aa, 0x000022a8, 0x00000000, 0x00000000, 0xa28a28aa, 0x000022e8, 0x00000000, 0x00000000, 0xbefbefbe, 0x00003e2f, 0x00000000, 0x00000000, + 0x00000004, 0x00002028, 0x00000000, 0x00000000, 0x80000000, 0x00003e0f, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 }; int charsHeight = 10; int charsDivisor = 1; // Every char is separated from the consecutive by a 1 pixel divisor, horizontally and vertically - int charsWidth[96] = { 3, 1, 4, 6, 5, 7, 6, 2, 3, 3, 5, 5, 2, 4, 1, 7, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 3, 4, 3, 6, - 7, 6, 6, 6, 6, 6, 6, 6, 6, 3, 5, 6, 5, 7, 6, 6, 6, 6, 6, 6, 7, 6, 7, 7, 6, 6, 6, 2, 7, 2, 3, 5, - 2, 5, 5, 5, 5, 5, 4, 5, 5, 1, 2, 5, 2, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 3, 1, 3, 4, 4 }; + int charsWidth[224] = { 3, 1, 4, 6, 5, 7, 6, 2, 3, 3, 5, 5, 2, 4, 1, 7, 5, 2, 5, 5, 5, 5, 5, 5, 5, 5, 1, 1, 3, 4, 3, 6, + 7, 6, 6, 6, 6, 6, 6, 6, 6, 3, 5, 6, 5, 7, 6, 6, 6, 6, 6, 6, 7, 6, 7, 7, 6, 6, 6, 2, 7, 2, 3, 5, + 2, 5, 5, 5, 5, 5, 4, 5, 5, 1, 2, 5, 2, 5, 5, 5, 5, 5, 5, 5, 4, 5, 5, 5, 5, 5, 5, 3, 1, 3, 4, 4, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 5, 5, 5, 7, 1, 5, 3, 7, 3, 5, 4, 1, 7, 4, 3, 5, 3, 3, 2, 5, 6, 1, 2, 2, 3, 5, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 6, 3, 3, 3, 3, 7, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 6, 4, 6, + 5, 5, 5, 5, 5, 5, 9, 5, 5, 5, 5, 5, 2, 2, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 3, 5 }; // Re-construct image from defaultFontData and generate OpenGL texture //---------------------------------------------------------------------- - image.pixels = (Color *)malloc(image.width * image.height * sizeof(Color)); + int imWidth = 128; + int imHeight = 128; + + Color *imagePixels = (Color *)malloc(imWidth*imHeight*sizeof(Color)); - for (int i = 0; i < image.width * image.height; i++) image.pixels[i] = BLANK; // Initialize array + for (int i = 0; i < imWidth*imHeight; i++) imagePixels[i] = BLANK; // Initialize array int counter = 0; // Font data elements counter // Fill imgData with defaultFontData (convert from bit to pixel!) - for (int i = 0; i < image.width * image.height; i += 32) + for (int i = 0; i < imWidth*imHeight; i += 32) { for (int j = 31; j >= 0; j--) { - if (BIT_CHECK(defaultFontData[counter], j)) image.pixels[i+j] = WHITE; + if (BIT_CHECK(defaultFontData[counter], j)) imagePixels[i+j] = WHITE; } counter++; - if (counter > 256) counter = 0; // Security check... + if (counter > 512) counter = 0; // Security check... } + //FILE *myimage = fopen("default_font.raw", "wb"); + //fwrite(image.pixels, 1, 128*128*4, myimage); + //fclose(myimage); + + Image image = LoadImageFromData(imagePixels, imWidth, imHeight, UNCOMPRESSED_GRAY_ALPHA); + + free(imagePixels); + defaultFont.texture = LoadTextureFromImage(image, false); // Convert loaded image to OpenGL texture UnloadImage(image); @@ -172,7 +213,7 @@ extern void LoadDefaultFont(void) extern void UnloadDefaultFont(void) { - rlDeleteTextures(defaultFont.texture.id); + UnloadTexture(defaultFont.texture); free(defaultFont.charSet); } @@ -189,18 +230,21 @@ SpriteFont LoadSpriteFont(const char *fileName) // Check file extension if (strcmp(GetExtension(fileName),"rbmf") == 0) spriteFont = LoadRBMF(fileName); + else if (strcmp(GetExtension(fileName),"ttf") == 0) spriteFont = LoadTTF(fileName, 20); else { Image image = LoadImage(fileName); - // At this point we have a pixel array with all the data... + // At this point we have a data array... + Color *imagePixels = GetPixelData(image); + #if defined(PLATFORM_RPI) || defined(PLATFORM_WEB) ConvertToPOT(&image, MAGENTA); #endif // Process bitmap Font pixel data to get measures (Character array) // spriteFont.charSet data is filled inside the function and memory is allocated! - int numChars = ParseImageData(image.pixels, image.width, image.height, &spriteFont.charSet); + int numChars = ParseImageData(imagePixels, image.width, image.height, &spriteFont.charSet); TraceLog(INFO, "[%s] SpriteFont data parsed correctly", fileName); TraceLog(INFO, "[%s] SpriteFont num chars detected: %i", fileName, numChars); @@ -208,7 +252,8 @@ SpriteFont LoadSpriteFont(const char *fileName) spriteFont.numChars = numChars; spriteFont.texture = LoadTextureFromImage(image, false); // Convert loaded image to OpenGL texture - + + free(imagePixels); UnloadImage(image); } @@ -218,7 +263,7 @@ SpriteFont LoadSpriteFont(const char *fileName) // Unload SpriteFont from GPU memory void UnloadSpriteFont(SpriteFont spriteFont) { - rlDeleteTextures(spriteFont.texture.id); + UnloadTexture(spriteFont.texture); free(spriteFont.charSet); } @@ -244,41 +289,37 @@ void DrawText(const char *text, int posX, int posY, int fontSize, Color color) void DrawTextEx(SpriteFont spriteFont, const char *text, Vector2 position, int fontSize, int spacing, Color tint) { int length = strlen(text); - int positionX = (int)position.x; + int offsetX = 0; float scaleFactor; + unsigned char letter; Character c; - if (fontSize <= spriteFont.charSet[0].h) scaleFactor = 1.0f; - else scaleFactor = (float)fontSize / spriteFont.charSet[0].h; - - rlEnableTexture(spriteFont.texture.id); + //if (fontSize <= spriteFont.charSet[0].h) scaleFactor = 1.0f; + //else scaleFactor = (float)fontSize / spriteFont.charSet[0].h; + + scaleFactor = (float)fontSize/spriteFont.charSet[0].h; - rlBegin(RL_QUADS); - for(int i = 0; i < length; i++) + for(int i = 0; i < length; i++) + { + if ((unsigned char)text[i] == 0xc2) { - c = spriteFont.charSet[(int)text[i] - FONT_FIRST_CHAR]; - - rlColor4ub(tint.r, tint.g, tint.b, tint.a); - rlNormal3f(0.0f, 0.0f, 1.0f); // Normal Pointing Towards Viewer - - rlTexCoord2f((float)c.x / spriteFont.texture.width, (float)c.y / spriteFont.texture.height); - rlVertex2f(positionX, position.y); - - rlTexCoord2f((float)c.x / spriteFont.texture.width, (float)(c.y + c.h) / spriteFont.texture.height); - rlVertex2f(positionX, position.y + (c.h) * scaleFactor); - - rlTexCoord2f((float)(c.x + c.w) / spriteFont.texture.width, (float)(c.y + c.h) / spriteFont.texture.height); - rlVertex2f(positionX + (c.w) * scaleFactor, position.y + (c.h) * scaleFactor); - - rlTexCoord2f((float)(c.x + c.w) / spriteFont.texture.width, (float)c.y / spriteFont.texture.height); - rlVertex2f(positionX + (c.w) * scaleFactor, position.y); - - positionX += ((spriteFont.charSet[(int)text[i] - FONT_FIRST_CHAR].w) * scaleFactor + spacing); + letter = (unsigned char)text[i + 1]; + c = spriteFont.charSet[letter - FONT_FIRST_CHAR]; + i++; + } + else if ((unsigned char)text[i] == 0xc3) + { + letter = (unsigned char)text[i + 1]; + c = spriteFont.charSet[letter - FONT_FIRST_CHAR + 64]; + i++; } - rlEnd(); + else c = spriteFont.charSet[(int)text[i] - FONT_FIRST_CHAR]; + + DrawTexturePro(spriteFont.texture, (Rectangle){ c.x, c.y, c.w, c.h }, (Rectangle){ position.x + offsetX, position.y, c.w*scaleFactor, c.h*scaleFactor} , (Vector2){ 0, 0 }, 0.0f, tint); - rlDisableTexture(); + offsetX += (c.w*scaleFactor + spacing); + } } // Formatting of text with variables to 'embed' @@ -465,7 +506,6 @@ static SpriteFont LoadRBMF(const char *fileName) } rbmfInfoHeader; SpriteFont spriteFont; - Image image; rbmfInfoHeader rbmfHeader; unsigned int *rbmfFileData = NULL; @@ -487,9 +527,6 @@ static SpriteFont LoadRBMF(const char *fileName) spriteFont.numChars = (int)rbmfHeader.numChars; - image.width = (int)rbmfHeader.imgWidth; - image.height = (int)rbmfHeader.imgHeight; - int numPixelBits = rbmfHeader.imgWidth * rbmfHeader.imgHeight / 32; rbmfFileData = (unsigned int *)malloc(numPixelBits * sizeof(unsigned int)); @@ -502,22 +539,26 @@ static SpriteFont LoadRBMF(const char *fileName) // Re-construct image from rbmfFileData //----------------------------------------- - image.pixels = (Color *)malloc(image.width * image.height * sizeof(Color)); + Color *imagePixels = (Color *)malloc(rbmfHeader.imgWidth*rbmfHeader.imgHeight*sizeof(Color)); - for (int i = 0; i < image.width * image.height; i++) image.pixels[i] = BLANK; // Initialize array + for (int i = 0; i < rbmfHeader.imgWidth*rbmfHeader.imgHeight; i++) imagePixels[i] = BLANK; // Initialize array int counter = 0; // Font data elements counter // Fill image data (convert from bit to pixel!) - for (int i = 0; i < image.width * image.height; i += 32) + for (int i = 0; i < rbmfHeader.imgWidth*rbmfHeader.imgHeight; i += 32) { for (int j = 31; j >= 0; j--) { - if (BIT_CHECK(rbmfFileData[counter], j)) image.pixels[i+j] = WHITE; + if (BIT_CHECK(rbmfFileData[counter], j)) imagePixels[i+j] = WHITE; } counter++; } + + Image image = LoadImageFromData(imagePixels, rbmfHeader.imgWidth, rbmfHeader.imgHeight, UNCOMPRESSED_GRAY_ALPHA); + + free(imagePixels); TraceLog(INFO, "[%s] Image reconstructed correctly, now converting it to texture", fileName); @@ -557,7 +598,7 @@ static SpriteFont LoadRBMF(const char *fileName) TraceLog(INFO, "[%s] rBMF file loaded correctly as SpriteFont", fileName); } - + fclose(rbmfFile); free(rbmfFileData); // Now we can free loaded data from RAM memory @@ -567,11 +608,105 @@ static SpriteFont LoadRBMF(const char *fileName) } // Generate a sprite font from TTF data (font size required) -static SpriteFont GenerateFromTTF(const char *fileName, int fontSize) +static SpriteFont LoadTTF(const char *fileName, int fontSize) { SpriteFont font; - // TODO: Load TTF and generate bitmap font and chars data + Image image; + image.width = 512; + image.height = 512; + //image.pixels = (Color *)malloc(image.width*image.height*sizeof(Color)); + + unsigned char *ttfBuffer = (unsigned char *)malloc(1 << 25); + + // TODO: Load TTF and generate bitmap font and chars data -> REVIEW! + + stbtt_packedchar chardata[128]; // Num characters: 128 (?) -> REVIEW! + + unsigned char *tempBitmap = (unsigned char *)malloc(image.width*image.height*sizeof(unsigned char)); // One channel bitmap returned! + + // REFERENCE +/* + typedef struct + { + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; + } stbtt_packedchar; +*/ + + stbtt_pack_context pc; + + FILE *ttfFile = fopen(fileName, "rb"); + + fread(ttfBuffer, 1, 1<<25, ttfFile); + + stbtt_PackBegin(&pc, tempBitmap, image.width, image.height, 0, 1, NULL); + + //stbtt_PackSetOversampling(&pc, 1, 1); + //stbtt_PackFontRange(&pc, ttfBuffer, 0, fontSize, 32, 95, chardata[0]+32); + stbtt_PackSetOversampling(&pc, 2, 2); // Better results + stbtt_PackFontRange(&pc, ttfBuffer, 0, fontSize, 32, 95, chardata + 32); + //stbtt_PackSetOversampling(&pc, 3, 1); + //stbtt_PackFontRange(&pc, ttfBuffer, 0, fontSize, 32, 95, chardata[2]+32); + + stbtt_PackEnd(&pc); + + free(ttfBuffer); + + // Now we have image data in tempBitmap and chardata filled... +/* + for (int i = 0; i < 512*512; i++) + { + image.pixels[i].r = tempBitmap[i]; + image.pixels[i].g = tempBitmap[i]; + image.pixels[i].b = tempBitmap[i]; + image.pixels[i].a = 255; + } +*/ + free(tempBitmap); + + // REFERENCE EXAMPLE +/* + //To draw, provide *text, posX, posY + //stbtt_aligned_quad letter; + //stbtt_GetPackedQuad(chardata[0], BITMAP_W, BITMAP_H, *text++, &posX, &posY, &letter, font ? 0 : integer_align); + + void print(float x, float y, int fontNum, char *text) + { + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, font_tex); + glBegin(GL_QUADS); + while (*text) { + stbtt_aligned_quad q; + stbtt_GetPackedQuad(chardata[fontNum], BITMAP_W, BITMAP_H, *text++, &x, &y, &q, fontNum ? 0 : integer_align); + drawBoxTC(q.x0,q.y0,q.x1,q.y1, q.s0,q.t0,q.s1,q.t1); + } + glEnd(); + } + + print(100,160, 0, "This is a test"); +*/ + font.numChars = 95; + font.charSet = (Character *)malloc(font.numChars*sizeof(Character)); + font.texture = LoadTextureFromImage(image, false); + + //stbtt_aligned_quad letter; + //int x = 0, y = 0; + + for (int i = 0; i < font.numChars; i++) + { + font.charSet[i].value = i + 32; + + //stbtt_GetPackedQuad(chardata[0], 512, 512, i, &x, &y, &letter, 0); + + font.charSet[i].x = chardata[i + 32].x0; + font.charSet[i].y = chardata[i + 32].y0; + font.charSet[i].w = chardata[i + 32].x1 - chardata[i + 32].x0; + font.charSet[i].h = chardata[i + 32].y1 - chardata[i + 32].y0; + } + + UnloadImage(image); return font; }
\ No newline at end of file diff --git a/src/textures.c b/src/textures.c index f701c380..e6c6cccb 100644 --- a/src/textures.c +++ b/src/textures.c @@ -47,15 +47,7 @@ //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- -typedef struct { - unsigned char *data; // Image raw data - int width; // Image base width - int height; // Image base height - //int bpp; // bytes per pixel - //int components; // num color components - int mipmaps; // Mipmap levels, 1 by default - int compFormat; // Compressed data format, 0 if no compression -} ImageEx; +// ... //---------------------------------------------------------------------------------- // Global Variables Definition @@ -65,13 +57,16 @@ typedef struct { //---------------------------------------------------------------------------------- // Other Modules Functions Declaration (required by text) //---------------------------------------------------------------------------------- -//... +// ... //---------------------------------------------------------------------------------- // Module specific Functions Declaration //---------------------------------------------------------------------------------- -static ImageEx LoadDDS(const char *fileName); // Load DDS file -static ImageEx LoadPKM(const char *fileName); // Load PKM file +static Image LoadDDS(const char *fileName); // Load DDS file +static Image LoadPKM(const char *fileName); // Load PKM file +static Image LoadKTX(const char *fileName); // Load KTX file +static Image LoadPVR(const char *fileName); // Load PVR file +static Image LoadASTC(const char *fileName); // Load ASTC file //---------------------------------------------------------------------------------- // Module Functions Definition @@ -82,10 +77,12 @@ Image LoadImage(const char *fileName) { Image image; - // Initial values - image.pixels = NULL; + // Initialize image default values + image.data = NULL; image.width = 0; image.height = 0; + image.mipmaps = 0; + image.format = 0; if ((strcmp(GetExtension(fileName),"png") == 0) || (strcmp(GetExtension(fileName),"bmp") == 0) || @@ -95,84 +92,39 @@ Image LoadImage(const char *fileName) (strcmp(GetExtension(fileName),"psd") == 0) || (strcmp(GetExtension(fileName),"pic") == 0)) { - int imgWidth; - int imgHeight; - int imgBpp; - + int imgWidth = 0; + int imgHeight = 0; + int imgBpp = 0; + // NOTE: Using stb_image to load images (Supports: BMP, TGA, PNG, JPG, ...) - // Force loading to 4 components (RGBA) - byte *imgData = stbi_load(fileName, &imgWidth, &imgHeight, &imgBpp, 4); - - // TODO: Check if file could be loaded! (imgData == NULL)? - - if (imgData != NULL) - { - // Convert array to pixel array for working convenience - image.pixels = (Color *)malloc(imgWidth * imgHeight * sizeof(Color)); - - int pix = 0; - - for (int i = 0; i < (imgWidth * imgHeight * 4); i += 4) - { - image.pixels[pix].r = imgData[i]; - image.pixels[pix].g = imgData[i+1]; - image.pixels[pix].b = imgData[i+2]; - image.pixels[pix].a = imgData[i+3]; - pix++; - } - - stbi_image_free(imgData); - - image.width = imgWidth; - image.height = imgHeight; - - TraceLog(INFO, "[%s] Image loaded successfully (%ix%i)", fileName, image.width, image.height); - } - else TraceLog(WARNING, "[%s] Image could not be loaded, file not recognized", fileName); - } - else if (strcmp(GetExtension(fileName),"dds") == 0) - { - // NOTE: DDS uncompressed images can also be loaded (discarding mipmaps...) - - ImageEx imageDDS = LoadDDS(fileName); - - if (imageDDS.compFormat == 0) - { - image.pixels = (Color *)malloc(imageDDS.width * imageDDS.height * sizeof(Color)); - image.width = imageDDS.width; - image.height = imageDDS.height; + image.data = stbi_load(fileName, &imgWidth, &imgHeight, &imgBpp, 0); - int pix = 0; - - for (int i = 0; i < (image.width * image.height * 4); i += 4) - { - image.pixels[pix].r = imageDDS.data[i]; - image.pixels[pix].g = imageDDS.data[i+1]; - image.pixels[pix].b = imageDDS.data[i+2]; - image.pixels[pix].a = imageDDS.data[i+3]; - pix++; - } - - free(imageDDS.data); - - TraceLog(INFO, "[%s] DDS Image loaded successfully (uncompressed, no mipmaps)", fileName); - } - else TraceLog(WARNING, "[%s] DDS Compressed image data could not be loaded", fileName); + image.width = imgWidth; + image.height = imgHeight; + image.mipmaps = 1; + + if (imgBpp == 1) image.format = UNCOMPRESSED_GRAYSCALE; + else if (imgBpp == 2) image.format = UNCOMPRESSED_GRAY_ALPHA; + else if (imgBpp == 3) image.format = UNCOMPRESSED_R8G8B8; + else if (imgBpp == 4) image.format = UNCOMPRESSED_R8G8B8A8; } - else if (strcmp(GetExtension(fileName),"pkm") == 0) - { - TraceLog(INFO, "[%s] PKM Compressed image data could not be loaded", fileName); + else if (strcmp(GetExtension(fileName),"dds") == 0) image = LoadDDS(fileName); + else if (strcmp(GetExtension(fileName),"pkm") == 0) image = LoadPKM(fileName); + else if (strcmp(GetExtension(fileName),"ktx") == 0) image = LoadKTX(fileName); + else if (strcmp(GetExtension(fileName),"pvr") == 0) image = LoadPVR(fileName); + else if (strcmp(GetExtension(fileName),"astc") == 0) image = LoadASTC(fileName); + + if (image.data != NULL) + { + TraceLog(INFO, "[%s] Image loaded successfully (%ix%i)", fileName, image.width, image.height); } - else TraceLog(WARNING, "[%s] Image extension not recognized, it can't be loaded", fileName); - - // ALTERNATIVE: We can load pixel data directly into Color struct pixels array, - // to do that, struct data alignment should be the right one (4 byte); it is. - //image.pixels = stbi_load(fileName, &imgWidth, &imgHeight, &imgBpp, 4); + else TraceLog(WARNING, "[%s] Image could not be loaded, file not recognized", fileName); return image; } // Load an image from rRES file (raylib Resource) +// TODO: Review function to support multiple color modes Image LoadImageFromRES(const char *rresName, int resId) { Image image; @@ -187,7 +139,7 @@ Image LoadImageFromRES(const char *rresName, int resId) FILE *rresFile = fopen(rresName, "rb"); - if (rresFile == NULL) + if (rresFile == NULL) { TraceLog(WARNING, "[%s] rRES raylib resource file could not be opened", rresName); } @@ -235,28 +187,19 @@ Image LoadImageFromRES(const char *rresName, int resId) image.width = (int)imgWidth; image.height = (int)imgHeight; - unsigned char *data = malloc(infoHeader.size); + unsigned char *compData = malloc(infoHeader.size); - fread(data, infoHeader.size, 1, rresFile); + fread(compData, infoHeader.size, 1, rresFile); - unsigned char *imgData = DecompressData(data, infoHeader.size, infoHeader.srcSize); + unsigned char *imgData = DecompressData(compData, infoHeader.size, infoHeader.srcSize); - image.pixels = (Color *)malloc(sizeof(Color)*imgWidth*imgHeight); + // TODO: Review color mode + //image.data = (unsigned char *)malloc(sizeof(unsigned char)*imgWidth*imgHeight*4); + image.data = imgData; - int pix = 0; + //free(imgData); - for (int i = 0; i < (imgWidth*imgHeight*4); i += 4) - { - image.pixels[pix].r = imgData[i]; - image.pixels[pix].g = imgData[i+1]; - image.pixels[pix].b = imgData[i+2]; - image.pixels[pix].a = imgData[i+3]; - pix++; - } - - free(imgData); - - free(data); + free(compData); TraceLog(INFO, "[%s] Image loaded successfully from resource, size: %ix%i", rresName, image.width, image.height); } @@ -296,64 +239,42 @@ Image LoadImageFromRES(const char *rresName, int resId) Texture2D LoadTexture(const char *fileName) { Texture2D texture; - - // Init texture to default values - texture.id = 0; - texture.width = 0; - texture.height = 0; - - if (strcmp(GetExtension(fileName),"dds") == 0) - { - ImageEx image = LoadDDS(fileName); - - if (image.compFormat == 0) - { - texture.id = rlglLoadTexture(image.data, image.width, image.height, false); - } - else - { - texture.id = rlglLoadCompressedTexture(image.data, image.width, image.height, image.mipmaps, image.compFormat); - } - - texture.width = image.width; - texture.height = image.height; - - if (texture.id == 0) TraceLog(WARNING, "[%s] DDS texture could not be loaded", fileName); - else TraceLog(INFO, "[%s] DDS texture loaded successfully", fileName); - - free(image.data); - } - else if (strcmp(GetExtension(fileName),"pkm") == 0) - { - ImageEx image = LoadPKM(fileName); - - texture.id = rlglLoadCompressedTexture(image.data, image.width, image.height, image.mipmaps, image.compFormat); - texture.width = image.width; - texture.height = image.height; - - if (texture.id == 0) TraceLog(WARNING, "[%s] PKM texture could not be loaded", fileName); - else TraceLog(INFO, "[%s] PKM texture loaded successfully", fileName); + Image image = LoadImage(fileName); + +#if defined(PLATFORM_RPI) || defined(PLATFORM_WEB) + ConvertToPOT(&image, BLANK); +#endif - free(image.data); + if (image.data != NULL) + { + texture = LoadTextureFromImage(image, false); + UnloadImage(image); } else { - Image image = LoadImage(fileName); + TraceLog(WARNING, "Texture could not be created"); - if (image.pixels != NULL) - { -#if defined(PLATFORM_RPI) || defined(PLATFORM_WEB) - ConvertToPOT(&image, BLANK); -#endif - texture = LoadTextureFromImage(image, false); - UnloadImage(image); - } + texture.id = 0; } return texture; } +Texture2D LoadTextureEx(void *data, int width, int height, int textureFormat, int mipmapCount, bool genMipmaps) +{ + Texture2D texture; + + texture.width = width; + texture.height = height; + texture.mipmaps = mipmapCount; + texture.format = textureFormat; + + texture.id = rlglLoadTexture(data, width, height, textureFormat, mipmapCount, genMipmaps); + + return texture; +} + // Load a texture from image data // NOTE: image is not unloaded, it must be done manually Texture2D LoadTextureFromImage(Image image, bool genMipmaps) @@ -364,45 +285,15 @@ Texture2D LoadTextureFromImage(Image image, bool genMipmaps) texture.id = 0; texture.width = 0; texture.height = 0; - - if ((image.pixels != NULL) && (image.width > 0) && (image.height > 0)) - { - unsigned char *imgData = malloc(image.width * image.height * 4); - - int j = 0; - - for (int i = 0; i < image.width * image.height * 4; i += 4) - { - imgData[i] = image.pixels[j].r; - imgData[i+1] = image.pixels[j].g; - imgData[i+2] = image.pixels[j].b; - imgData[i+3] = image.pixels[j].a; - - j++; - } - - // NOTE: rlglLoadTexture() can generate mipmaps (POT image required) - texture.id = rlglLoadTexture(imgData, image.width, image.height, genMipmaps); - - texture.width = image.width; - texture.height = image.height; - - free(imgData); - } - else TraceLog(WARNING, "Texture could not be loaded, image data is not valid"); - - return texture; -} - -// [DEPRECATED] Load a texture from image data -// NOTE: Use LoadTextureFromImage() instead -Texture2D CreateTexture(Image image, bool genMipmaps) -{ - Texture2D texture; - - texture = LoadTextureFromImage(image, genMipmaps); + texture.mipmaps = 0; + texture.format = 0; - TraceLog(INFO, "Created texture id: %i", texture.id); + texture.id = rlglLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps, false); + + texture.width = image.width; + texture.height = image.height; + texture.mipmaps = image.mipmaps; + texture.format = image.format; return texture; } @@ -422,7 +313,7 @@ Texture2D LoadTextureFromRES(const char *rresName, int resId) // Unload image from CPU memory (RAM) void UnloadImage(Image image) { - free(image.pixels); + free(image.data); } // Unload texture from GPU memory @@ -435,6 +326,8 @@ void UnloadTexture(Texture2D texture) // NOTE: Requirement on OpenGL ES 2.0 (RPI, HTML5) void ConvertToPOT(Image *image, Color fillColor) { + // TODO: Review for new image struct + /* // Just add the required amount of pixels at the right and bottom sides of image... int potWidth = GetNextPOT(image->width); int potHeight = GetNextPOT(image->height); @@ -451,7 +344,7 @@ void ConvertToPOT(Image *image, Color fillColor) { for (int i = 0; i < potWidth; i++) { - if ((j < image->height) && (i < image->width)) imgDataPixelPOT[j*potWidth + i] = image->pixels[j*image->width + i]; + if ((j < image->height) && (i < image->width)) imgDataPixelPOT[j*potWidth + i] = image->data[j*image->width + i]; else imgDataPixelPOT[j*potWidth + i] = fillColor; } } @@ -464,6 +357,232 @@ void ConvertToPOT(Image *image, Color fillColor) image->width = potWidth; image->height = potHeight; } + */ +} + +// Get pixel data from image in the form of Color struct array +Color *GetPixelData(Image image) +{ + Color *pixels = (Color *)malloc(image.width*image.height*sizeof(Color)); + + int k = 0; + + for (int i = 0; i < image.width*image.height; i++) + { + switch (image.format) + { + case UNCOMPRESSED_GRAYSCALE: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k]; + pixels[i].b = ((unsigned char *)image.data)[k]; + pixels[i].a = 255; + + k++; + } break; + case UNCOMPRESSED_GRAY_ALPHA: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k]; + pixels[i].b = ((unsigned char *)image.data)[k]; + pixels[i].a = ((unsigned char *)image.data)[k + 1]; + + k += 2; + } break; + case UNCOMPRESSED_R5G5B5A1: + { + unsigned short pixel = ((unsigned short *)image.data)[k]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111000000) >> 6)*(255/31)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000000111110) >> 1)*(255/31)); + pixels[i].a = (unsigned char)((pixel & 0b0000000000000001)*255); + + k++; + } break; + case UNCOMPRESSED_R5G6B5: + { + unsigned short pixel = ((unsigned short *)image.data)[k]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111100000000000) >> 11)*(255/31)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000011111100000) >> 5)*(255/63)); + pixels[i].b = (unsigned char)((float)(pixel & 0b0000000000011111)*(255/31)); + pixels[i].a = 255; + + k++; + } break; + case UNCOMPRESSED_R4G4B4A4: + { + unsigned short pixel = ((unsigned short *)image.data)[k]; + + pixels[i].r = (unsigned char)((float)((pixel & 0b1111000000000000) >> 12)*(255/15)); + pixels[i].g = (unsigned char)((float)((pixel & 0b0000111100000000) >> 8)*(255/15)); + pixels[i].b = (unsigned char)((float)((pixel & 0b0000000011110000) >> 4)*(255/15)); + pixels[i].a = (unsigned char)((float)(pixel & 0b0000000000001111)*(255/15)); + + k++; + } break; + case UNCOMPRESSED_R8G8B8A8: + { + pixels[i].r = ((unsigned char *)image.data)[k]; + pixels[i].g = ((unsigned char *)image.data)[k + 1]; + pixels[i].b = ((unsigned char *)image.data)[k + 2]; + pixels[i].a = ((unsigned char *)image.data)[k + 3]; + + k += 4; + } break; + case UNCOMPRESSED_R8G8B8: + { + pixels[i].r = (unsigned char)((unsigned char *)image.data)[k]; + pixels[i].g = (unsigned char)((unsigned char *)image.data)[k + 1]; + pixels[i].b = (unsigned char)((unsigned char *)image.data)[k + 2]; + pixels[i].a = 255; + + k += 3; + } break; + default: TraceLog(WARNING, "Format not supported for pixel data retrieval"); break; + } + } + + return pixels; +} + +// Fill image data with pixels Color data (RGBA - 32bit) +// NOTE: Data is transformed to desired format +Image LoadImageFromData(Color *pixels, int width, int height, int format) +{ + Image image; + image.data = NULL; + image.width = width; + image.height = height; + image.mipmaps = 1; + image.format = format; + + int k = 0; + + switch (format) + { + case UNCOMPRESSED_GRAYSCALE: + { + image.data = (unsigned char *)malloc(image.width*image.height*sizeof(unsigned char)); + + for (int i = 0; i < image.width*image.height; i++) + { + ((unsigned char *)image.data)[i] = (unsigned char)((float)pixels[k].r*0.299f + (float)pixels[k].g*0.587f + (float)pixels[k].b*0.114f); + k++; + } + + } break; + case UNCOMPRESSED_GRAY_ALPHA: + { + image.data = (unsigned char *)malloc(image.width*image.height*2*sizeof(unsigned char)); + + for (int i = 0; i < image.width*image.height*2; i += 2) + { + ((unsigned char *)image.data)[i] = (unsigned char)((float)pixels[k].r*0.299f + (float)pixels[k].g*0.587f + (float)pixels[k].b*0.114f); + ((unsigned char *)image.data)[i + 1] = pixels[k].a; + k++; + } + + } break; + case UNCOMPRESSED_R5G6B5: + { + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + + unsigned char r; + unsigned char g; + unsigned char b; + + for (int i = 0; i < image.width*image.height; i++) + { + r = (unsigned char)(round((float)pixels[k].r*31/255)); + g = (unsigned char)(round((float)pixels[k].g*63/255)); + b = (unsigned char)(round((float)pixels[k].b*31/255)); + + ((unsigned short *)image.data)[i] = (unsigned short)r << 11 | (unsigned short)g << 5 | (unsigned short)b; + + k++; + } + + } break; + case UNCOMPRESSED_R8G8B8: + { + image.data = (unsigned char *)malloc(image.width*image.height*3*sizeof(unsigned char)); + + for (int i = 0; i < image.width*image.height*3; i += 3) + { + ((unsigned char *)image.data)[i] = pixels[k].r; + ((unsigned char *)image.data)[i + 1] = pixels[k].g; + ((unsigned char *)image.data)[i + 2] = pixels[k].b; + k++; + } + } break; + case UNCOMPRESSED_R5G5B5A1: + { + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a = 1; + + for (int i = 0; i < image.width*image.height; i++) + { + r = (unsigned char)(round((float)pixels[k].r*31/255)); + g = (unsigned char)(round((float)pixels[k].g*31/255)); + b = (unsigned char)(round((float)pixels[k].b*31/255)); + a = (pixels[k].a > 50) ? 1 : 0; + + ((unsigned short *)image.data)[i] = (unsigned short)r << 11 | (unsigned short)g << 6 | (unsigned short)b << 1| (unsigned short)a; + + k++; + } + + } break; + case UNCOMPRESSED_R4G4B4A4: + { + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + + for (int i = 0; i < image.width*image.height; i++) + { + r = (unsigned char)(round((float)pixels[k].r*15/255)); + g = (unsigned char)(round((float)pixels[k].g*15/255)); + b = (unsigned char)(round((float)pixels[k].b*15/255)); + a = (unsigned char)(round((float)pixels[k].a*15/255)); + + ((unsigned short *)image.data)[i] = (unsigned short)r << 12 | (unsigned short)g << 8| (unsigned short)b << 4| (unsigned short)a; + + k++; + } + + } break; + case UNCOMPRESSED_R8G8B8A8: + { + image.data = (unsigned char *)malloc(image.width*image.height*4*sizeof(unsigned char)); + + for (int i = 0; i < image.width*image.height*4; i += 4) + { + ((unsigned char *)image.data)[i] = pixels[k].r; + ((unsigned char *)image.data)[i + 1] = pixels[k].g; + ((unsigned char *)image.data)[i + 2] = pixels[k].b; + ((unsigned char *)image.data)[i + 3] = pixels[k].a; + k++; + } + } break; + default: + { + TraceLog(WARNING, "Format not recognized, image could not be loaded"); + + return image; + } break; + } + + return image; } // Draw a Texture2D @@ -494,7 +613,7 @@ void DrawTextureRec(Texture2D texture, Rectangle sourceRec, Vector2 position, Co Rectangle destRec = { (int)position.x, (int)position.y, sourceRec.width, sourceRec.height }; Vector2 origin = { 0, 0 }; - DrawTexturePro(texture, sourceRec, destRec, origin, 0, tint); + DrawTexturePro(texture, sourceRec, destRec, origin, 0.0f, tint); } // Draw a part of a texture (defined by a rectangle) with 'pro' parameters @@ -538,25 +657,21 @@ void DrawTexturePro(Texture2D texture, Rectangle sourceRec, Rectangle destRec, V //---------------------------------------------------------------------------------- // Loading DDS image data (compressed or uncompressed) -// NOTE: Compressed data loading not supported on OpenGL 1.1 -static ImageEx LoadDDS(const char *fileName) +static Image LoadDDS(const char *fileName) { + // Required extension: + // GL_EXT_texture_compression_s3tc + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 + // GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 + // GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 + // GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 + #define FOURCC_DXT1 0x31545844 // Equivalent to "DXT1" in ASCII #define FOURCC_DXT3 0x33545844 // Equivalent to "DXT3" in ASCII #define FOURCC_DXT5 0x35545844 // Equivalent to "DXT5" in ASCII - #ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 - #endif - - #ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 - #endif - - #ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 - #endif - // DDS Pixel Format typedef struct { unsigned int size; @@ -565,7 +680,7 @@ static ImageEx LoadDDS(const char *fileName) unsigned int rgbBitCount; unsigned int rBitMask; unsigned int gBitMask; - unsigned int bitMask; + unsigned int bBitMask; unsigned int aBitMask; } ddsPixelFormat; @@ -577,7 +692,7 @@ static ImageEx LoadDDS(const char *fileName) unsigned int width; unsigned int pitchOrLinearSize; unsigned int depth; - unsigned int mipMapCount; + unsigned int mipmapCount; unsigned int reserved1[11]; ddsPixelFormat ddspf; unsigned int caps; @@ -587,20 +702,19 @@ static ImageEx LoadDDS(const char *fileName) unsigned int reserved2; } ddsHeader; - ImageEx image; - ddsHeader header; - + Image image; + image.data = NULL; image.width = 0; image.height = 0; image.mipmaps = 0; - image.compFormat = 0; + image.format = 0; FILE *ddsFile = fopen(fileName, "rb"); if (ddsFile == NULL) { - TraceLog(WARNING, "[%s] DDS image file could not be opened", fileName); + TraceLog(WARNING, "[%s] DDS file could not be opened", fileName); } else { @@ -612,11 +726,12 @@ static ImageEx LoadDDS(const char *fileName) if (strncmp(filecode, "DDS ", 4) != 0) { TraceLog(WARNING, "[%s] DDS file does not seem to be a valid image", fileName); - fclose(ddsFile); } else { - // Get the surface descriptor + ddsHeader header; + + // Get the image header fread(&header, sizeof(ddsHeader), 1, ddsFile); TraceLog(DEBUG, "[%s] DDS file header size: %i", fileName, sizeof(ddsHeader)); @@ -626,75 +741,101 @@ static ImageEx LoadDDS(const char *fileName) image.width = header.width; image.height = header.height; - image.mipmaps = 1; - image.compFormat = 0; + image.mipmaps = 1; // Default value, could be changed (header.mipmapCount) - if (header.ddspf.flags == 0x40 && header.ddspf.rgbBitCount == 24) // DDS_RGB, no compressed + if (header.ddspf.rgbBitCount == 16) // 16bit mode, no compressed { - image.data = (unsigned char *)malloc(header.width * header.height * 4); - unsigned char *buffer = (unsigned char *)malloc(header.width * header.height * 3); - - fread(buffer, image.width*image.height*3, 1, ddsFile); - - unsigned char *src = buffer; - unsigned char *dest = image.data; + if (header.ddspf.flags == 0x40) // no alpha channel + { + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); - for(int y = 0; y < image.height; y++) + image.format = UNCOMPRESSED_R5G6B5; + } + else if (header.ddspf.flags == 0x41) // with alpha channel { - for(int x = 0; x < image.width; x++) + if (header.ddspf.aBitMask == 0x8000) // 1bit alpha + { + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); + + unsigned char alpha = 0; + + // NOTE: Data comes as A1R5G5B5, it must be reordered to R5G5B5A1 + for (int i = 0; i < image.width*image.height; i++) + { + alpha = ((unsigned short *)image.data)[i] >> 15; + ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 1; + ((unsigned short *)image.data)[i] += alpha; + } + + image.format = UNCOMPRESSED_R5G5B5A1; + } + else if (header.ddspf.aBitMask == 0xf000) // 4bit alpha { - *dest++ = *src++; - *dest++ = *src++; - *dest++ = *src++; - *dest++ = 255; + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); + + unsigned char alpha = 0; + + // NOTE: Data comes as A4R4G4B4, it must be reordered R4G4B4A4 + for (int i = 0; i < image.width*image.height; i++) + { + alpha = ((unsigned short *)image.data)[i] >> 12; + ((unsigned short *)image.data)[i] = ((unsigned short *)image.data)[i] << 4; + ((unsigned short *)image.data)[i] += alpha; + } + + image.format = UNCOMPRESSED_R4G4B4A4; } } - - free(buffer); + } + if (header.ddspf.flags == 0x40 && header.ddspf.rgbBitCount == 24) // DDS_RGB, no compressed + { + // NOTE: not sure if this case exists... + image.data = (unsigned char *)malloc(image.width*image.height*3*sizeof(unsigned char)); + fread(image.data, image.width*image.height*3, 1, ddsFile); + + image.format = UNCOMPRESSED_R8G8B8; } else if (header.ddspf.flags == 0x41 && header.ddspf.rgbBitCount == 32) // DDS_RGBA, no compressed { - image.data = (unsigned char *)malloc(header.width * header.height * 4); - + image.data = (unsigned char *)malloc(image.width*image.height*4*sizeof(unsigned char)); fread(image.data, image.width*image.height*4, 1, ddsFile); - image.mipmaps = 1; - image.compFormat = 0; + image.format = UNCOMPRESSED_R8G8B8A8; } - else if ((header.ddspf.flags == 0x04) && (header.ddspf.fourCC > 0)) + else if (((header.ddspf.flags == 0x04) || (header.ddspf.flags == 0x05)) && (header.ddspf.fourCC > 0)) { - TraceLog(WARNING, "[%s] DDS image uses compression, not supported on OpenGL 1.1", fileName); - TraceLog(WARNING, "[%s] DDS compressed files require OpenGL 3.2+ or ES 2.0", fileName); - int bufsize; // Calculate data size, including all mipmaps - if (header.mipMapCount > 1) bufsize = header.pitchOrLinearSize * 2; + if (header.mipmapCount > 1) bufsize = header.pitchOrLinearSize*2; else bufsize = header.pitchOrLinearSize; + + TraceLog(DEBUG, "Pitch or linear size: %i", header.pitchOrLinearSize); - image.data = (unsigned char*)malloc(bufsize * sizeof(unsigned char)); + image.data = (unsigned char*)malloc(bufsize*sizeof(unsigned char)); fread(image.data, 1, bufsize, ddsFile); - // Close file pointer - fclose(ddsFile); - - image.mipmaps = header.mipMapCount; - image.compFormat = 0; + image.mipmaps = header.mipmapCount; switch(header.ddspf.fourCC) { - case FOURCC_DXT1: image.compFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; - case FOURCC_DXT3: image.compFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; - case FOURCC_DXT5: image.compFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case FOURCC_DXT1: + { + if (header.ddspf.flags == 0x04) image.format = COMPRESSED_DXT1_RGB; + else image.format = COMPRESSED_DXT1_RGBA; + } break; + case FOURCC_DXT3: image.format = COMPRESSED_DXT3_RGBA; break; + case FOURCC_DXT5: image.format = COMPRESSED_DXT5_RGBA; break; default: break; } - - // NOTE: Image num color components not required... for now... - //if (fourCC == FOURCC_DXT1) image.components = 3; - //else image.components = 4; } } + + fclose(ddsFile); // Close file pointer } return image; @@ -703,85 +844,410 @@ static ImageEx LoadDDS(const char *fileName) // Loading PKM image data (ETC1/ETC2 compression) // NOTE: KTX is the standard Khronos Group compression format (ETC1/ETC2, mipmaps) // PKM is a much simpler file format used mainly to contain a single ETC1/ETC2 compressed image (no mipmaps) -static ImageEx LoadPKM(const char *fileName) +static Image LoadPKM(const char *fileName) { - // If OpenGL ES 2.0. the following format could be supported (ETC1): - //GL_ETC1_RGB8_OES - - #ifndef GL_ETC1_RGB8_OES - #define GL_ETC1_RGB8_OES 0x8D64 - #endif - - // If OpenGL ES 3.0, the following formats are supported (ETC2/EAC): - //GL_COMPRESSED_RGB8_ETC2 - //GL_COMPRESSED_RGBA8_ETC2 - //GL_COMPRESSED_RG11_EAC - //... + // Required extensions: + // GL_OES_compressed_ETC1_RGB8_texture (ETC1) (OpenGL ES 2.0) + // GL_ARB_ES3_compatibility (ETC2/EAC) (OpenGL ES 3.0) + + // Supported tokens (defined by extensions) + // GL_ETC1_RGB8_OES 0x8D64 + // GL_COMPRESSED_RGB8_ETC2 0x9274 + // GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 // PKM file (ETC1) Header (16 bytes) typedef struct { char id[4]; // "PKM " - char version[2]; // "10" - unsigned short format; // Format = number of mipmaps = 0 (ETC1_RGB_NO_MIPMAPS) - unsigned short extWidth; // Texture width (big-endian) - unsigned short extHeight; // Texture height (big-endian) + char version[2]; // "10" or "20" + unsigned short format; // Data format (big-endian) (Check list below) + unsigned short width; // Texture width (big-endian) (origWidth rounded to multiple of 4) + unsigned short height; // Texture height (big-endian) (origHeight rounded to multiple of 4) unsigned short origWidth; // Original width (big-endian) unsigned short origHeight; // Original height (big-endian) } pkmHeader; + + // Formats list + // version 10: format: 0=ETC1_RGB, [1=ETC1_RGBA, 2=ETC1_RGB_MIP, 3=ETC1_RGBA_MIP] (not used) + // version 20: format: 0=ETC1_RGB, 1=ETC2_RGB, 2=ETC2_RGBA_OLD, 3=ETC2_RGBA, 4=ETC2_RGBA1, 5=ETC2_R, 6=ETC2_RG, 7=ETC2_SIGNED_R, 8=ETC2_SIGNED_R // NOTE: The extended width and height are the widths rounded up to a multiple of 4. - // NOTE: ETC is always 4bit per pixel (64 bits for each 4x4 block of pixels) - - // Bytes Swap (little-endian <-> big-endian) - //unsigned short data; - //unsigned short swap = ((data & 0x00FF) << 8) | ((data & 0xFF00) >> 8); - - ImageEx image; + // NOTE: ETC is always 4bit per pixel (64 bit for each 4x4 block of pixels) - unsigned short width; - unsigned short height; - unsigned short useless; + Image image; + + image.data = NULL; + image.width = 0; + image.height = 0; + image.mipmaps = 0; + image.format = 0; FILE *pkmFile = fopen(fileName, "rb"); if (pkmFile == NULL) { - TraceLog(WARNING, "[%s] PKM image file could not be opened", fileName); + TraceLog(WARNING, "[%s] PKM file could not be opened", fileName); } else { - // Verify the type of file - char filecode[4]; + pkmHeader header; - fread(filecode, 1, 4, pkmFile); + // Get the image header + fread(&header, sizeof(pkmHeader), 1, pkmFile); - if (strncmp(filecode, "PKM ", 4) != 0) + if (strncmp(header.id, "PKM ", 4) != 0) { TraceLog(WARNING, "[%s] PKM file does not seem to be a valid image", fileName); - fclose(pkmFile); } else { - // Get the surface descriptor - fread(&useless, sizeof(unsigned short), 1, pkmFile); // Discard version - fread(&useless, sizeof(unsigned short), 1, pkmFile); // Discard format - - fread(&width, sizeof(unsigned short), 1, pkmFile); // Read extended width - fread(&height, sizeof(unsigned short), 1, pkmFile); // Read extended height - - int size = (width/4)*(height/4)*8; // Total data size in bytes + // NOTE: format, width and height come as big-endian, data must be swapped to little-endian + header.format = ((header.format & 0x00FF) << 8) | ((header.format & 0xFF00) >> 8); + header.width = ((header.width & 0x00FF) << 8) | ((header.width & 0xFF00) >> 8); + header.height = ((header.height & 0x00FF) << 8) | ((header.height & 0xFF00) >> 8); + + TraceLog(INFO, "PKM (ETC) image width: %i", header.width); + TraceLog(INFO, "PKM (ETC) image height: %i", header.height); + TraceLog(INFO, "PKM (ETC) image format: %i", header.format); + + image.width = header.width; + image.height = header.height; + image.mipmaps = 1; + + int size = image.width*image.height*4/8; // Total data size in bytes image.data = (unsigned char*)malloc(size * sizeof(unsigned char)); fread(image.data, 1, size, pkmFile); - fclose(pkmFile); // Close file pointer + if (header.format == 0) image.format = COMPRESSED_ETC1_RGB; + else if (header.format == 1) image.format = COMPRESSED_ETC2_RGB; + else if (header.format == 3) image.format = COMPRESSED_ETC2_EAC_RGBA; + } + + fclose(pkmFile); // Close file pointer + } + + return image; +} + +// Load KTX compressed image data (ETC1/ETC2 compression) +static Image LoadKTX(const char *fileName) +{ + // Required extensions: + // GL_OES_compressed_ETC1_RGB8_texture (ETC1) + // GL_ARB_ES3_compatibility (ETC2/EAC) + + // Supported tokens (defined by extensions) + // GL_ETC1_RGB8_OES 0x8D64 + // GL_COMPRESSED_RGB8_ETC2 0x9274 + // GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 + + // KTX file Header (64 bytes) + // https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + typedef struct { + char id[12]; // Identifier: "«KTX 11»\r\n\x1A\n" + unsigned int endianness; // Little endian: 0x01 0x02 0x03 0x04 + unsigned int glType; // For compressed textures, glType must equal 0 + unsigned int glTypeSize; // For compressed texture data, usually 1 + unsigned int glFormat; // For compressed textures is 0 + unsigned int glInternalFormat; // Compressed internal format + unsigned int glBaseInternalFormat; // Same as glFormat (RGB, RGBA, ALPHA...) + unsigned int width; // Texture image width in pixels + unsigned int height; // Texture image height in pixels + unsigned int depth; // For 2D textures is 0 + unsigned int elements; // Number of array elements, usually 0 + unsigned int faces; // Cubemap faces, for no-cubemap = 1 + unsigned int mipmapLevels; // Non-mipmapped textures = 1 + unsigned int keyValueDataSize; // Used to encode any arbitrary data... + } ktxHeader; + + // NOTE: Before start of every mipmap data block, we have: unsigned int dataSize + + Image image; + + image.width = 0; + image.height = 0; + image.mipmaps = 0; + image.format = 0; + + FILE *ktxFile = fopen(fileName, "rb"); + + if (ktxFile == NULL) + { + TraceLog(WARNING, "[%s] KTX image file could not be opened", fileName); + } + else + { + ktxHeader header; + + // Get the image header + fread(&header, sizeof(ktxHeader), 1, ktxFile); + + if ((header.id[1] != 'K') || (header.id[2] != 'T') || (header.id[3] != 'X') || + (header.id[4] != ' ') || (header.id[5] != '1') || (header.id[6] != '1')) + { + TraceLog(WARNING, "[%s] KTX file does not seem to be a valid file", fileName); + } + else + { + image.width = header.width; + image.height = header.height; + image.mipmaps = header.mipmapLevels; + + TraceLog(DEBUG, "KTX (ETC) image width: %i", header.width); + TraceLog(DEBUG, "KTX (ETC) image height: %i", header.height); + TraceLog(DEBUG, "KTX (ETC) image format: 0x%x", header.glInternalFormat); + + unsigned char unused; + + if (header.keyValueDataSize > 0) + { + for (int i = 0; i < header.keyValueDataSize; i++) fread(&unused, 1, 1, ktxFile); + } + + int dataSize; + fread(&dataSize, sizeof(unsigned int), 1, ktxFile); + + image.data = (unsigned char*)malloc(dataSize * sizeof(unsigned char)); - image.width = width; - image.height = height; + fread(image.data, 1, dataSize, ktxFile); + + if (header.glInternalFormat == 0x8D64) image.format = COMPRESSED_ETC1_RGB; + else if (header.glInternalFormat == 0x9274) image.format = COMPRESSED_ETC2_RGB; + else if (header.glInternalFormat == 0x9278) image.format = COMPRESSED_ETC2_EAC_RGBA; + } + + fclose(ktxFile); // Close file pointer + } + + return image; +} + +// Loading PVR image data (uncompressed or PVRT compression) +// NOTE: PVR v2 not supported, use PVR v3 instead +static Image LoadPVR(const char *fileName) +{ + // Required extension: + // GL_IMG_texture_compression_pvrtc + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 + // GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 + + // PVR file v2 Header (52 bytes) + typedef struct { + unsigned int headerLength; + unsigned int height; + unsigned int width; + unsigned int numMipmaps; + unsigned int flags; + unsigned int dataLength; + unsigned int bpp; + unsigned int bitmaskRed; + unsigned int bitmaskGreen; + unsigned int bitmaskBlue; + unsigned int bitmaskAlpha; + unsigned int pvrTag; + unsigned int numSurfs; + } pvrHeaderV2; + + // PVR file v3 Header (52 bytes) + // NOTE: After it could be metadata (15 bytes?) + typedef struct { + char id[4]; + unsigned int flags; + unsigned char channels[4]; // pixelFormat high part + unsigned char channelDepth[4]; // pixelFormat low part + unsigned int colourSpace; + unsigned int channelType; + unsigned int height; + unsigned int width; + unsigned int depth; + unsigned int numSurfaces; + unsigned int numFaces; + unsigned int numMipmaps; + unsigned int metaDataSize; + } pvrHeaderV3; + + // Metadata (usually 15 bytes) + typedef struct { + unsigned int devFOURCC; + unsigned int key; + unsigned int dataSize; // Not used? + unsigned char *data; // Not used? + } pvrMetadata; + + Image image; + + image.data = NULL; + image.width = 0; + image.height = 0; + image.mipmaps = 0; + image.format = 0; + + FILE *pvrFile = fopen(fileName, "rb"); + + if (pvrFile == NULL) + { + TraceLog(WARNING, "[%s] PVR file could not be opened", fileName); + } + else + { + // Check PVR image version + unsigned char pvrVersion = 0; + fread(&pvrVersion, sizeof(unsigned char), 1, pvrFile); + fseek(pvrFile, 0, SEEK_SET); + + // Load different PVR data formats + if (pvrVersion == 0x50) + { + pvrHeaderV3 header; + + // Get PVR image header + fread(&header, sizeof(pvrHeaderV3), 1, pvrFile); + + if ((header.id[0] != 'P') || (header.id[1] != 'V') || (header.id[2] != 'R') || (header.id[3] != 3)) + { + TraceLog(WARNING, "[%s] PVR file does not seem to be a valid image", fileName); + } + else + { + image.width = header.width; + image.height = header.height; + image.mipmaps = header.numMipmaps; + + // Check data format + if (((header.channels[0] == 'l') && (header.channels[1] == 0)) && (header.channelDepth[0] == 8)) image.format = UNCOMPRESSED_GRAYSCALE; + else if (((header.channels[0] == 'l') && (header.channels[1] == 'a')) && ((header.channelDepth[0] == 8) && (header.channelDepth[1] == 8))) image.format = UNCOMPRESSED_GRAY_ALPHA; + else if ((header.channels[0] == 'r') && (header.channels[1] == 'g') && (header.channels[2] == 'b')) + { + if (header.channels[3] == 'a') + { + if ((header.channelDepth[0] == 5) && (header.channelDepth[1] == 5) && (header.channelDepth[2] == 5) && (header.channelDepth[3] == 1)) image.format = UNCOMPRESSED_R5G5B5A1; + else if ((header.channelDepth[0] == 4) && (header.channelDepth[1] == 4) && (header.channelDepth[2] == 4) && (header.channelDepth[3] == 4)) image.format = UNCOMPRESSED_R4G4B4A4; + else if ((header.channelDepth[0] == 8) && (header.channelDepth[1] == 8) && (header.channelDepth[2] == 8) && (header.channelDepth[3] == 8)) image.format = UNCOMPRESSED_R8G8B8A8; + } + else if (header.channels[3] == 0) + { + if ((header.channelDepth[0] == 5) && (header.channelDepth[1] == 6) && (header.channelDepth[2] == 5)) image.format = UNCOMPRESSED_R5G6B5; + else if ((header.channelDepth[0] == 8) && (header.channelDepth[1] == 8) && (header.channelDepth[2] == 8)) image.format = UNCOMPRESSED_R8G8B8; + } + } + else if (header.channels[0] == 2) image.format = COMPRESSED_PVRT_RGB; + else if (header.channels[0] == 3) image.format = COMPRESSED_PVRT_RGBA; + + // Skip meta data header + unsigned char unused = 0; + for (int i = 0; i < header.metaDataSize; i++) fread(&unused, sizeof(unsigned char), 1, pvrFile); + + // Calculate data size (depends on format) + int bpp = 0; + + switch (image.format) + { + case UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case UNCOMPRESSED_GRAY_ALPHA: + case UNCOMPRESSED_R5G5B5A1: + case UNCOMPRESSED_R5G6B5: + case UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case UNCOMPRESSED_R8G8B8: bpp = 24; break; + case COMPRESSED_PVRT_RGB: + case COMPRESSED_PVRT_RGBA: bpp = 4; break; + default: break; + } + + int dataSize = image.width*image.height*bpp/8; // Total data size in bytes + image.data = (unsigned char*)malloc(dataSize*sizeof(unsigned char)); + + // Read data from file + fread(image.data, dataSize, 1, pvrFile); + } + } + else if (pvrVersion == 52) TraceLog(INFO, "PVR v2 not supported, update your files to PVR v3"); + + fclose(pvrFile); // Close file pointer + } + + return image; +} + +// Load ASTC compressed image data (ASTC compression) +static Image LoadASTC(const char *fileName) +{ + // Required extensions: + // GL_KHR_texture_compression_astc_hdr + // GL_KHR_texture_compression_astc_ldr + + // Supported tokens (defined by extensions) + // GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 + // GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 + + // ASTC file Header (16 bytes) + typedef struct { + unsigned char id[4]; // Signature: 0x13 0xAB 0xA1 0x5C + unsigned char blockX; // Block X dimensions + unsigned char blockY; // Block Y dimensions + unsigned char blockZ; // Block Z dimensions (1 for 2D images) + unsigned char width[3]; // Image width in pixels (24bit value) + unsigned char height[3]; // Image height in pixels (24bit value) + unsigned char lenght[3]; // Image Z-size (1 for 2D images) + } astcHeader; + + Image image; + + image.data = NULL; + image.width = 0; + image.height = 0; + image.mipmaps = 0; + image.format = 0; + + FILE *astcFile = fopen(fileName, "rb"); + + if (astcFile == NULL) + { + TraceLog(WARNING, "[%s] ASTC file could not be opened", fileName); + } + else + { + astcHeader header; + + // Get ASTC image header + fread(&header, sizeof(astcHeader), 1, astcFile); + + if ((header.id[3] != 0x5c) || (header.id[2] != 0xa1) || (header.id[1] != 0xab) || (header.id[0] != 0x13)) + { + TraceLog(WARNING, "[%s] ASTC file does not seem to be a valid image", fileName); + } + else + { + // NOTE: Assuming Little Endian (could it be wrong?) + image.width = 0x00000000 | ((int)header.width[2] << 16) | ((int)header.width[1] << 8) | ((int)header.width[0]); + image.height = 0x00000000 | ((int)header.height[2] << 16) | ((int)header.height[1] << 8) | ((int)header.height[0]); image.mipmaps = 1; - image.compFormat = GL_ETC1_RGB8_OES; + + TraceLog(DEBUG, "ASTC image width: %i", image.width); + TraceLog(DEBUG, "ASTC image height: %i", image.height); + TraceLog(DEBUG, "ASTC image blocks: %ix%i", header.blockX, header.blockY); + + // NOTE: Each block is always stored in 128bit so we can calculate the bpp + int bpp = 128/(header.blockX*header.blockY); + + // NOTE: Currently we only support 2 blocks configurations: 4x4 and 8x8 + if ((bpp == 8) || (bpp == 2)) + { + int dataSize = image.width*image.height*bpp/8; // Data size in bytes + + image.data = (unsigned char *)malloc(dataSize*sizeof(unsigned char)); + fread(image.data, dataSize, 1, astcFile); + + if (bpp == 8) image.format = COMPRESSED_ASTC_4x4_RGBA; + else if (bpp == 2) image.format = COMPRESSED_ASTC_4x4_RGBA; + } + else TraceLog(WARNING, "[%s] ASTC block size configuration not supported", fileName); } + + fclose(astcFile); } return image; diff --git a/src/utils.c b/src/utils.c index c3c20b47..8e42e533 100644 --- a/src/utils.c +++ b/src/utils.c @@ -79,7 +79,7 @@ unsigned char *DecompressData(const unsigned char *data, unsigned long compSize, pUncomp = (mz_uint8 *)malloc((size_t)uncompSize); // Check correct memory allocation - if (!pUncomp) + if (pUncomp == NULL) { TraceLog(WARNING, "Out of memory while decompressing data"); } @@ -235,18 +235,18 @@ void TraceLog(int msgType, const char *text, ...) } // Initialize asset manager from android app -void InitAssetManager(AAssetManager *manager) +void InitAssetManager(AAssetManager *manager) { assetManager = manager; } // Replacement for fopen -FILE *android_fopen(const char *fileName, const char *mode) +FILE *android_fopen(const char *fileName, const char *mode) { if (mode[0] == 'w') return NULL; AAsset *asset = AAssetManager_open(assetManager, fileName, 0); - + if(!asset) return NULL; return funopen(asset, android_read, android_write, android_seek, android_close); @@ -292,24 +292,24 @@ int GetNextPOT(int num) // Module specific Functions Definition //---------------------------------------------------------------------------------- #if defined(PLATFORM_ANDROID) -static int android_read(void *cookie, char *buf, int size) +static int android_read(void *cookie, char *buf, int size) { return AAsset_read((AAsset *)cookie, buf, size); } -static int android_write(void *cookie, const char *buf, int size) +static int android_write(void *cookie, const char *buf, int size) { TraceLog(ERROR, "Can't provide write access to the APK"); return EACCES; } -static fpos_t android_seek(void *cookie, fpos_t offset, int whence) +static fpos_t android_seek(void *cookie, fpos_t offset, int whence) { return AAsset_seek((AAsset *)cookie, offset, whence); } -static int android_close(void *cookie) +static int android_close(void *cookie) { AAsset_close((AAsset *)cookie); return 0; |
