diff --git a/examples/models_custom_shader.c b/examples/models_custom_shader.c new file mode 100644 index 00000000..5430ed65 --- /dev/null +++ b/examples/models_custom_shader.c @@ -0,0 +1,86 @@ +/******************************************************************************************* +* +* raylib [models] example - Load and draw a 3d model (OBJ) +* +* This example has been created using raylib 1.0 (www.raylib.com) +* raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) +* +* Copyright (c) 2014 Ramon Santamaria (Ray San - raysan@raysanweb.com) +* +********************************************************************************************/ + +#include "raylib.h" + +int main() +{ + // Initialization + //-------------------------------------------------------------------------------------- + int screenWidth = 1280; + int screenHeight = 720; + + InitWindow(screenWidth, screenHeight, "raylib [models] example - custom shader"); + + // Define the camera to look into our 3d world + Camera camera = {{ 10.0, 8.0, 10.0 }, { 0.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 }}; + + Texture2D texture = LoadTexture("resources/catsham.png"); // Load model texture + Shader shader = LoadShader("resources/shaders/custom.vs", "resources/shaders/custom.fs"); + //Shader poste = LoadShader("resources/shaders/custom.vs", "resources/shaders/pixel.fs"); + + Model cat = LoadModel("resources/cat.obj"); // Load OBJ model + + SetModelTexture(&cat, texture); // Bind texture to model + //SetModelShader(&cat, poste); + + Vector3 catPosition = { 0.0, 0.0, 0.0 }; // Set model position + + SetPostproShader(shader); + + SetTargetFPS(60); // Set our game to run at 60 frames-per-second + //-------------------------------------------------------------------------------------- + + // Main game loop + while (!WindowShouldClose()) // Detect window close button or ESC key + { + // Update + //---------------------------------------------------------------------------------- + if (IsKeyDown(KEY_LEFT)) catPosition.x -= 0.2; + if (IsKeyDown(KEY_RIGHT)) catPosition.x += 0.2; + if (IsKeyDown(KEY_UP)) catPosition.z -= 0.2; + if (IsKeyDown(KEY_DOWN)) catPosition.z += 0.2; + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + Begin3dMode(camera); + + DrawModel(cat, catPosition, 0.1f, WHITE); // Draw 3d model with texture + + DrawGrid(10.0, 1.0); // Draw a grid + + DrawGizmo(catPosition); // Draw gizmo + + End3dMode(); + + DrawFPS(10, 10); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + UnloadTexture(texture); // Unload texture + UnloadModel(cat); // Unload model + UnloadShader(shader); + //UnloadShader(poste); + + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/resources/shaders/base.vs b/examples/resources/shaders/base.vs new file mode 100644 index 00000000..78e543b7 --- /dev/null +++ b/examples/resources/shaders/base.vs @@ -0,0 +1,19 @@ +#version 110 + +attribute vec3 vertexPosition; +attribute vec2 vertexTexCoord; +attribute vec4 vertexColor; + +uniform mat4 projectionMatrix; +uniform mat4 modelviewMatrix; + +varying vec2 fragTexCoord; +varying vec4 fragColor; + +void main() +{ + fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + + gl_Position = projectionMatrix*modelviewMatrix*vec4(vertexPosition, 1.0); +} \ No newline at end of file diff --git a/examples/resources/shaders/custom.fs b/examples/resources/shaders/custom.fs new file mode 100644 index 00000000..1e53933b --- /dev/null +++ b/examples/resources/shaders/custom.fs @@ -0,0 +1,16 @@ +#version 330 + +uniform sampler2D texture0; +varying vec2 fragTexCoord; + +uniform vec4 tintColor; + +void main() +{ + vec4 base = texture2D(texture0, fragTexCoord)*tintColor; + + // Convert to grayscale using NTSC conversion weights + float gray = dot(base.rgb, vec3(0.299, 0.587, 0.114)); + + gl_FragColor = vec4(gray, gray, gray, tintColor.a); +} \ No newline at end of file diff --git a/examples/resources/shaders/custom.vs b/examples/resources/shaders/custom.vs new file mode 100644 index 00000000..629c954d --- /dev/null +++ b/examples/resources/shaders/custom.vs @@ -0,0 +1,16 @@ +#version 330 + +attribute vec3 vertexPosition; +attribute vec2 vertexTexCoord; +attribute vec3 vertexNormal; + +uniform mat4 projectionMatrix; +uniform mat4 modelviewMatrix; + +varying vec2 fragTexCoord; + +void main() +{ + fragTexCoord = vertexTexCoord; + gl_Position = projectionMatrix*modelviewMatrix*vec4(vertexPosition, 1.0); +} \ No newline at end of file diff --git a/examples/resources/shaders/grayscale.fs b/examples/resources/shaders/grayscale.fs new file mode 100644 index 00000000..1b778871 --- /dev/null +++ b/examples/resources/shaders/grayscale.fs @@ -0,0 +1,15 @@ +#version 110 + +uniform sampler2D texture0; +varying vec2 fragTexCoord; +varying vec4 fragColor; + +void main() +{ + vec4 base = texture2D(texture0, fragTexCoord)*fragColor; + + // Convert to grayscale using NTSC conversion weights + float gray = dot(base.rgb, vec3(0.299, 0.587, 0.114)); + + gl_FragColor = vec4(gray, gray, gray, base.a); +} \ No newline at end of file diff --git a/src/camera.c b/src/camera.c new file mode 100644 index 00000000..b960afdf --- /dev/null +++ b/src/camera.c @@ -0,0 +1,476 @@ +/********************************************************************************************** +* +* 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 + +//---------------------------------------------------------------------------------- +// 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 12 +#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) + { + 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; +} + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/core.c b/src/core.c index d83c14f7..384e043a 100644 --- a/src/core.c +++ b/src/core.c @@ -45,7 +45,7 @@ #include // Standard input / output lib #include // Declares malloc() and free() for memory management, rand(), atexit() #include // Required for typedef unsigned long long int uint64_t, used by hi-res timer -#include // Useful to initialize random seed - Android/RPI hi-res timer +#include // Useful to initialize random seed - Android/RPI hi-res timer (NOTE: Linux only!) #include // Math related functions, tan() used to set perspective #include // String function definitions, memset() #include // Macros for reporting and retrieving error conditions through error codes @@ -99,48 +99,6 @@ //---------------------------------------------------------------------------------- #define MAX_TOUCH_POINTS 256 -// Camera System configuration -//---------------------------------------------------------------------------------- -// 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 12 -#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 - //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- @@ -207,7 +165,6 @@ static bool cursorOnScreen = false; // Tracks if cursor is inside client static Texture2D cursor; // Cursor texture static Vector2 mousePosition; -static bool cursorHidden; static char previousKeyState[512] = { 0 }; // Required to check if key pressed/released once static char currentKeyState[512] = { 0 }; // Required to check if key pressed/released once @@ -225,6 +182,8 @@ static int exitKey = KEY_ESCAPE; // Default exit key (ESC) static int lastKeyPressed = -1; #endif +static bool cursorHidden; + static double currentTime, previousTime; // Used to track timmings static double updateTime, drawTime; // Time measures for update and draw static double frameTime; // Time measure for one frame @@ -233,17 +192,6 @@ static double targetTime = 0.0; // Desired time for one frame, if 0 static char configFlags = 0; static bool showLogo = false; -// Camera variables -static int cameraMode = CAMERA_CUSTOM; -static Camera currentCamera; -static Camera internalCamera = {{2,0,2},{0,0,0},{0,1,0}}; -static Vector2 cameraAngle = { 0, 0 }; -static float cameraTargetDistance = 5; -static Vector2 cameraMousePosition = { 0, 0 }; -static Vector2 cameraMouseVariation = { 0, 0 }; -static int cameraMovementCounter = 0; -static bool cameraUseGravity = true; - // Shaders variables static bool enabledPostpro = false; @@ -306,8 +254,6 @@ static void TakeScreenshot(void); static void AndroidCommandCallback(struct android_app *app, int32_t cmd); // Process Android activity lifecycle commands #endif -static void ProcessCamera(Camera *camera, Vector3 *playerPosition); - //---------------------------------------------------------------------------------- // Module Functions Definition - Window and OpenGL Context Functions //---------------------------------------------------------------------------------- @@ -600,10 +546,7 @@ void Begin3dMode(Camera camera) rlLoadIdentity(); // Reset current matrix (MODELVIEW) // Setup Camera view - if (cameraMode == CAMERA_CUSTOM) currentCamera = camera; - else currentCamera = internalCamera; - - Matrix view = MatrixLookAt(currentCamera.position, currentCamera.target, currentCamera.up); + Matrix view = MatrixLookAt(camera.position, camera.target, camera.up); rlMultMatrixf(GetMatrixVector(view)); // Multiply MODELVIEW matrix by view matrix (camera) } @@ -707,7 +650,7 @@ Ray GetMouseRay(Vector2 mousePosition, Camera camera) Ray ray; Matrix proj = MatrixIdentity(); - Matrix view = MatrixLookAt(currentCamera.position, currentCamera.target, currentCamera.up); + 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); @@ -725,7 +668,7 @@ Ray GetMouseRay(Vector2 mousePosition, Camera camera) Vector3 nearPoint = { mousePosition.x, realy, 0.0f }; Vector3 farPoint = { mousePosition.x, realy, 1.0f }; - nearPoint = internalCamera.position; + //nearPoint = internalCamera.position; farPoint = rlglUnproject(farPoint, proj, view); Vector3 direction = VectorSubtract(farPoint, nearPoint); @@ -734,72 +677,9 @@ Ray GetMouseRay(Vector2 mousePosition, Camera camera) ray.position = nearPoint; ray.direction = direction; - // Test - Vector2 screenPos; - screenPos.x = (mousePosition.x / (float)GetScreenWidth() * 2.0) - 1.0f; - screenPos.y = (mousePosition.y / (float)GetScreenHeight() * 2.0) - 1.0f; - - direction = VectorSubtract(internalCamera.target, internalCamera.position); - - printf("/nScreenPos %f, %f", screenPos.x, screenPos.y); - - Matrix rotate; - rotate = MatrixIdentity(); - rotate = MatrixRotate(0, 45*DEG2RAD*screenPos.y, 0); - VectorTransform(&direction, rotate); - - VectorNormalize(&direction); - - ray.position = internalCamera.position; - ray.direction = direction; - return ray; } -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; -} - -Camera UpdateCamera(Vector3 *position) -{ - // Calculate camera - if (cameraMode != CAMERA_CUSTOM) ProcessCamera(&internalCamera, position); - - return internalCamera; -} - //---------------------------------------------------------------------------------- // Module Functions Definition - Input (Keyboard, Mouse, Gamepad) Functions //---------------------------------------------------------------------------------- @@ -915,8 +795,8 @@ int GetMouseWheelMove(void) { return previousMouseWheelY; } -#endif +// Hide mouse cursor void HideCursor() { #if defined(PLATFORM_DESKTOP) @@ -936,6 +816,7 @@ void HideCursor() cursorHidden = true; } +// Show mouse cursor void ShowCursor() { #if defined(PLATFORM_DESKTOP) @@ -948,10 +829,12 @@ void ShowCursor() cursorHidden = false; } +// Check if mouse cursor is hidden bool IsCursorHidden() { return cursorHidden; } +#endif // TODO: Enable gamepad usage on Rapsberry Pi // NOTE: emscripten not implemented @@ -1105,6 +988,18 @@ void SetPostproShader(Shader shader) } } +// Set custom shader to be used in batch draw +void SetCustomShader(Shader shader) +{ + rlglSetCustomShader(shader); +} + +// Set default shader to be used in batch draw +void SetDefaultShader(void) +{ + rlglSetDefaultShader(); +} + //---------------------------------------------------------------------------------- // Module specific Functions Definition //---------------------------------------------------------------------------------- @@ -2161,260 +2056,3 @@ static void LogoAnimation(void) showLogo = false; // Prevent for repeating when reloading window (Android) } - -// 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) - { - HideCursor(); - if (GetMousePosition().x < GetScreenHeight() / 3) SetMousePosition((Vector2){ screenWidth - GetScreenHeight() / 3, GetMousePosition().y}); - else if (GetMousePosition().y < GetScreenHeight() / 3) SetMousePosition((Vector2){ GetMousePosition().x, screenHeight - GetScreenHeight() / 3}); - else if (GetMousePosition().x > screenWidth - GetScreenHeight() / 3) SetMousePosition((Vector2) { GetScreenHeight() / 3, GetMousePosition().y}); - else if (GetMousePosition().y > screenHeight - 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: - { - // Pass to orbiting camera - if (IsKeyPressed('O')) cameraMode = CAMERA_ORBITAL; - - // 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(KEY_LEFT_ALT)) - { - if (IsKeyDown(KEY_LEFT_CONTROL)) - { - // Camera smooth zoom - if (IsMouseButtonDown(MOUSE_MIDDLE_BUTTON)) cameraTargetDistance += (cameraMouseVariation.y * FREE_CAMERA_SMOOTH_ZOOM_SENSITIVITY); - } - // Camera orientation calculation - else if (IsMouseButtonDown(MOUSE_MIDDLE_BUTTON)) - { - // 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(MOUSE_MIDDLE_BUTTON)) - { - 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('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_ORBITAL: - { - // Pass to free camera - if (IsKeyPressed('O')) cameraMode = CAMERA_FREE; - - 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('W')) - { - 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('S')) - { - 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('A')) - { - playerPosition->x -= cos(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; - playerPosition->z += sin(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; - - isMoving = true; - } - else if (IsKeyDown('D')) - { - playerPosition->x += cos(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; - playerPosition->z -= sin(cameraAngle.x) / PLAYER_MOVEMENT_DIVIDER; - - isMoving = true; - } - - if (IsKeyDown('E')) - { - if (!cameraUseGravity) playerPosition->y += 1 / PLAYER_MOVEMENT_DIVIDER; - } - else if (IsKeyDown('Q')) - { - if (!cameraUseGravity) playerPosition->y -= 1 / PLAYER_MOVEMENT_DIVIDER; - } - - if (cameraMode == CAMERA_THIRD_PERSON) - { - // Camera orientation calculation - // Get the mouse sensitivity - cameraAngle.x += cameraMouseVariation.x * -THIRD_PERSON_MOUSE_SENSITIVITY; - cameraAngle.y += cameraMouseVariation.y * -THIRD_PERSON_MOUSE_SENSITIVITY; - - // 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 * -FIRST_PERSON_MOUSE_SENSITIVITY; - cameraAngle.y += cameraMouseVariation.y * -FIRST_PERSON_MOUSE_SENSITIVITY; - - // 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 -} \ No newline at end of file diff --git a/src/gestures.c b/src/gestures.c index 00b57e4c..2574aa02 100644 --- a/src/gestures.c +++ b/src/gestures.c @@ -30,9 +30,14 @@ #include // malloc(), free() #include // printf(), fprintf() #include // Used for ... -#include // Used for clock functions #include // Defines int32_t, int64_t +#if defined(_WIN32) + //#include +#elif defined(__linux) + #include // Used for clock functions +#endif + #if defined(PLATFORM_ANDROID) #include // Java native interface #include // Android sensors functions @@ -75,15 +80,10 @@ typedef struct { // Global Variables Definition //---------------------------------------------------------------------------------- -// typedef static GestureType gestureType = TYPE_MOTIONLESS; - -// Gestures detection variables +static double eventTime = 0; //static int32_t touchId; // Not used... -// Event -static int64_t eventTime = 0; - // Tap // Our initial press position on tap static Vector2 initialTapPosition = { 0, 0 }; @@ -127,7 +127,7 @@ static float pinchDelta = 0; // Detected gesture static int currentGesture = GESTURE_NONE; -static float touchX, touchY; +static Vector2 touchPosition; //---------------------------------------------------------------------------------- // Module specific Functions Declaration @@ -141,7 +141,7 @@ static float OnPinch(); static void SetDualInput(GestureEvent event); static float Distance(Vector2 v1, Vector2 v2); static float DotProduct(Vector2 v1, Vector2 v2); -static int GetCurrentTime(); +static double GetCurrentTime(); #if defined(PLATFORM_WEB) static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); @@ -158,9 +158,7 @@ static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) // Returns tap position XY extern Vector2 GetRawPosition(void) { - Vector2 position = { touchX, touchY }; - - return position; + return touchPosition; } // Check if a gesture have been detected @@ -531,13 +529,27 @@ static float DotProduct(Vector2 v1, Vector2 v2) return result; } -static int GetCurrentTime() +static double GetCurrentTime() { +#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 + + return ((double)nowTime/10000000.0); // Return 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 - return nowTime / 1000000; // Return time in miliseconds + return ((double)nowTime/1000000.0); // Return time in miliseconds +#endif } #if defined(PLATFORM_ANDROID) @@ -545,23 +557,21 @@ static int GetCurrentTime() static int32_t AndroidInputCallback(struct android_app *app, AInputEvent *event) { int type = AInputEvent_getType(event); - //int32_t key = 0; if (type == AINPUT_EVENT_TYPE_MOTION) { - touchX = AMotionEvent_getX(event, 0); - touchY = AMotionEvent_getY(event, 0); + touchPosition.x = AMotionEvent_getX(event, 0); + touchPosition.y = AMotionEvent_getY(event, 0); } else if (type == AINPUT_EVENT_TYPE_KEY) { - //key = AKeyEvent_getKeyCode(event); + //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 @@ -609,6 +619,8 @@ static EM_BOOL EmscriptenInputCallback(int eventType, const EmscriptenTouchEvent } */ + touchPosition = gestureEvent.position[0]; + GestureEvent gestureEvent; // Action diff --git a/src/models.c b/src/models.c index ffb077d4..be1b949a 100644 --- a/src/models.c +++ b/src/models.c @@ -599,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? @@ -628,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 @@ -649,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 @@ -691,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() @@ -713,17 +717,19 @@ Model LoadHeightmap(Image heightmap, float maxHeight) } // 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 @@ -775,9 +781,9 @@ Model LoadCubicmap(Image cubesmap) Vector3 v8 = { x + w/2, 0, z + h/2 }; // 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!) //---------------------------------------------- @@ -832,10 +838,10 @@ Model LoadCubicmap(Image cubesmap) 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 @@ -865,9 +871,9 @@ Model LoadCubicmap(Image cubesmap) } 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 @@ -896,10 +902,10 @@ Model LoadCubicmap(Image cubesmap) 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 @@ -929,9 +935,9 @@ Model LoadCubicmap(Image cubesmap) } 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 @@ -961,9 +967,9 @@ Model LoadCubicmap(Image cubesmap) } } // 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-v2-v3, v1-v3-v4) mapVertices[vCounter] = v1; @@ -1065,6 +1071,8 @@ 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 @@ -1335,6 +1343,8 @@ bool CheckCollisionBoxSphere(Vector3 minBBox, Vector3 maxBBox, Vector3 centerSph // 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 }; @@ -1347,8 +1357,8 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p // Multiple Axis -------------------------------------------------------------------------------------------- // Axis x-, y- - if ((cubicmap.pixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) && - (cubicmap.pixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0)) + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0)) { if (((playerPosition->x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) @@ -1360,8 +1370,8 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p } // Axis x-, y+ - if ((cubicmap.pixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) && - (cubicmap.pixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0)) + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0)) { if (((playerPosition->x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) @@ -1373,8 +1383,8 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p } // Axis x+, y- - if ((cubicmap.pixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) && - (cubicmap.pixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0)) + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) && + (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0)) { if (((playerPosition->x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) @@ -1386,8 +1396,8 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p } // Axis x+, y+ - if ((cubicmap.pixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) && - (cubicmap.pixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0)) + if ((cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) && + (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0)) { if (((playerPosition->x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) @@ -1401,7 +1411,7 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p // Single Axis --------------------------------------------------------------------------------------------------- // Axis x- - if (cubicmap.pixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) + if (cubicmapPixels[locationCellY * cubicmap.width + (locationCellX - 1)].r != 0) { if ((playerPosition->x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) { @@ -1410,7 +1420,7 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p } } // Axis x+ - if (cubicmap.pixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) + if (cubicmapPixels[locationCellY * cubicmap.width + (locationCellX + 1)].r != 0) { if ((playerPosition->x + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) { @@ -1419,7 +1429,7 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p } } // Axis y- - if (cubicmap.pixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0) + if (cubicmapPixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r != 0) { if ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius) { @@ -1428,7 +1438,7 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p } } // Axis y+ - if (cubicmap.pixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0) + if (cubicmapPixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r != 0) { if ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius) { @@ -1440,9 +1450,9 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p // Diagonals ------------------------------------------------------------------------------------------------------- // Axis x-, y- - if ((cubicmap.pixels[locationCellY * cubicmap.width + (locationCellX - 1)].r == 0) && - (cubicmap.pixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r == 0) && - (cubicmap.pixels[(locationCellY - 1) * cubicmap.width + (locationCellX - 1)].r != 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 + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) @@ -1460,9 +1470,9 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p } // Axis x-, y+ - if ((cubicmap.pixels[locationCellY * cubicmap.width + (locationCellX - 1)].r == 0) && - (cubicmap.pixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r == 0) && - (cubicmap.pixels[(locationCellY + 1) * cubicmap.width + (locationCellX - 1)].r != 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 + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX < radius) && ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) @@ -1480,9 +1490,9 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p } // Axis x+, y- - if ((cubicmap.pixels[locationCellY * cubicmap.width + (locationCellX + 1)].r == 0) && - (cubicmap.pixels[(locationCellY - 1) * cubicmap.width + (locationCellX)].r == 0) && - (cubicmap.pixels[(locationCellY - 1) * cubicmap.width + (locationCellX + 1)].r != 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 + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY < radius)) @@ -1500,9 +1510,9 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p } // Axis x+, y+ - if ((cubicmap.pixels[locationCellY * cubicmap.width + (locationCellX + 1)].r == 0) && - (cubicmap.pixels[(locationCellY + 1) * cubicmap.width + (locationCellX)].r == 0) && - (cubicmap.pixels[(locationCellY + 1) * cubicmap.width + (locationCellX + 1)].r != 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 + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellX > 1 - radius) && ((playerPosition->z + CUBIC_MAP_HALF_BLOCK_SIZE) - locationCellY > 1 - radius)) @@ -1531,6 +1541,8 @@ Vector3 ResolveCollisionCubicmap(Image cubicmap, Vector3 mapPosition, Vector3 *p playerPosition->y = (1.5 - radius) - 0.01; impactDirection = (Vector3) { impactDirection.x, 1, impactDirection.z}; } + + free(cubicmapPixels); return impactDirection; } diff --git a/src/raylib.h b/src/raylib.h index e804da62..bd1692d2 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -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) @@ -183,20 +182,6 @@ typedef enum { false, true } bool; #endif -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; - // byte type typedef unsigned char byte; @@ -240,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) @@ -337,9 +326,11 @@ typedef struct Wave { short channels; } Wave; -// Texture formats (support depends on OpenGL version) +// 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) @@ -354,9 +345,25 @@ typedef enum { COMPRESSED_ETC2_EAC_RGBA, // 8 bpp COMPRESSED_PVRT_RGB, // 4 bpp COMPRESSED_PVRT_RGBA, // 4 bpp - /*COMPRESSED_ASTC_RGBA_4x4*/ // 8 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 @@ -402,16 +409,27 @@ 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 SetCameraMode(int mode); // Multiple camera modes available -Camera UpdateCamera(Vector3 *position); // Update camera with position (when using internal camera) - 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); + //------------------------------------------------------------------------------------ // Input Handling Functions (Module: core) //------------------------------------------------------------------------------------ @@ -451,6 +469,7 @@ int GetTouchX(void); // Returns touch positio 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); @@ -504,6 +523,8 @@ Texture2D CreateTexture(Image image, bool genMipmaps); 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 SetPixelData(Image *image, Color *pixels, int format); // Set image data from 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 diff --git a/src/rlgl.c b/src/rlgl.c index a5c40815..fbea84a2 100644 --- a/src/rlgl.c +++ b/src/rlgl.c @@ -143,6 +143,7 @@ typedef struct { typedef struct { GLuint textureId; int vertexCount; + // TODO: DrawState state -> Blending mode, shader } DrawCall; // pixel type (same as Color type) @@ -175,6 +176,7 @@ static VertexPositionColorTextureIndexBuffer quads; // Shader Programs static Shader defaultShader, simpleShader; +static Shader currentShader; // By default, defaultShader // Vertex Array Objects (VAO) static GLuint vaoLines, vaoTriangles, vaoQuads; @@ -330,14 +332,14 @@ void rlRotatef(float angleDeg, float x, float y, float z) Matrix rotation = 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); + if (x == 1) rotation = MatrixRotateX(angleDeg*DEG2RAD); + else if (y == 1) rotation = MatrixRotateY(angleDeg*DEG2RAD); + else if (z == 1) rotation = MatrixRotateZ(angleDeg*DEG2RAD); // OPTION 2: Requires review... - Vector3 axis = (Vector3){ x, y, z }; - VectorNormalize(&axis); - rotation = MatrixRotateY(angleDeg*DEG2RAD); //MatrixFromAxisAngle(axis, angleDeg*DEG2RAD); + //Vector3 axis = (Vector3){ x, y, z }; + //VectorNormalize(&axis); + //rotation = MatrixRotateY(angleDeg*DEG2RAD); //MatrixFromAxisAngle(axis, angleDeg*DEG2RAD); // OPTION 3: TODO: Review, it doesn't work! //Vector3 vec = (Vector3){ x, y, z }; @@ -921,6 +923,8 @@ void rlglInit(void) defaultShader = LoadDefaultShader(); simpleShader = LoadSimpleShader(); //customShader = rlglLoadShader("custom.vs", "custom.fs"); // Works ok + + currentShader = defaultShader; InitializeBuffers(); // Init vertex arrays InitializeBuffersGPU(); // Init VBO and VAO @@ -1094,6 +1098,7 @@ void rlglClose(void) #endif } +// Drawing batches: triangles, quads, lines void rlglDraw(void) { #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) @@ -1101,11 +1106,11 @@ void rlglDraw(void) if ((lines.vCounter > 0) || (triangles.vCounter > 0) || (quads.vCounter > 0)) { - glUseProgram(defaultShader.id); + glUseProgram(currentShader.id); - glUniformMatrix4fv(defaultShader.projectionLoc, 1, false, GetMatrixVector(projection)); - glUniformMatrix4fv(defaultShader.modelviewLoc, 1, false, GetMatrixVector(modelview)); - glUniform1i(defaultShader.textureLoc, 0); + glUniformMatrix4fv(currentShader.projectionLoc, 1, false, GetMatrixVector(projection)); + glUniformMatrix4fv(currentShader.modelviewLoc, 1, false, GetMatrixVector(modelview)); + glUniform1i(currentShader.textureLoc, 0); } // NOTE: We draw in this order: triangle shapes, textured quads and lines @@ -1121,12 +1126,12 @@ void rlglDraw(void) else { glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[0]); - glVertexAttribPointer(defaultShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultShader.vertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); glBindBuffer(GL_ARRAY_BUFFER, trianglesBuffer[1]); - glVertexAttribPointer(defaultShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(defaultShader.colorLoc); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); } glDrawArrays(GL_TRIANGLES, 0, triangles.vCounter); @@ -1149,16 +1154,16 @@ void rlglDraw(void) { // Enable vertex attributes glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[0]); - glVertexAttribPointer(defaultShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultShader.vertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[1]); - glVertexAttribPointer(defaultShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultShader.texcoordLoc); + glVertexAttribPointer(currentShader.texcoordLoc, 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.texcoordLoc); glBindBuffer(GL_ARRAY_BUFFER, quadsBuffer[2]); - glVertexAttribPointer(defaultShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(defaultShader.colorLoc); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadsBuffer[3]); } @@ -1206,12 +1211,12 @@ void rlglDraw(void) else { glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[0]); - glVertexAttribPointer(defaultShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(defaultShader.vertexLoc); + glVertexAttribPointer(currentShader.vertexLoc, 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(currentShader.vertexLoc); glBindBuffer(GL_ARRAY_BUFFER, linesBuffer[1]); - glVertexAttribPointer(defaultShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(defaultShader.colorLoc); + glVertexAttribPointer(currentShader.colorLoc, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(currentShader.colorLoc); } glDrawArrays(GL_LINES, 0, lines.vCounter); @@ -1310,7 +1315,7 @@ void rlglDrawModel(Model model, Vector3 position, float rotationAngle, Vector3 r // 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)); - glUniform1i(model.shader.textureLoc, 0); + glUniform1i(model.shader.textureLoc, 0); // Texture fits in texture unit 0 (Check glActiveTexture()) // Apply color tinting to model // NOTE: Just update one uniform on fragment shader @@ -1573,16 +1578,18 @@ unsigned int rlglLoadTexture(void *data, int width, int height, int textureForma GLuint id; - // TODO: Review compressed textures support by OpenGL version - /* (rlGetVersion() == OPENGL_11) - if ((textureFormat == COMPRESSED_DXT1_RGB) || (textureFormat == COMPRESSED_DXT3_RGBA) || (textureFormat == COMPRESSED_DXT5_RGBA) || - (textureFormat == COMPRESSED_ETC1_RGB8) || (textureFormat == COMPRESSED_ETC2_RGB8) || (textureFormat == COMPRESSED_ETC2_EAC_RGBA8)) + // Check compressed textures support by OpenGL 1.1 + if (rlGetVersion() == OPENGL_11) { - id = 0; - TraceLog(WARNING, "GPU compressed textures not supported on OpenGL 1.1"); - return id; + if ((textureFormat == COMPRESSED_ETC1_RGB) || (textureFormat == COMPRESSED_ETC2_RGB) || (textureFormat == COMPRESSED_ETC2_EAC_RGBA) || + (textureFormat == COMPRESSED_PVRT_RGB) || (textureFormat == COMPRESSED_PVRT_RGBA) || + (textureFormat == COMPRESSED_ASTC_4x4_RGBA) || (textureFormat == COMPRESSED_ASTC_8x8_RGBA)) + { + id = 0; + TraceLog(WARNING, "Required GPU compressed texture format not supported"); + return id; + } } - */ glGenTextures(1, &id); // Generate Pointer to the texture @@ -1679,10 +1686,17 @@ unsigned int rlglLoadTexture(void *data, int width, int height, int textureForma // 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, 1.0f }; + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - TraceLog(INFO, "Grayscale texture loaded and swizzled!"); + 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; @@ -1711,6 +1725,7 @@ unsigned int rlglLoadTexture(void *data, int width, int height, int textureForma 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; @@ -1724,7 +1739,7 @@ unsigned int rlglLoadTexture(void *data, int width, int height, int textureForma case COMPRESSED_ETC2_RGB: 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: 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_ASTC_RGBA_4x4: LoadCompressedTexture((unsigned char *)data, width, height, mipmapCount, GL_COMPRESSED_RGBA_ASTC_4x4_KHR); break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 - default: TraceLog(WARNING, "Texture format not recognized"); break; + default: TraceLog(WARNING, "Texture format not supported"); break; } if ((mipmapCount == 1) && (genMipmaps)) @@ -2009,13 +2024,50 @@ void rlglSetModelShader(Model *model, Shader shader) #endif } +// Set custom shader to be used on batch draw +void rlglSetCustomShader(Shader shader) +{ + 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 +*/ + } +} + +// Set default shader to be used on batch draw +void rlglSetDefaultShader(void) +{ + rlglSetCustomShader(defaultShader); +} + #if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -void PrintProjectionMatrix() +void PrintProjectionMatrix(void) { PrintMatrix(projection); } -void PrintModelviewMatrix() +void PrintModelviewMatrix(void) { PrintMatrix(modelview); } @@ -2292,7 +2344,7 @@ static void InitializeBuffers(void) } // Initialize Vertex Array Objects (Contain VBO) -// NOTE: lines, triangles and quads buffers use defaultShader +// NOTE: lines, triangles and quads buffers use currentShader static void InitializeBuffersGPU(void) { if (vaoSupported) @@ -2308,14 +2360,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(defaultShader.vertexLoc); - glVertexAttribPointer(defaultShader.vertexLoc, 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(defaultShader.colorLoc); - glVertexAttribPointer(defaultShader.colorLoc, 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]); @@ -2334,13 +2386,13 @@ static void InitializeBuffersGPU(void) // 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(defaultShader.vertexLoc); - glVertexAttribPointer(defaultShader.vertexLoc, 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(defaultShader.colorLoc); - glVertexAttribPointer(defaultShader.colorLoc, 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]); @@ -2359,18 +2411,18 @@ static void InitializeBuffersGPU(void) // 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(defaultShader.vertexLoc); - glVertexAttribPointer(defaultShader.vertexLoc, 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(defaultShader.texcoordLoc); - glVertexAttribPointer(defaultShader.texcoordLoc, 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(defaultShader.colorLoc); - glVertexAttribPointer(defaultShader.colorLoc, 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]); diff --git a/src/rlgl.h b/src/rlgl.h index 3335fe16..41c13ef7 100644 --- a/src/rlgl.h +++ b/src/rlgl.h @@ -216,6 +216,8 @@ void rlglInitPostpro(void); // Initialize postprocessing sys 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, float rotationAngle, Vector3 rotationAxis, Vector3 scale, Color color, bool wires); diff --git a/src/text.c b/src/text.c index 7b3fc7d2..8e3fc4b9 100644 --- a/src/text.c +++ b/src/text.c @@ -87,6 +87,8 @@ extern void LoadDefaultFont(void) Image image; image.width = 128; // We know our default font image is 128 pixels width image.height = 128; // We know our default font image is 128 pixels height + image.mipmaps = 1; + image.format = UNCOMPRESSED_R8G8B8A8; // Default font is directly defined here (data generated from a sprite font image) // This way, we reconstruct SpriteFont without creating large global variables @@ -149,9 +151,9 @@ extern void LoadDefaultFont(void) // Re-construct image from defaultFontData and generate OpenGL texture //---------------------------------------------------------------------- - image.pixels = (Color *)malloc(image.width * image.height * sizeof(Color)); + Color *imagePixels = (Color *)malloc(image.width*image.height*sizeof(Color)); - for (int i = 0; i < image.width * image.height; i++) image.pixels[i] = BLANK; // Initialize array + for (int i = 0; i < image.width*image.height; i++) imagePixels[i] = BLANK; // Initialize array int counter = 0; // Font data elements counter @@ -160,7 +162,7 @@ extern void LoadDefaultFont(void) { 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++; @@ -171,6 +173,10 @@ extern void LoadDefaultFont(void) //FILE *myimage = fopen("default_font.raw", "wb"); //fwrite(image.pixels, 1, 128*128*4, myimage); //fclose(myimage); + + SetPixelData(&image, imagePixels, 0); + + free(imagePixels); defaultFont.texture = LoadTextureFromImage(image, false); // Convert loaded image to OpenGL texture UnloadImage(image); @@ -232,14 +238,16 @@ SpriteFont LoadSpriteFont(const char *fileName) { 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); @@ -248,6 +256,7 @@ SpriteFont LoadSpriteFont(const char *fileName) spriteFont.texture = LoadTextureFromImage(image, false); // Convert loaded image to OpenGL texture + free(imagePixels); UnloadImage(image); } @@ -522,6 +531,8 @@ static SpriteFont LoadRBMF(const char *fileName) image.width = (int)rbmfHeader.imgWidth; image.height = (int)rbmfHeader.imgHeight; + image.mipmaps = 1; + image.format = UNCOMPRESSED_R8G8B8A8; int numPixelBits = rbmfHeader.imgWidth * rbmfHeader.imgHeight / 32; @@ -535,9 +546,9 @@ 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(image.width*image.height*sizeof(Color)); - for (int i = 0; i < image.width * image.height; i++) image.pixels[i] = BLANK; // Initialize array + for (int i = 0; i < image.width*image.height; i++) imagePixels[i] = BLANK; // Initialize array int counter = 0; // Font data elements counter @@ -546,11 +557,15 @@ static SpriteFont LoadRBMF(const char *fileName) { 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++; } + + SetPixelData(&image, imagePixels, 0); + + free(imagePixels); TraceLog(INFO, "[%s] Image reconstructed correctly, now converting it to texture", fileName); @@ -607,7 +622,7 @@ static SpriteFont LoadTTF(const char *fileName, int fontSize) Image image; image.width = 512; image.height = 512; - image.pixels = (Color *)malloc(image.width*image.height*sizeof(Color)); + //image.pixels = (Color *)malloc(image.width*image.height*sizeof(Color)); unsigned char *ttfBuffer = (unsigned char *)malloc(1 << 25); @@ -647,7 +662,7 @@ static SpriteFont LoadTTF(const char *fileName, int fontSize) 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]; @@ -655,7 +670,7 @@ static SpriteFont LoadTTF(const char *fileName, int fontSize) image.pixels[i].b = tempBitmap[i]; image.pixels[i].a = 255; } - +*/ free(tempBitmap); // REFERENCE EXAMPLE diff --git a/src/textures.c b/src/textures.c index 1d22e509..d2b2de1d 100644 --- a/src/textures.c +++ b/src/textures.c @@ -47,13 +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 mipmaps; // Mipmap levels, 1 by default - int format; // Data format -} ImageEx; +// ... //---------------------------------------------------------------------------------- // Global Variables Definition @@ -63,14 +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 ImageEx LoadPVR(const char *fileName); // Load PVR 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 @@ -81,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) || @@ -94,82 +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); + image.data = stbi_load(fileName, &imgWidth, &imgHeight, &imgBpp, 0); - 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); + 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),"dds") == 0) - { - // NOTE: DDS uncompressed images can also be loaded (discarding mipmaps...) - - ImageEx imageDDS = LoadDDS(fileName); - - if (imageDDS.format == 0) - { - image.pixels = (Color *)malloc(imageDDS.width * imageDDS.height * sizeof(Color)); - image.width = imageDDS.width; - image.height = imageDDS.height; - - 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); + 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 if (strcmp(GetExtension(fileName),"pkm") == 0) - { - TraceLog(INFO, "[%s] PKM Compressed image data could not be loaded", fileName); - } - 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; @@ -232,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); } @@ -294,66 +240,14 @@ 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); - - texture.id = rlglLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps, false); - - 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 = rlglLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps, false); - - 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); - - free(image.data); - } - else if (strcmp(GetExtension(fileName),"pvr") == 0) - { - ImageEx image = LoadPVR(fileName); - - texture.id = rlglLoadTexture(image.data, image.width, image.height, image.format, image.mipmaps, false); - - texture.width = image.width; - texture.height = image.height; - - if (texture.id == 0) TraceLog(WARNING, "[%s] PVR texture could not be loaded", fileName); - else TraceLog(INFO, "[%s] PVR texture loaded successfully", fileName); - - free(image.data); - } - else - { - Image image = LoadImage(fileName); - - if (image.pixels != NULL) - { + Image image = LoadImage(fileName); + #if defined(PLATFORM_RPI) || defined(PLATFORM_WEB) - ConvertToPOT(&image, BLANK); + ConvertToPOT(&image, BLANK); #endif - texture = LoadTextureFromImage(image, false); - UnloadImage(image); - } - } + + texture = LoadTextureFromImage(image, false); + UnloadImage(image); return texture; } @@ -364,6 +258,8 @@ Texture2D LoadTextureEx(void *data, int width, int height, int textureFormat, in texture.width = width; texture.height = height; + texture.mipmaps = mipmapCount; + texture.format = textureFormat; texture.id = rlglLoadTexture(data, width, height, textureFormat, mipmapCount, genMipmaps); @@ -380,45 +276,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, UNCOMPRESSED_R8G8B8A8, 1, 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); - - TraceLog(INFO, "Created texture id: %i", texture.id); + texture.mipmaps = 0; + texture.format = 0; + + 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; } @@ -438,7 +304,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 @@ -451,6 +317,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); @@ -467,7 +335,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; } } @@ -480,6 +348,115 @@ 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: Pixels color array size must be coherent with image size +// TODO: Review to support different color modes (TextureFormat) +void SetPixelData(Image *image, Color *pixels, int format) +{ + free(image->data); + image->data = (unsigned char *)malloc(image->width*image->height*4*sizeof(unsigned char)); + + int k = 0; + + 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++; + } } // Draw a Texture2D @@ -554,9 +531,17 @@ 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 @@ -569,7 +554,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; @@ -581,7 +566,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; @@ -591,8 +576,7 @@ static ImageEx LoadDDS(const char *fileName) unsigned int reserved2; } ddsHeader; - ImageEx image; - ddsHeader header; + Image image; image.data = NULL; image.width = 0; @@ -604,7 +588,7 @@ static ImageEx LoadDDS(const char *fileName) 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 { @@ -616,11 +600,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)); @@ -630,63 +615,85 @@ static ImageEx LoadDDS(const char *fileName) image.width = header.width; image.height = header.height; + 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; - - for(int y = 0; y < image.height; y++) + if (header.ddspf.flags == 0x40) // no alpha channel { - for(int x = 0; x < image.width; x++) + image.data = (unsigned short *)malloc(image.width*image.height*sizeof(unsigned short)); + fread(image.data, image.width*image.height*sizeof(unsigned short), 1, ddsFile); + + image.format = UNCOMPRESSED_R5G6B5; + } + else if (header.ddspf.flags == 0x41) // with alpha channel + { + if (header.ddspf.aBitMask == 0x8000) // 1bit 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 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 + { + 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; } } + } + 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); - free(buffer); - - image.mipmaps = 1; 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.format = UNCOMPRESSED_R8G8B8A8; } 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.mipmaps = header.mipmapCount; switch(header.ddspf.fourCC) { @@ -701,6 +708,8 @@ static ImageEx LoadDDS(const char *fileName) } } } + + fclose(ddsFile); // Close file pointer } return image; @@ -709,94 +718,195 @@ 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) -// ETC1 compression support requires extension GL_OES_compressed_ETC1_RGB8_texture -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 - - // 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) + // NOTE: ETC is always 4bit per pixel (64 bit 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; - - 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 - - image.width = width; - image.height = height; - image.mipmaps = 1; - image.format = COMPRESSED_ETC1_RGB; + 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; } -// Loading PVR image data (uncompressed or PVRT compression) -// NOTE: PVR compression requires extension GL_IMG_texture_compression_pvrtc (PowerVR GPUs) -static ImageEx LoadPVR(const char *fileName) +// Load KTX compressed image data (ETC1/ETC2 compression) +static Image LoadKTX(const char *fileName) { - // If supported in OpenGL ES 2.0. the following formats could be defined: - // GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG (RGB) - // GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG (RGBA) + // 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(INFO, "KTX (ETC) image width: %i", header.width); + TraceLog(INFO, "KTX (ETC) image height: %i", header.height); + TraceLog(INFO, "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)); + + 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 { @@ -818,25 +928,30 @@ static ImageEx LoadPVR(const char *fileName) // PVR file v3 Header (52 bytes) // NOTE: After it could be metadata (15 bytes?) typedef struct { - unsigned int version; - unsigned int flags; - unsigned long long pixelFormat; - 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; + 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; - // Bytes Swap (little-endian <-> big-endian) - //unsigned short data; - //unsigned short swap = ((data & 0x00FF) << 8) | ((data & 0xFF00) >> 8); + // Metadata (usually 15 bytes) + typedef struct { + unsigned int devFOURCC; + unsigned int key; + unsigned int dataSize; // Not used? + unsigned char *data; // Not used? + } pvrMetadata; - ImageEx image; + Image image; image.data = NULL; image.width = 0; @@ -848,49 +963,165 @@ static ImageEx LoadPVR(const char *fileName) if (pvrFile == NULL) { - TraceLog(WARNING, "[%s] PVR image file could not be opened", fileName); + TraceLog(WARNING, "[%s] PVR file could not be opened", fileName); } else { // Check PVR image version - unsigned int pvrVersion = 0; - fread(&pvrVersion, sizeof(unsigned int), 1, pvrFile); - fsetpos(pvrFile, 0); + unsigned char pvrVersion = 0; + fread(&pvrVersion, sizeof(unsigned char), 1, pvrFile); + fseek(pvrFile, 0, SEEK_SET); - if (pvrVersion == 52) - { - pvrHeaderV2 header; - - fread(&header, sizeof(pvrHeaderV2), 1, pvrFile); - - image.width = header.width; - image.height = header.height; - image.mipmaps = header.numMipmaps; - image.format = COMPRESSED_PVRT_RGB; //COMPRESSED_PVRT_RGBA - } - else if (pvrVersion == 3) + // Load different PVR data formats + if (pvrVersion == 0x50) { pvrHeaderV3 header; + // Get PVR image header fread(&header, sizeof(pvrHeaderV3), 1, pvrFile); - image.width = header.width; - image.height = header.height; - image.mipmaps = header.numMipmaps; - - // TODO: Skip metaDataSize - - image.format = COMPRESSED_PVRT_RGB; //COMPRESSED_PVRT_RGBA + 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 + 1; + + // 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); + } } - - int size = (image.width/4)*(image.height/4)*8; // Total data size in bytes - - image.data = (unsigned char*)malloc(size * sizeof(unsigned char)); - - fread(image.data, 1, size, 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 + { + image.width = 0x00000000 | ((int)header.width[0] << 16) | ((int)header.width[1] << 8) | ((int)header.width[2]); + image.height = 0x00000000 | ((int)header.height[0] << 16) | ((int)header.height[1] << 8) | ((int)header.height[2]); + image.mipmaps = 1; + + 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; } \ No newline at end of file