master
John Montagu, the 4th Earl of Sandvich 2026-03-08 22:38:01 -07:00
parent a57505485e
commit 76b4223fea
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
1 changed files with 392 additions and 84 deletions

View File

@ -7,17 +7,33 @@
#include "RadiansDegrees.hpp"
#include "Vector3.hpp"
#include "raylib.h"
#include <BufferedRaylib.hpp>
#include <raylib-cpp.hpp>
#include <algorithm>
#include <cmath>
#include <concepts>
#include <functional>
#include <iostream>
#include <memory>
#include <optional>
#include <raylib-cpp.hpp>
#include <vector>
#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<std::shared_ptr<Component>> components;
template<std::derived_from<Component> T>
T &AddComponent() {
std::shared_ptr<Component> out = components.emplace_back(std::make_shared<T>());
out->entity = this;
return (T &)*out;
template <std::derived_from<Component> T> T &AddComponent() {
auto &ptr = components.emplace_back(std::make_shared<T>());
ptr->entity = this;
ptr->Setup();
return static_cast<T &>(*ptr);
}
template<std::derived_from<Component> T>
template <std::derived_from<Component> T>
std::optional<std::reference_wrapper<T>> GetComponent() const {
for (auto &c : components) {
T *cast = dynamic_cast<T *>(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 <typename T> 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<TransformComponent>();
if (t) {
return old
.Translate(t->get().position)
.Rotate(t->get().rotation);
}
return old;
});
}
void Cleanup() override { }
};
template<typename T>
struct Delegate { };
template<typename TReturn, typename... TArgs>
struct Delegate<TReturn(TArgs...)> {
template <typename TReturn, typename... TArgs> struct Delegate<TReturn(TArgs...)> {
std::vector<std::function<void(int)>> functions;
void connect(const std::function<TReturn(TArgs ...)> &func) {
functions.push_back(func);
}
void connect(const std::function<TReturn(TArgs...)> &func) { functions.push_back(func); }
void operator+=(const std::function<TReturn(TArgs ...)> &func) {
connect(func);
}
void operator+=(const std::function<TReturn(TArgs...)> &func) { connect(func); }
void operator()(int arg) {
static_assert(
std::is_same_v<TReturn, void>,
"EC HW: Delegate only supports void return type");
static_assert(std::is_same_v<TReturn, void>,
"EC HW: Delegate only supports void return type");
for (const auto &func : functions) {
f(std::forward<TArgs>(arg)...);
@ -133,62 +123,380 @@ struct Delegate<TReturn(TArgs...)> {
}
};
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<TransformComponent>();
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<TransformComponent>();
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<TransformComponent>();
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<TransformComponent>();
auto targetT = target->GetComponent<TransformComponent>();
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<OrientedPhysicsComponent>();
auto tf = entity->GetComponent<TransformComponent>();
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<PhysicsComponent>();
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<Entity> entities;
entities.reserve(10);
Entity &e = entities.emplace_back();
e.AddComponent<TransformComponent>();
e.AddComponent<DrawModelComponent>();
e.GetComponent<DrawModelComponent>()->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<TransformComponent>();
tf.position = raylib::Vector3(xPos, 0.0f, 0.0f);
tf.heading = raylib::Degree(90.0f); // face right
auto &phys = e.AddComponent<OrientedPhysicsComponent>();
phys.maxSpeed = maxSpeed;
phys.acceleration = acceleration;
phys.turningRate = turningRate;
auto &render = e.AddComponent<RenderComponent>();
render.model = &penguinModel;
auto &inp = e.AddComponent<InputComponent>();
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<TransformComponent>();
tf.position = raylib::Vector3(xPos, 200.0f, 0.0f);
tf.heading = raylib::Degree(90.0f);
auto &follow = e.AddComponent<FollowComponent>();
follow.target = &penguin;
follow.maxSpeed = 220.0f;
follow.acceleration = 70.0f;
follow.turningRate = 100.0f;
follow.hoverHeight = 200.0f;
auto &render = e.AddComponent<RenderComponent>();
render.model = &eagleModel;
auto &inp = e.AddComponent<InputComponent>();
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<RenderComponent>()) {
r->get().drawBoundingBox = false;
}
if (auto i = old.GetComponent<InputComponent>()) {
i->get().isActive = false;
}
selectedIdx = (selectedIdx + 1) % (int)entities.size();
auto &current = entities[selectedIdx];
if (auto r = current.GetComponent<RenderComponent>()) {
r->get().drawBoundingBox = true;
}
if (auto i = current.GetComponent<InputComponent>()) {
i->get().isActive = true;
}
});
if (auto r = entities[selectedIdx].GetComponent<RenderComponent>()) {
r->get().drawBoundingBox = true;
}
if (auto i = entities[selectedIdx].GetComponent<InputComponent>()) {
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>()) {
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<Component> &c : e.components) {
c->Update(dt);
}
for (Entity &e : entities) {
e.Update(dt);
}
camera.EndMode();
window.EndDrawing();
}