Browse Source

Add screen->world and world->screen functions for 2D; add extended camera2D example (#947)

arvyy 6 years ago
parent
commit
97101d1003
3 changed files with 324 additions and 22 deletions
  1. 284 0
      examples/core/core_2d_camera_ext.c
  2. 37 22
      src/core.c
  3. 3 0
      src/raylib.h

+ 284 - 0
examples/core/core_2d_camera_ext.c

@@ -0,0 +1,284 @@
+/*******************************************************************************************
+*
+*   raylib [core] example - 2d camera extended
+*
+*   This example has been created using raylib 1.5 (www.raylib.com)
+*   raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details)
+*
+*   Copyright (c) 2016 Ramon Santamaria (@raysan5)
+*
+********************************************************************************************/
+
+#include "raylib.h"
+#include "raymath.h"
+
+#define G 400
+#define PLAYER_JUMP_SPD 350.f
+#define PLAYER_HOR_SPD 200.f
+
+typedef struct Player {
+    Vector2 pos;
+    float vel;
+    int canJump;
+} Player;
+
+typedef struct EnvItem {
+    Rectangle rect;
+    int blocking;
+    Color color;
+} EnvItem;
+
+void updateCameraCenter(
+        float delta, 
+        Camera2D *camera, 
+        Player *player, 
+        EnvItem *envItems, 
+        int envItemsLength, 
+        int width, int height
+) {
+    camera->offset = (Vector2){ width/2, height/2 };
+    camera->target = player->pos;
+}
+
+void updateCameraCenterInsideMap(
+        float delta, 
+        Camera2D *camera, 
+        Player *player, 
+        EnvItem *envItems, 
+        int envItemsLength, 
+        int width, int height
+) {
+    camera->target = player->pos;
+    camera->offset = (Vector2){ width/2, height/2 };
+    float minX = 1000, minY = 1000, maxX = -1000, maxY = -1000;
+    for (int i = 0; i < envItemsLength; i++) {
+        EnvItem *ei = envItems + i;
+        minX = fminf(ei->rect.x, minX);
+        maxX = fmaxf(ei->rect.x + ei->rect.width, maxX);
+        minY = fminf(ei->rect.y, minY);
+        maxY = fmaxf(ei->rect.y + ei->rect.height, maxY);
+    }
+    Vector2 max = GetWorldToScreen2D((Vector2){ maxX, maxY }, *camera);
+    Vector2 min = GetWorldToScreen2D((Vector2){ minX, minY }, *camera);
+    if (max.x < width) {
+        camera->offset.x = width - (max.x - width/2);
+    }
+    if (max.y < height) {
+        camera->offset.y = height - (max.y - height/2);
+    }
+    if (min.x > 0) {
+        camera->offset.x = width/2 - min.x;
+    }
+    if (min.y > 0) {
+        camera->offset.y = height/2- min.y;
+    }
+}
+
+void updateCameraCenterSmoothFollow(
+        float delta, 
+        Camera2D *camera, 
+        Player *player, 
+        EnvItem *envItems, 
+        int envItemsLength, 
+        int width, int height
+) {
+    static float minSpeed = 30;
+    static float minEffectLength = 10;
+    static float fractionSpeed = 0.8f;
+    camera->offset = (Vector2){ width/2, height/2 };
+    Vector2 diff = Vector2Subtract(player->pos, camera->target);
+    float length = Vector2Length(diff);
+    if (length > minEffectLength) {
+        float speed = fmaxf(fractionSpeed * length, minSpeed);
+        camera->target = Vector2Add(camera->target, Vector2Scale(diff, speed*delta/length));
+    }
+}
+
+void updateCameraEvenOutOnLanding(
+        float delta, 
+        Camera2D *camera, 
+        Player *player, 
+        EnvItem *envItems, 
+        int envItemsLength, 
+        int width, int height
+) {
+    static float evenOutSpeed = 700;
+    static int eveningOut = false;
+    static float evenOutTarget;
+    camera->offset = (Vector2){ width/2, height/2 };
+    camera->target.x = player->pos.x;
+    if (eveningOut) {
+        if (evenOutTarget > camera->target.y) {
+            camera->target.y += evenOutSpeed * delta;
+            if (camera->target.y > evenOutTarget) {
+                camera->target.y = evenOutTarget;
+                eveningOut = 0;
+            }
+        } else {
+            camera->target.y -= evenOutSpeed * delta;
+            if (camera->target.y < evenOutTarget) {
+                camera->target.y = evenOutTarget;
+                eveningOut = 0;
+            }
+        }
+    } else {
+        if (player->canJump && 
+            player->vel == 0 && 
+            player->pos.y != camera->target.y
+        ) {
+            eveningOut = 1;
+            evenOutTarget = player->pos.y;
+        }
+    }
+}
+
+void updateCameraPlayerBoundsPush(
+        float delta, 
+        Camera2D *camera, 
+        Player *player, 
+        EnvItem *envItems, 
+        int envItemsLength, 
+        int width, int height
+) {
+    static Vector2 bbox = { 0.2f, 0.2f };
+
+    Vector2 bboxWorldMin = GetScreenToWorld2D((Vector2){ (1 - bbox.x) * 0.5 * width, (1 - bbox.y) * 0.5 * height }, *camera);
+    Vector2 bboxWorldMax = GetScreenToWorld2D((Vector2){ (1 + bbox.x) * 0.5 * width, (1 + bbox.y) * 0.5 * height }, *camera);
+    camera->offset = (Vector2){ (1 - bbox.x) * 0.5 * width, (1 - bbox.y) * 0.5 * height };
+
+    if (player->pos.x < bboxWorldMin.x) {
+        camera->target.x = player->pos.x;
+    }
+    if (player->pos.y < bboxWorldMin.y) {
+        camera->target.y = player->pos.y;
+    }
+    if (player->pos.x > bboxWorldMax.x) {
+        camera->target.x = bboxWorldMin.x + (player->pos.x - bboxWorldMax.x);
+    }
+    if (player->pos.y > bboxWorldMax.y) {
+        camera->target.y = bboxWorldMin.y + (player->pos.y - bboxWorldMax.y);
+    }
+}
+
+
+void updatePlayer(float delta, Player *player, EnvItem *envItems, int envItemsLength) {
+    if (IsKeyDown(KEY_LEFT)) player->pos.x -= PLAYER_HOR_SPD*delta;
+    if (IsKeyDown(KEY_RIGHT)) player->pos.x += PLAYER_HOR_SPD*delta;
+    if (IsKeyDown(KEY_SPACE) && player->canJump) {
+        player->vel = -PLAYER_JUMP_SPD;
+        player->canJump = 0;
+    }
+
+    int hitObstacle = 0;
+    for (int i = 0; i < envItemsLength; i++) {
+        EnvItem *ei = envItems + i;
+        Vector2 *p = &(player->pos);
+        if (ei->blocking &&
+            ei->rect.x <= p->x && 
+            ei->rect.x + ei->rect.width >= p->x &&
+            ei->rect.y >= p->y &&
+            ei->rect.y < p->y + player->vel * delta) 
+        {
+            hitObstacle = 1;
+            player->vel = 0.0f;
+            p->y = ei->rect.y;
+        }
+    }
+    if (!hitObstacle) {
+        player->pos.y += player->vel * delta;
+        player->vel += G * delta;
+        player->canJump = 0;
+    } else {
+        player->canJump = 1;
+    }
+}
+
+void renderWorld(Player *player, EnvItem *envItems, int envItemsLength) {
+    for (int i = 0; i < envItemsLength; i++) {
+        DrawRectangleRec(envItems[i].rect, envItems[i].color);
+    }
+    Rectangle playerRect = { player->pos.x - 20, player->pos.y - 40, 40, 40 };
+    DrawRectangleRec(playerRect, RED);
+}
+
+int main(void)
+{
+    const int screenWidth = 800;
+    const int screenHeight = 450;
+    InitWindow(screenWidth, screenHeight, "raylib [core] example - 2d camera");
+    SetTargetFPS(60);
+
+    Player player;
+    player.pos = (Vector2){ 400, 280 };
+    player.vel = 0;
+    player.canJump = 0;
+    EnvItem envItems[] = {
+        {{ 0, 0, 1000, 400 }, 0, LIGHTGRAY },
+        {{ 0, 400, 1000, 200 }, 1, GRAY },
+        {{ 300, 200, 400, 10 }, 1, GRAY },
+        {{ 250, 300, 100, 10 }, 1, GRAY },
+        {{ 650, 300, 100, 10 }, 1, GRAY }
+    };
+    int envItemsLength = sizeof(envItems) / sizeof (envItems[0]);
+
+    Camera2D camera = { 0 };
+    camera.target = player.pos;
+    camera.offset = (Vector2){ screenWidth/2, screenHeight/2 };
+    camera.rotation = 0.0f;
+    camera.zoom = 1.0f;
+
+    int cameraOption = 0;
+    void (*cameraUpdaters[])(float, Camera2D*, Player*, EnvItem*, int, int, int) = {
+        updateCameraCenter,
+        updateCameraCenterInsideMap,
+        updateCameraCenterSmoothFollow,
+        updateCameraEvenOutOnLanding,
+        updateCameraPlayerBoundsPush
+    };
+    int cameraUpdatersLength = sizeof(cameraUpdaters) / sizeof(cameraUpdaters[0]);
+    char* cameraDescriptions[] = {
+        "Follow player center",
+        "Follow player center, but clamp to map edges",
+        "Follow player center; smoothed",
+        "Follow player center horizontally; updateplayer center vertically after landing",
+        "Player push camera on getting too close to screen edge"
+    };
+    
+    while (!WindowShouldClose()) {
+        float delta = GetFrameTime();
+        updatePlayer(delta, &player, envItems, envItemsLength);
+
+        camera.zoom += ((float)GetMouseWheelMove()*0.05f);
+        if (camera.zoom > 3.0f) camera.zoom = 3.0f;
+        else if (camera.zoom < 0.25f) camera.zoom = 0.25f;
+        if (IsKeyPressed(KEY_R))
+        {
+            camera.zoom = 1.0f;
+        }
+
+        if (IsKeyPressed(KEY_C)) {
+            cameraOption = (cameraOption + 1) % cameraUpdatersLength;
+        }
+        cameraUpdaters[cameraOption](delta, &camera, &player, envItems, envItemsLength, screenWidth, screenHeight);
+
+        BeginDrawing();
+            ClearBackground(RAYWHITE);
+
+            BeginMode2D(camera);
+               renderWorld(&player, envItems, envItemsLength); 
+            EndMode2D();
+
+            DrawText("Controls:", 20, 20, 10, BLACK);
+            DrawText("- Right/Left to move", 40, 40, 10, DARKGRAY);
+            DrawText("- Space to jump", 40, 60, 10, DARKGRAY);
+            DrawText("- Mouse Wheel to Zoom in-out, R to reset zoom", 40, 80, 10, DARKGRAY);
+            DrawText("- C to change camera mode", 40, 100, 10, DARKGRAY);
+            DrawText("Current camera mode:", 20, 120, 10, BLACK);
+            DrawText(cameraDescriptions[cameraOption], 40, 140, 10, DARKGRAY);
+        EndDrawing();
+    }
+
+    CloseWindow();        // Close window and OpenGL context
+
+    return 0;
+}

+ 37 - 22
src/core.c

@@ -1250,28 +1250,7 @@ void BeginMode2D(Camera2D camera)
     rlLoadIdentity();                   // Reset current matrix (MODELVIEW)
     rlLoadIdentity();                   // Reset current matrix (MODELVIEW)
     rlMultMatrixf(MatrixToFloat(screenScaling)); // Apply screen scaling if required
     rlMultMatrixf(MatrixToFloat(screenScaling)); // Apply screen scaling if required
 
 
-    // The camera in world-space is set by
-    //   1. Move it to target
-    //   2. Rotate by -rotation and scale by (1/zoom)
-    //      When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller),
-    //      not for the camera getting bigger, hence the invert. Same deal with rotation.
-    //   3. Move it by (-offset);
-    //      Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera)
-    //      we need to do it into opposite direction (inverse transform)
-
-    // Having camera transform in world-space, inverse of it gives the modelview transform.
-    // Since (A*B*C)' = C'*B'*A', the modelview is
-    //   1. Move to offset
-    //   2. Rotate and Scale
-    //   3. Move by -target
-    Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f);
-    Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD);
-    Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f);
-    Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f);
-
-    Matrix matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation);
-
-    rlMultMatrixf(MatrixToFloat(matTransform));  // Apply transformation to modelview
+    rlMultMatrixf(MatrixToFloat(GetCamera2DMatrix(camera)));  // Apply transformation to modelview
 }
 }
 
 
 // Ends 2D mode with custom camera
 // Ends 2D mode with custom camera
