From ddcbcbe7a67e5142234073593292d7be62ce7790 Mon Sep 17 00:00:00 2001 From: Daniel Bratsberg Date: Tue, 3 Oct 2023 17:26:38 +0200 Subject: [PATCH] Adds DrawModelPro() and DrawModelWiresPro(), accepting Matrixes as input This allows us to easily draw the same model with multiple transformations, in particular rotation around multiple axises without having to change the attributes of the model itself (which the Yaw, Pitch, Roll example does). This is particularly useful when loading one model and drawing it multiple times, such as in a game where you fight multiple enemies who all use the same model, but move and behave independently. --- examples/models/models_transformations.c | 190 +++++++++++++++++++++ examples/models/models_transformations.png | Bin 0 -> 17874 bytes src/raylib.h | 2 + src/rmodels.c | 18 +- 4 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 examples/models/models_transformations.c create mode 100644 examples/models/models_transformations.png diff --git a/examples/models/models_transformations.c b/examples/models/models_transformations.c new file mode 100644 index 000000000..1dcd85f72 --- /dev/null +++ b/examples/models/models_transformations.c @@ -0,0 +1,190 @@ +/******************************************************************************************* + * + * raylib [models] example - Draw some basic geometric shapes (cube, sphere, cylinder...) + * + * Example originally created with raylib 1.0, last time updated with raylib 3.5 + * + * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified, + * BSD-like license that allows static linking with closed source software + * + * Copyright (c) 2014-2023 Ramon Santamaria (@raysan5) + * + ********************************************************************************************/ + +#include "raylib.h" +#include "raymath.h" + +// Transforms of the cube +Vector3 position = (Vector3){0.0f, 0.0f, 0.0f}; +Vector3 scale = (Vector3){1.0f, 1.0f, 1.0f}; +Vector3 rotation = (Vector3){0.0f, 0.0f, 0.0f}; + +// Define movement speed +const float translationSpeed = 0.2f; +const float scaleSpeed = 0.1f; +const float rotationSpeed = 2.0f; + +bool left() +{ + return IsKeyDown(KEY_A) || IsKeyDown(KEY_LEFT); +} + +bool right() +{ + return IsKeyDown(KEY_D) || IsKeyDown(KEY_RIGHT); +} + +bool up() +{ + return IsKeyDown(KEY_W) || IsKeyDown(KEY_UP); +} + +bool down() +{ + return IsKeyDown(KEY_S) || IsKeyDown(KEY_DOWN); +} + +bool in() +{ + return IsKeyDown(KEY_E) || IsKeyDown(KEY_PAGE_DOWN); +} + +bool out() +{ + return IsKeyDown(KEY_Q) || IsKeyDown(KEY_PAGE_UP); +} + +bool shouldScale() +{ + return IsKeyDown(KEY_LEFT_CONTROL); +} + +bool shouldMove() +{ + return IsKeyDown(KEY_LEFT_SHIFT); +} + +void handleInput() +{ + if (shouldMove()) + { + if (left()) + position.x -= translationSpeed; + else if (right()) + position.x += translationSpeed; + if (down()) + position.y -= translationSpeed; + else if (up()) + position.y += translationSpeed; + if (out()) + position.z += translationSpeed; + else if (in()) + position.z -= translationSpeed; + } + else if (shouldScale()) + { + if (left()) + scale.x -= scaleSpeed; + else if (right()) + scale.x += scaleSpeed; + if (down()) + scale.y -= scaleSpeed; + else if (up()) + scale.y += scaleSpeed; + if (out()) + scale.z += scaleSpeed; + else if (in()) + scale.z -= scaleSpeed; + } + else + { + if (left()) + rotation.x -= rotationSpeed; + else if (right()) + rotation.x += rotationSpeed; + if (down()) + rotation.y -= rotationSpeed; + else if (up()) + rotation.y += rotationSpeed; + if (out()) + rotation.z += rotationSpeed; + else if (in()) + rotation.z -= rotationSpeed; + } +} +//------------------------------------------------------------------------------------ +// Program main entry point +//------------------------------------------------------------------------------------ +int main(void) +{ + // Initialization + //-------------------------------------------------------------------------------------- + const int screenWidth = 800; + const int screenHeight = 450; + + InitWindow(screenWidth, screenHeight, "raylib [models] example - model transformations"); + + // Generate a cube shaped model to play with + Model cube = LoadModelFromMesh(GenMeshCube(2.0f, 2.0f, 2.0f)); + + // Define the camera to look into our 3d world + Camera camera = {0}; + camera.position = (Vector3){0.0f, 10.0f, 10.0f}; + camera.target = (Vector3){0.0f, 0.0f, 0.0f}; + camera.up = (Vector3){0.0f, 1.0f, 0.0f}; + camera.fovy = 45.0f; + camera.projection = CAMERA_PERSPECTIVE; + + 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 + handleInput(); + + // Draw + //---------------------------------------------------------------------------------- + BeginDrawing(); + + ClearBackground(RAYWHITE); + + BeginMode3D(camera); + + // Set translation matrix + Matrix matTranslation = MatrixTranslate(position.x, position.y, position.z); + + // Multiply rotation matrices with each other, to rotate around each axis. + Matrix matRotX = MatrixRotate((Vector3){1.0f, 0.0f, 0.0f}, rotation.x * DEG2RAD); + Matrix matRotY = MatrixRotate((Vector3){0.0f, 1.0f, 0.0f}, rotation.y * DEG2RAD); + Matrix matRotZ = MatrixRotate((Vector3){0.0f, 0.0f, 1.0f}, rotation.z * DEG2RAD); + Matrix matRotCombined = MatrixMultiply(matRotX, matRotY); + matRotCombined = MatrixMultiply(matRotCombined, matRotZ); + + // Set scale matrix + Matrix matScale = MatrixScale(scale.x, scale.y, scale.z); + + DrawModelPro(cube, matTranslation, matRotCombined, matScale, RED); + DrawModelWiresPro(cube, matTranslation, matRotCombined, matScale, BLUE); + DrawGrid(10, 1.0f); // Draw a grid + + EndMode3D(); + + DrawFPS(10, 10); + + DrawText("Left Shift + W/A/S/D to translate", 10, 40, 20, BLUE); + DrawText("Left Ctrl + W/A/S/D to scale", 10, 70, 20, BLUE); + DrawText("W/A/S/D to rotate", 10, 100, 20, BLUE); + + EndDrawing(); + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + CloseWindow(); // Close window and OpenGL context + //-------------------------------------------------------------------------------------- + + return 0; +} \ No newline at end of file diff --git a/examples/models/models_transformations.png b/examples/models/models_transformations.png new file mode 100644 index 0000000000000000000000000000000000000000..338b3383ed3fc5ebbe4bebf21a83d26776ca8e30 GIT binary patch literal 17874 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