From 4704f9ca7be564092630fda7b377927b1659662c Mon Sep 17 00:00:00 2001 From: Sara Date: Wed, 25 Sep 2024 11:58:37 +0200 Subject: [PATCH] feat: implemented camera controller --- src/camera_controller.c | 53 ++++++++++++++++++++++++++++++++++++++++ src/camera_controller.h | 30 +++++++++++++++++++++++ src/core/camera_node.c | 2 +- src/core/engine_loop.c | 3 ++- src/core/scene.c | 27 ++++++++++---------- src/core/transformable.c | 18 ++++++++++---- src/core/transformable.h | 10 +++++++- src/main.c | 19 ++++++++++---- src/test_object.c | 18 +++++++++++--- src/test_object.h | 3 +++ 10 files changed, 153 insertions(+), 30 deletions(-) create mode 100644 src/camera_controller.c create mode 100644 src/camera_controller.h diff --git a/src/camera_controller.c b/src/camera_controller.c new file mode 100644 index 0000000..cfcaf01 --- /dev/null +++ b/src/camera_controller.c @@ -0,0 +1,53 @@ +#include "camera_controller.h" +#include "utils/mirror.h" + +START_REFLECT(CameraController); +REFLECT_TYPECLASS(CameraController, SceneNodeEntity); +END_REFLECT(CameraController); + +impl_Drop_for(CameraController, + DestroyCameraController +) +impl_SceneNodeEntity_for(CameraController, + CameraControllerEnterTree, + CameraControllerExitTree, + CameraControllerTick +) + +SceneNode *CreateCameraController(Transformable target) { + CameraController *self = new(CameraController); + *self = (CameraController){ + .transform = tc_null(Transformable), + .rotation_speed = 10.f, + .max_speed_time = 4.f, + .target = target, + .time_disjointed = 0.f + }; + SceneNode *node = CreateSceneNode(CameraController_as_SceneNodeEntity(self)); + return node; +} + +void DestroyCameraController(CameraController *self) { + free(self); +} + +void CameraControllerEnterTree(CameraController *self) { + self->transform = TC_CAST(self->node->parent->entity, Transformable); + self->global = self->transform.tc->get_global_transform(self->transform.data); +} + +void CameraControllerExitTree(CameraController *self) {} + +void CameraControllerTick(CameraController *self, double delta) { + Transform current = self->transform.tc->get_global_transform(self->transform.data); + Transform target = self->target.tc->get_global_transform(self->target.data); + float const time_mul = fminf(1.f, powf(self->time_disjointed / self->max_speed_time, 0.8f)); + float const angle = QuaternionAngleDifference(current.rotation, target.rotation); + float const step = self->rotation_speed * time_mul * delta; + self->time_disjointed += delta; + if(step < angle) + target.rotation = QuaternionSlerp(current.rotation, target.rotation, step / angle); + else + self->time_disjointed = 0.f; + self->transform.tc->set_global_transform(self->transform.data, target); +} diff --git a/src/camera_controller.h b/src/camera_controller.h new file mode 100644 index 0000000..3df788c --- /dev/null +++ b/src/camera_controller.h @@ -0,0 +1,30 @@ +#ifndef CAMERA_CONTROLLER_H +#define CAMERA_CONTROLLER_H + +#include "core/transformable.h" +#include "core/scene_node_entity.h" +#include "utils/mirror.h" +#include "utils/drop.h" + +typedef struct CameraController { + SceneNode *node; + Transformable transform; + Transform global; + float rotation_speed; + float max_speed_time; + Transformable target; + float time_disjointed; +} CameraController; + +extern SceneNode *CreateCameraController(Transformable target); +extern void DestroyCameraController(CameraController *self); + +extern void CameraControllerEnterTree(CameraController *self); +extern void CameraControllerExitTree(CameraController *self); +extern void CameraControllerTick(CameraController *self, double delta); + +DECL_REFLECT(CameraController); +decl_typeclass_impl(SceneNodeEntity, CameraController); +decl_typeclass_impl(Drop, CameraController); + +#endif // !CAMERA_CONTROLLER_H diff --git a/src/core/camera_node.c b/src/core/camera_node.c index 45fb3c2..6d21434 100644 --- a/src/core/camera_node.c +++ b/src/core/camera_node.c @@ -51,7 +51,7 @@ Camera3D CameraNodeGetCamera(CameraNode *self) { Vector3 forward = MATRIX_FORWARD(mat); // construct a new camera at the global transform location and facing the forward vector return (Camera3D){ - .fovy = 70, + .fovy = 90, .position = global_transform.translation, .projection = CAMERA_PERSPECTIVE, .target = Vector3Add(global_transform.translation, forward), diff --git a/src/core/engine_loop.c b/src/core/engine_loop.c index 8029aa7..766ff8c 100644 --- a/src/core/engine_loop.c +++ b/src/core/engine_loop.c @@ -15,9 +15,10 @@ void InitializeRaylibContext() { InitWindow(1280, 800, "FOGD Engine"); if(!IsWindowFullscreen()) ToggleFullscreen(); - SetTargetFPS(120); + SetTargetFPS(60); } + void RunGame(Scene *initial_scene) { // set the main scene SwitchScene(initial_scene); diff --git a/src/core/scene.c b/src/core/scene.c index 50b9b4d..f0c28f1 100644 --- a/src/core/scene.c +++ b/src/core/scene.c @@ -5,18 +5,6 @@ #include "utils/drop.h" #include "utils/mirror.h" -//! Tick a node and all children recursively -static -void Internal_SceneNodeTick(SceneNode *self, double delta) { - // tick self first - self->entity.tc->tick(self->entity.data, delta); - // fix the number of elements to update now, - // so that if/when new children are added, they don't get ticked until next frame - size_t const len = self->children.len; - for(size_t i = 0; i < len; ++i) - Internal_SceneNodeTick(*list_at_as(SceneNode*, &self->children, i), delta); -} - //! Update global transform of a node static void Internal_SceneNodeUpdateTransform(SceneNode *self) { @@ -45,6 +33,19 @@ void Internal_SceneNodeUpdateTransformRecursive(SceneNode *self, bool is_dirty) Internal_SceneNodeUpdateTransformRecursive(*child, is_dirty); } +//! Tick a node and all children recursively +static +void Internal_SceneNodeTick(SceneNode *self, double delta) { + // tick self first + self->entity.tc->tick(self->entity.data, delta); + // fix the number of elements to update now, + // so that if/when new children are added, they don't get ticked until next frame + size_t const len = self->children.len; + for(size_t i = 0; i < len; ++i) { + Internal_SceneNodeTick(*list_at_as(SceneNode*, &self->children, i), delta); + } +} + //! Remove a single node (does NOT recurse to children children) from this scene. static void Internal_SceneRemoveNode(Scene *self, SceneNode *node) { @@ -181,6 +182,6 @@ void DestroyScene(Scene *self) { } void SceneTick(Scene* self, double delta_time) { - Internal_SceneNodeTick(self->root, delta_time); Internal_SceneNodeUpdateTransformRecursive(self->root, false); + Internal_SceneNodeTick(self->root, delta_time); } diff --git a/src/core/transformable.c b/src/core/transformable.c index 1de61b2..67f0ac2 100644 --- a/src/core/transformable.c +++ b/src/core/transformable.c @@ -21,7 +21,9 @@ Vector3 TransformPosition(Transform self, Vector3 local_pos) { Vector3 TransformDirection(Transform self, Vector3 local_direction) { Matrix const m = TransformGetMatrix(self); - return Vector3Add(Vector3Add(Vector3Scale(MATRIX_RIGHT(m), local_direction.x), Vector3Scale(MATRIX_UP(m), local_direction.y)), Vector3Scale(MATRIX_FORWARD(m), local_direction.z)); + return Vector3Add(Vector3Add(Vector3Scale(MATRIX_RIGHT(m), local_direction.x), + Vector3Scale(MATRIX_UP(m), local_direction.y)), + Vector3Scale(MATRIX_FORWARD(m), local_direction.z)); } Vector3 TransformScale(Transform self, Vector3 local_scale) { @@ -41,15 +43,15 @@ Transform TransformTransform(Transform self, Transform other) { } Vector3 InverseTransformPosition(Transform self, Vector3 global_pos) { - return Vector3Subtract(InverseTransformDirection(self, global_pos), self.translation); + return InverseTransformDirection(self, Vector3Subtract(global_pos, self.translation)); } Vector3 InverseTransformDirection(Transform self, Vector3 global_direction) { Matrix const mat = TransformGetMatrix(self); return (Vector3){ - Vector3DotProduct(MATRIX_RIGHT(mat), Vector3Scale(VECTOR3_RIGHT, global_direction.x)), - Vector3DotProduct(MATRIX_UP(mat), Vector3Scale(VECTOR3_UP, global_direction.y)), - Vector3DotProduct(MATRIX_FORWARD(mat), Vector3Scale(VECTOR3_FORWARD, global_direction.z)) + Vector3DotProduct((Vector3){global_direction.x, 0.f, 0.f}, MATRIX_RIGHT(mat)), + Vector3DotProduct((Vector3){0.f, global_direction.y, 0.f}, MATRIX_UP(mat)), + Vector3DotProduct((Vector3){0.f, 0.f, global_direction.z}, MATRIX_FORWARD(mat)) }; } @@ -68,3 +70,9 @@ Transform InverseTransformTransform(Transform self, Transform other) { .rotation = InverseTransformRotation(self, other.rotation) }; } + +float QuaternionAngleDifference(Quaternion a, Quaternion b) { + Quaternion c = QuaternionMultiply(a, QuaternionInvert(b)); + return 2.f*asinf(Vector3Length((Vector3){c.x, c.y, c.z})); +} + diff --git a/src/core/transformable.h b/src/core/transformable.h index c80884c..9839f2d 100644 --- a/src/core/transformable.h +++ b/src/core/transformable.h @@ -43,6 +43,8 @@ extern Vector3 InverseTransformScale(Transform self, Vector3 global_scale); extern Quaternion InverseTransformRotation(Transform self, Quaternion quat); extern Transform InverseTransformTransform(Transform self, Transform other); +extern float QuaternionAngleDifference(Quaternion a, Quaternion b); + #define MATRIX_RIGHT(self_) ((Vector3){self_.m0, self_.m1, self_.m2}) #define MATRIX_UP(self_) ((Vector3){self_.m4, self_.m5, self_.m6}) #define MATRIX_FORWARD(self_) ((Vector3){self_.m8, self_.m9, self_.m10}) @@ -60,7 +62,13 @@ extern Transform InverseTransformTransform(Transform self, Transform other); #define impl_Transformable_for(T)\ static Transform T##_get_transform_GEN_(T* self) { return self->transform; }\ static void T##_set_transform_GEN_(T* self, Transform value) { self->transform = value; self->dirty_bit = 1; } /*!< sets dirty bit */\ -static Transform T##_get_global_transform_GEN_(T* self) { return self->global_transform; }\ +static void T##_force_update_GEN_(T* self);\ +static Transform T##_get_global_transform_GEN_(T* self) {\ + if(self->dirty_bit) {\ + T##_force_update_GEN_(self);\ + }\ + return self->global_transform;\ +}\ static void T##_set_global_transform_GEN_(T* self, Transform value) {\ if(tc_is_null(self->parent_transformable)) {\ self->transform = value;\ diff --git a/src/main.c b/src/main.c index af3b917..9896aee 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,6 @@ #include "raylib.h" #include "test_object.h" +#include "camera_controller.h" #include "core/input.h" #include "core/camera_node.h" #include "core/engine_global.h" @@ -8,15 +9,18 @@ #include "core/transform_node.h" #include "core/mesh_render_entity.h" #include "utils/debug.h" +#include "utils/mirror.h" //! Create camera node with a transformable parent. SceneNode *CreateCameraScene() { SceneNode *camera_parent = CreateTransformNode(); - SceneNodeAddChild(camera_parent, CreateCameraNode()); + SceneNode *camera_transform = CreateTransformNode(); + SceneNodeAddChild(camera_parent, camera_transform); + SceneNodeAddChild(camera_transform, CreateCameraNode()); // set camera parent offset - Transformable transformable = TC_CAST(camera_parent->entity, Transformable); + Transformable transformable = TC_CAST(camera_transform->entity, Transformable); Transform transform = transformable.tc->get_transform(transformable.data); - transform.translation = (Vector3){0.f, 4.f, -10.f}; + transform.translation = (Vector3){-0.2f, 0.5f, -1.5f}; transform.rotation = QuaternionFromEuler(7.5f * DEG2RAD, 0.f, 0.f); transformable.tc->set_transform(transformable.data, transform); return camera_parent; @@ -26,11 +30,13 @@ SceneNode *CreateCameraScene() { SceneNode *CreateModelScene() { SceneNode *model_parent = CreateTransformNode(); // create the rest of the scene with a mesh and a functionality node - SceneNodeAddChild(model_parent, CreateMeshRenderEntity("spacefighter")); + SceneNodeAddChild(model_parent, CreateMeshRenderEntity("rider")); SceneNodeAddChild(model_parent, CreateTestObject()); // move the renderer's parent transform Transformable transformable = TC_CAST(model_parent->entity, Transformable); Transform transform = transformable.tc->get_global_transform(transformable.data); + transform.translation.x = 10.f; + transform.translation.y = 10.f; transformable.tc->set_global_transform(transformable.data, transform); return model_parent; } @@ -38,8 +44,10 @@ SceneNode *CreateModelScene() { Scene *CreateInitialScene() { SceneNode *root = CreateTransformNode(); SceneNode *model = CreateModelScene(); + SceneNode *camera_scene = CreateCameraScene(); SceneNodeAddChild(root, model); - SceneNodeAddChild(model, CreateCameraScene()); + SceneNodeAddChild(root, camera_scene); + SceneNodeAddChild(camera_scene, CreateCameraController(TC_CAST(model->entity, Transformable))); return CreateScene(root); } @@ -48,6 +56,7 @@ void ConfigureInput() { AddAction("pitch_down", INPUT_LISTENER_KEY, 0, KEY_W); AddAction("roll_left", INPUT_LISTENER_KEY, 0, KEY_A); AddAction("roll_right", INPUT_LISTENER_KEY, 0, KEY_D); + AddAction("stop", INPUT_LISTENER_KEY, 0, KEY_SPACE); } int main() { diff --git a/src/test_object.c b/src/test_object.c index b7820f8..95f0940 100644 --- a/src/test_object.c +++ b/src/test_object.c @@ -26,6 +26,8 @@ impl_SceneNodeEntity_for(TestObject, SceneNode *CreateTestObject() { TestObject *self = new(TestObject); + self->rotation = self->fly_input = Vector2Zero(); + self->stopped = false; return CreateSceneNode(TestObject_as_SceneNodeEntity(self)); } @@ -40,6 +42,7 @@ void TestObjectEnterTree(TestObject *self) { AddListener("pitch_down", ButtonInputListener(self, TestObjectDownInput)); AddListener("roll_left", ButtonInputListener(self, TestObjectLeftInput)); AddListener("roll_right", ButtonInputListener(self, TestObjectRightInput)); + AddListener("stop", ButtonInputListener(self, TestObjectStopInput)); } void TestObjectExitTree(TestObject *self) { @@ -50,11 +53,16 @@ void TestObjectDraw(TestObject *self) { } void TestObjectTick(TestObject *self, double delta) { + if(self->stopped) return; + Vector2 diff = Vector2Subtract(self->fly_input, self->rotation); + float const length = Vector2Length(diff); + if(length != 0.f) + self->rotation = Vector2Add(self->rotation, Vector2Scale(diff, 1.0f/length * fminf(4.f * delta, length))); Transform global_transform = self->transform.tc->get_global_transform(self->transform.data); Matrix global_matrix = TransformGetMatrix(global_transform); global_transform.translation = Vector3Add(global_transform.translation, Vector3Scale(MATRIX_FORWARD(global_matrix), 20.f * delta)); - global_transform.rotation = QuaternionMultiply(QuaternionFromAxisAngle(MATRIX_FORWARD(global_matrix), self->fly_input.x * 1.f * delta), global_transform.rotation); - global_transform.rotation = QuaternionMultiply(QuaternionFromAxisAngle(MATRIX_RIGHT(global_matrix), self->fly_input.y * 1.f * delta), global_transform.rotation); + global_transform.rotation = QuaternionMultiply(QuaternionFromAxisAngle(MATRIX_FORWARD(global_matrix), self->rotation.x * 4.f * delta), global_transform.rotation); + global_transform.rotation = QuaternionMultiply(QuaternionFromAxisAngle(MATRIX_RIGHT(global_matrix), self->rotation.y * 4.f * delta), global_transform.rotation); self->transform.tc->set_global_transform(self->transform.data, global_transform); } @@ -65,14 +73,16 @@ void TestObjectLeftInput(TestObject *self, bool value) { void TestObjectRightInput(TestObject *self, bool value) { self->fly_input.x += value ? +1 : -1; - LOG_INFO("Input Right"); } void TestObjectUpInput(TestObject *self, bool value) { self->fly_input.y += value ? -1 : +1; - LOG_INFO("Input Up"); } void TestObjectDownInput(TestObject *self, bool value) { self->fly_input.y += value ? +1 : -1; } + +void TestObjectStopInput(TestObject *self, bool value) { + self->stopped = value; +} diff --git a/src/test_object.h b/src/test_object.h index ce83a0e..73579de 100644 --- a/src/test_object.h +++ b/src/test_object.h @@ -13,6 +13,8 @@ typedef struct TestObject { SceneNode *node; Transformable transform; Vector2 fly_input; + Vector2 rotation; + bool stopped; } TestObject; SceneNode *CreateTestObject(); @@ -27,6 +29,7 @@ extern void TestObjectLeftInput(TestObject *self, bool value); extern void TestObjectRightInput(TestObject *self, bool value); extern void TestObjectUpInput(TestObject *self, bool value); extern void TestObjectDownInput(TestObject *self, bool value); +extern void TestObjectStopInput(TestObject *self, bool value); DECL_REFLECT(TestObject); decl_typeclass_impl(SceneNodeEntity, TestObject);