@@ -1502,6 +1481,42 @@ Matrix GetCameraMatrix(Camera camera)
     return MatrixLookAt(camera.position, camera.target, camera.up);
     return MatrixLookAt(camera.position, camera.target, camera.up);
 }
 }
 
 
+
+Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera) {
+    Matrix m = MatrixInvert(GetCamera2DMatrix(camera));
+    Vector3 transform = Vector3Transform((Vector3){position.x, position.y, 0}, m);
+    return (Vector2){transform.x, transform.y};
+}
+
+Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera) {
+    Matrix m = GetCamera2DMatrix(camera);
+    Vector3 transform = Vector3Transform((Vector3){position.x, position.y, 0}, m);
+    return (Vector2){transform.x, transform.y};
+}
+
+Matrix GetCamera2DMatrix(Camera2D camera) {
+    // The camera in world-space is set by
+    //   1. Move it to target
+    //   2. Rotate by -rotation and scale by (1/zoom)
+    //      When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller),
+    //      not for the camera getting bigger, hence the invert. Same deal with rotation.
+    //   3. Move it by (-offset);
+    //      Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera)
+    //      we need to do it into opposite direction (inverse transform)
+
+    // Having camera transform in world-space, inverse of it gives the modelview transform.
+    // Since (A*B*C)' = C'*B'*A', the modelview is
+    //   1. Move to offset
+    //   2. Rotate and Scale
+    //   3. Move by -target
+    Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f);
+    Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD);
+    Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f);
+    Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f);
+    Matrix matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation);
+    return matTransform;
+}
+
 // Set target FPS (maximum)
 // Set target FPS (maximum)
 void SetTargetFPS(int fps)
 void SetTargetFPS(int fps)
 {
 {

+ 3 - 0
src/raylib.h

@@ -911,6 +911,9 @@ RLAPI void EndScissorMode(void);                                  // End scissor
 RLAPI Ray GetMouseRay(Vector2 mousePosition, Camera camera);      // Returns a ray trace from mouse position
 RLAPI Ray GetMouseRay(Vector2 mousePosition, Camera camera);      // Returns a ray trace from mouse position
 RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera);  // Returns the screen space position for a 3d world space position
 RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera);  // Returns the screen space position for a 3d world space position
 RLAPI Matrix GetCameraMatrix(Camera camera);                      // Returns camera transform matrix (view matrix)
 RLAPI Matrix GetCameraMatrix(Camera camera);                      // Returns camera transform matrix (view matrix)
+RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera);
+RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera);
+RLAPI Matrix GetCamera2DMatrix(Camera2D camera);
 
 
 // Timing-related functions
 // Timing-related functions
 RLAPI void SetTargetFPS(int fps);                                 // Set target FPS (maximum)
 RLAPI void SetTargetFPS(int fps);                                 // Set target FPS (maximum)