From 76b4223fea692ed7db2a9210de50790a471ebc7e Mon Sep 17 00:00:00 2001 From: HumanoidSandvichDispenser Date: Sun, 8 Mar 2026 22:38:01 -0700 Subject: [PATCH] Add --- as5/as5.cpp | 476 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 392 insertions(+), 84 deletions(-) diff --git a/as5/as5.cpp b/as5/as5.cpp index eb71070..43212b5 100644 --- a/as5/as5.cpp +++ b/as5/as5.cpp @@ -7,17 +7,33 @@ #include "RadiansDegrees.hpp" #include "Vector3.hpp" #include "raylib.h" +#include +#include + +#include +#include #include #include +#include #include #include -#include #include #define SKYBOX_IMPLEMENTATION #include "skybox.hpp" -void DrawBoundedModel(raylib::Model &model, auto transformer) { +raylib::Degree angle_normalize(raylib::Degree angle) { + float decimal = float(angle) - int(angle); + int normalized = (int(angle) % 360 + 360) % 360; + return raylib::Degree(normalized + decimal); +} + +raylib::Vector3 velocity_from_speed_heading(float speed, raylib::Degree heading) { + return raylib::Vector3{speed * std::sin(heading.RadianValue()), 0.0f, + speed * std::cos(heading.RadianValue())}; +} + +void DrawBoundedModel(raylib::Model &model, bool drawBoundingBox, auto transformer) { // store the original transform to apply a different transform to the // model without affecting the next time we draw raylib::Matrix oldTransform = model.GetTransform(); @@ -37,32 +53,40 @@ void DrawBoundedModel(raylib::Model &model, auto transformer) { auto box = model.GetTransformedBoundingBox(); // draw the bounding box of the model using raylib's built in function - DrawBoundingBox(box, raylib::Color::White()); + if (drawBoundingBox) { + DrawBoundingBox(box, raylib::Color::White()); + } // restore the model's transform to its original state so that the next time we // draw the model, it doesn't have the previous transform applied to it model.SetTransform(oldTransform); } +struct Entity; +class Component; + class Component { -public: - struct Entity *entity; + public: + Entity *entity = nullptr; + virtual void Setup() = 0; virtual void Update(float dt) = 0; virtual void Cleanup() = 0; + + virtual ~Component() = default; }; struct Entity { std::vector> components; - template T> - T &AddComponent() { - std::shared_ptr out = components.emplace_back(std::make_shared()); - out->entity = this; - return (T &)*out; + template T> T &AddComponent() { + auto &ptr = components.emplace_back(std::make_shared()); + ptr->entity = this; + ptr->Setup(); + return static_cast(*ptr); } - template T> + template T> std::optional> GetComponent() const { for (auto &c : components) { T *cast = dynamic_cast(c.get()); @@ -70,62 +94,28 @@ struct Entity { return *cast; } } + return {}; + } - return { }; + void Update(float dt) { + for (auto &c : components) { + c->Update(dt); + } } }; -struct TransformComponent : public Component { - raylib::Vector3 position = { 0, 0, 0 }; - raylib::Quaternion rotation = raylib::Quaternion::Identity(); +template struct Delegate {}; - void Setup() override { } - - void Update(float dt) override { } - - void Cleanup() override { } -}; - -struct DrawModelComponent : public Component { - raylib::Model *model; - - void Setup() override { } - - void Update(float dt) override { - DrawBoundedModel(*model, [this](raylib::Transform old) { - auto t = entity->GetComponent(); - if (t) { - return old - .Translate(t->get().position) - .Rotate(t->get().rotation); - } - - return old; - }); - } - - void Cleanup() override { } -}; - -template -struct Delegate { }; - -template -struct Delegate { +template struct Delegate { std::vector> functions; - void connect(const std::function &func) { - functions.push_back(func); - } + void connect(const std::function &func) { functions.push_back(func); } - void operator+=(const std::function &func) { - connect(func); - } + void operator+=(const std::function &func) { connect(func); } void operator()(int arg) { - static_assert( - std::is_same_v, - "EC HW: Delegate only supports void return type"); + static_assert(std::is_same_v, + "EC HW: Delegate only supports void return type"); for (const auto &func : functions) { f(std::forward(arg)...); @@ -133,62 +123,380 @@ struct Delegate { } }; -raylib::Degree angle_normalize(raylib::Degree angle) { - float decimal = float(angle) - int(angle); - int normalized = (int(angle) % 360 + 360) % 360; - return raylib::Degree(normalized + decimal); -} +struct TransformComponent : public Component { + raylib::Vector3 position = {0, 0, 0}; + raylib::Degree heading = 0; + + void Setup() override {} + void Update(float dt) override {} + void Cleanup() override {} +}; + +struct RenderComponent : public Component { + raylib::Model *model = nullptr; + bool drawBoundingBox = false; + float modelScale = 40.0f; + + void Setup() override {} + void Cleanup() override {} + + void Update(float dt) override { + if (!model) { + return; + } + + auto t = entity->GetComponent(); + if (!t) { + return; + } + + TransformComponent &tf = t->get(); + + DrawBoundedModel(*model, drawBoundingBox, [&tf, this](raylib::Matrix transform) { + return transform.RotateY(tf.heading) + .Scale(modelScale, modelScale, modelScale) + .Translate(tf.position); + }); + } +}; + +struct PhysicsComponent : public Component { + raylib::Vector3 velocity = {0, 0, 0}; + float maxSpeed = 300.0f; + float acceleration = 100.0f; + float turningRate = 90.0f; + + void Setup() override {} + void Cleanup() override {} + + void Update(float dt) override { + auto t = entity->GetComponent(); + if (!t) { + return; + } + t->get().position = t->get().position + velocity * dt; + } +}; + +struct OrientedPhysicsComponent : public PhysicsComponent { + float speed = 0.0f; + + void Setup() override {} + void Cleanup() override {} + + void Update(float dt) override { + auto t = entity->GetComponent(); + if (!t) { + return; + } + + TransformComponent &tf = t->get(); + + speed = std::clamp(speed, -maxSpeed, maxSpeed); + + velocity = velocity_from_speed_heading(speed, tf.heading); + + tf.position = tf.position + velocity * dt; + } +}; + +struct FollowComponent : public Component { + Entity *target = nullptr; + float hoverHeight = 200.0f; + float speed = 0.0f; + float maxSpeed = 250.0f; + float acceleration = 80.0f; + float turningRate = 120.0f; + float epsilon = 8.0f; + + void Setup() override {} + void Cleanup() override {} + + void Update(float dt) override { + if (!target) { + return; + } + + auto myT = entity->GetComponent(); + auto targetT = target->GetComponent(); + if (!myT || !targetT) { + return; + } + + TransformComponent &myTf = myT->get(); + TransformComponent &targetTf = targetT->get(); + + raylib::Vector3 desired = {targetTf.position.x, targetTf.position.y + hoverHeight, + targetTf.position.z}; + + raylib::Vector3 toDesired = desired - myTf.position; + float distXZ = std::sqrt(toDesired.x * toDesired.x + toDesired.z * toDesired.z); + + if (distXZ > epsilon) { + float desiredHeadingRad = std::atan2(toDesired.x, toDesired.z); + raylib::Degree desiredHeading = raylib::Degree(desiredHeadingRad * RAD2DEG); + + float diff = float(desiredHeading) - float(myTf.heading); + + // norm to [-180, 180] for shortest turn direction + angle_normalize(raylib::Degree(diff + 180) - raylib::Degree(180)); + + float maxTurn = turningRate * dt; + float turn = std::clamp(diff, -maxTurn, maxTurn); + myTf.heading = angle_normalize(myTf.heading + raylib::Degree(turn)); + + speed = std::min(speed + acceleration * dt, maxSpeed); + } else { + speed = std::max(speed - acceleration * dt, 0.0f); + } + + raylib::Vector3 move = velocity_from_speed_heading(speed, myTf.heading); + + myTf.position.x += move.x * dt; + myTf.position.z += move.z * dt; + + float yDiff = desired.y - myTf.position.y; + myTf.position.y += yDiff * 5.0f * dt; + } +}; + +struct InputComponent : public Component { + raylib::BufferedInput *input = nullptr; + bool isActive = false; + + float axisForward = 0.0f; + float axisTurn = 0.0f; + bool keySpace = false; + + void Setup() override { + if (!input) { + return; + } + + input->actions["forward"].AddCallback( + [this](float state, float delta) { axisForward = state; }); + + input->actions["turn"].AddCallback([this](float state, float delta) { axisTurn = state; }); + + input->actions["space"].AddCallback( + [this](float state, float delta) { keySpace = (state > 0.0f); }); + } + + void Cleanup() override {} + + void Update(float dt) override { + if (!isActive) { + return; + } + + // might not be efficient + auto oriented = entity->GetComponent(); + auto tf = entity->GetComponent(); + if (!tf) { + return; + } + + if (oriented) { + OrientedPhysicsComponent &phys = oriented->get(); + + float forward = axisForward; + float turn = axisTurn; + + phys.speed += forward * phys.acceleration * dt; + tf->get().heading = + angle_normalize(tf->get().heading + raylib::Degree(turn * phys.turningRate * dt)); + + if (keySpace) { + phys.speed *= std::pow(0.05f, dt); + } + } else { + auto phys = entity->GetComponent(); + if (!phys) { + return; + } + + PhysicsComponent &p = phys->get(); + + float spd = std::sqrt(p.velocity.x * p.velocity.x + p.velocity.z * p.velocity.z); + float forward = axisForward; + float turn = axisTurn; + + spd += forward * p.acceleration * dt; + tf->get().heading = + angle_normalize(tf->get().heading + raylib::Degree(turn * p.turningRate * dt)); + + if (keySpace) { + spd *= std::pow(0.05f, dt); + } + + spd = std::clamp(spd, -p.maxSpeed, p.maxSpeed); + p.velocity = velocity_from_speed_heading(spd, tf->get().heading); + } + } +}; int main() { raylib::Window window(800, 600, "CS381 - Assignment 5"); window.SetState(FLAG_WINDOW_RESIZABLE); raylib::AudioDevice audio; - raylib::Model penguin("models/penguin.glb"); - raylib::Transform penguinTransform = raylib::Transform::Identity() - .Scale(40, 40, 40); - penguin.SetTransform(penguinTransform); + raylib::Model penguinModel("models/penguin.glb"); + raylib::Model eagleModel("models/eagle.glb"); - raylib::Camera3D camera( - { 0, 400, -40 }, - { 0, -0.25f, 1 }, - { 0, 1, 0 }, - 45.0f); + auto defaultPenguinTf = (raylib::Transform)penguinModel.GetTransform(); + defaultPenguinTf = defaultPenguinTf.RotateY(raylib::Degree(90)); + penguinModel.SetTransform(defaultPenguinTf); raylib::Model ground = raylib::Mesh::Plane(10000, 10000, 50, 50, 25); - raylib::Texture snowTexture("textures/snow.jpg"); ground.GetMaterials()[0].maps[MATERIAL_MAP_DIFFUSE].texture = snowTexture; cs381::SkyBox skybox("textures/skybox.png"); + raylib::Camera3D camera({0, 400, -768}, {0, -8, 0}, {0, 1, 0}, 45.0f); + + raylib::BufferedInput input; + + input.actions["forward"] = + raylib::Action::button_axis({raylib::Button::key(KEY_W)}, {raylib::Button::key(KEY_S)}) + .move(); + input.actions["turn"] = + raylib::Action::button_axis({raylib::Button::key(KEY_A)}, {raylib::Button::key(KEY_D)}) + .move(); + input.actions["space"] = raylib::Action::key(KEY_SPACE).move(); + input.actions["tab"] = raylib::Action::key(KEY_TAB).move(); + std::vector entities; + entities.reserve(10); - Entity &e = entities.emplace_back(); - e.AddComponent(); - e.AddComponent(); - e.GetComponent()->get().model = &penguin; + auto makePenguin = [&](float xPos, float maxSpeed, float acceleration, + float turningRate) -> Entity & { + Entity &e = entities.emplace_back(); - window.SetTargetFPS(60); // save cpu cycles + auto &tf = e.AddComponent(); + tf.position = raylib::Vector3(xPos, 0.0f, 0.0f); + tf.heading = raylib::Degree(90.0f); // face right + + auto &phys = e.AddComponent(); + phys.maxSpeed = maxSpeed; + phys.acceleration = acceleration; + phys.turningRate = turningRate; + + auto &render = e.AddComponent(); + render.model = &penguinModel; + + auto &inp = e.AddComponent(); + inp.input = &input; + inp.Setup(); + + return e; + }; + + // helper to add an eagle that follows a specific penguin entity + auto makeEagle = [&](Entity &penguin, float xPos) -> Entity & { + Entity &e = entities.emplace_back(); + + auto &tf = e.AddComponent(); + tf.position = raylib::Vector3(xPos, 200.0f, 0.0f); + tf.heading = raylib::Degree(90.0f); + + auto &follow = e.AddComponent(); + follow.target = &penguin; + follow.maxSpeed = 220.0f; + follow.acceleration = 70.0f; + follow.turningRate = 100.0f; + follow.hoverHeight = 200.0f; + + auto &render = e.AddComponent(); + render.model = &eagleModel; + + auto &inp = e.AddComponent(); + inp.input = &input; + inp.Setup(); + + return e; + }; + + Entity &p0 = makePenguin(-400.0f, 50.0f, 50.0f, 90.0f); + Entity &p1 = makePenguin(-200.0f, 350.0f, 140.0f, 60.0f); + Entity &p2 = makePenguin(0.0f, 150.0f, 220.0f, 200.0f); + Entity &p3 = makePenguin(200.0f, 450.0f, 60.0f, 45.0f); + Entity &p4 = makePenguin(400.0f, 800.0f, 4000.0f, 500.0f); + + makeEagle(p0, -400.0f); + makeEagle(p1, -200.0f); + makeEagle(p2, 0.0f); + makeEagle(p3, 200.0f); + makeEagle(p4, 400.0f); + + int selectedIdx = 0; + + input.actions["tab"].AddPressedCallback([&]() { + auto &old = entities[selectedIdx]; + if (auto r = old.GetComponent()) { + r->get().drawBoundingBox = false; + } + if (auto i = old.GetComponent()) { + i->get().isActive = false; + } + + selectedIdx = (selectedIdx + 1) % (int)entities.size(); + + auto ¤t = entities[selectedIdx]; + if (auto r = current.GetComponent()) { + r->get().drawBoundingBox = true; + } + if (auto i = current.GetComponent()) { + i->get().isActive = true; + } + }); + + if (auto r = entities[selectedIdx].GetComponent()) { + r->get().drawBoundingBox = true; + } + + if (auto i = entities[selectedIdx].GetComponent()) { + i->get().isActive = true; + } + + window.SetTargetFPS(60); while (!window.ShouldClose()) { + float dt = window.GetFrameTime(); + + input.PollEvents(); + window.BeginDrawing(); window.ClearBackground(raylib::Color::Gray()); - float dt = window.GetFrameTime(); + // could probably make the camera be an entity and have a transform + // and have a camera component but nah + if (auto tf = entities[selectedIdx].GetComponent()) { + TransformComponent &actualTfForReal = tf->get(); + raylib::Vector3 forward = velocity_from_speed_heading(1.0f, actualTfForReal.heading); + + const float camDistance = 768.0f; + const float camHeight = 400.0f; + + camera.SetPosition({actualTfForReal.position.x - forward.x * camDistance, + actualTfForReal.position.y + camHeight, + actualTfForReal.position.z - forward.z * camDistance}); + + camera.SetTarget({actualTfForReal.position.x, actualTfForReal.position.y - 8.0f, + actualTfForReal.position.z}); + } camera.BeginMode(); + skybox.Draw(); + ground.Draw({0, 0, 0}, 1.0f, raylib::Color::White()); - ground.Draw({ 0, 0, 0 }, 1.0f, raylib::Color::White()); - - for (const Entity &e : entities) { - for (const std::shared_ptr &c : e.components) { - c->Update(dt); - } + for (Entity &e : entities) { + e.Update(dt); } camera.EndMode(); + window.EndDrawing(); }