Add
parent
a57505485e
commit
76b4223fea
456
as5/as5.cpp
456
as5/as5.cpp
|
|
@ -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,29 +53,37 @@ void DrawBoundedModel(raylib::Model &model, auto transformer) {
|
|||
auto box = model.GetTransformedBoundingBox();
|
||||
|
||||
// draw the bounding box of the model using raylib's built in function
|
||||
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;
|
||||
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>
|
||||
|
|
@ -70,61 +94,27 @@ struct Entity {
|
|||
return *cast;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
struct TransformComponent : public Component {
|
||||
raylib::Vector3 position = { 0, 0, 0 };
|
||||
raylib::Quaternion rotation = raylib::Quaternion::Identity();
|
||||
|
||||
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);
|
||||
void Update(float dt) {
|
||||
for (auto &c : components) {
|
||||
c->Update(dt);
|
||||
}
|
||||
|
||||
return old;
|
||||
});
|
||||
}
|
||||
|
||||
void Cleanup() override { }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Delegate { };
|
||||
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>,
|
||||
static_assert(std::is_same_v<TReturn, void>,
|
||||
"EC HW: Delegate only supports void return type");
|
||||
|
||||
for (const auto &func : functions) {
|
||||
|
|
@ -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);
|
||||
|
||||
auto makePenguin = [&](float xPos, float maxSpeed, float acceleration,
|
||||
float turningRate) -> Entity & {
|
||||
Entity &e = entities.emplace_back();
|
||||
e.AddComponent<TransformComponent>();
|
||||
e.AddComponent<DrawModelComponent>();
|
||||
e.GetComponent<DrawModelComponent>()->get().model = &penguin;
|
||||
|
||||
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 ¤t = 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();
|
||||
|
||||
skybox.Draw();
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue