Add AS5 files

master
John Montagu, the 4th Earl of Sandvich 2026-03-08 21:58:03 -07:00
parent 4366b39fc5
commit 938419b0bb
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
9 changed files with 621 additions and 0 deletions

24
as5/CMakeLists.txt 100644
View File

@ -0,0 +1,24 @@
cmake_minimum_required(VERSION 3.18)
project(as3 CXX)
set(CMAKE_CXX_STANDARD 20)
# adding this option to make clangd work
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
add_subdirectory(../raylib-cpp raylib)
include(../assets/includeable.cmake)
add_executable(as5 as5.cpp skybox.cpp)
target_link_libraries(as5 PUBLIC raylib raylib_cpp)
make_includeable(../assets/shaders/skybox.vs generated/skybox.vs)
make_includeable(../assets/shaders/skybox.fs generated/skybox.fs)
make_includeable(../assets/shaders/cubemap.vs generated/cubemap.vs)
make_includeable(../assets/shaders/cubemap.fs generated/cubemap.fs)
configure_file(../assets/models/penguin.glb models/penguin.glb COPYONLY)
configure_file(../assets/models/eagle.glb models/eagle.glb COPYONLY)
configure_file(../assets/textures/skybox.png textures/skybox.png COPYONLY)
configure_file(../assets/textures/snow.jpg textures/snow.jpg COPYONLY)

71
as5/README.md 100644
View File

@ -0,0 +1,71 @@
# Building and Running
Clone the repository, navigate to the root of the project, and initialize the
submodules:
```sh
git clone https://github.com/humanoidsandvichdispenser/cs381.git
cd cs381
git submodule update --init --recursive
```
Navigate to the `as4` directory, create a build directory, and run CMake to
generate the build files:
```sh
cd as4
mkdir -p build
cd build
cmake ..
```
Compile the code using `make`:
```sh
make
```
This should create an executable named `as4` in the `build` directory. You can
run the executable with the following command:
```sh
./as4
```
# Instructions on how to use the program
Hold W and S to accelerate the selected entity forward and backward. Use A and
D to change heading direction. This allows you to steer the entity around the
environment.
Use TAB to switch between entities. The currently selected entity will
be drawn with a bounding box.
When the eagle is selected, press Q and Z to change the pitch.
# Readme Question
The entity selection system works by when TAB is pressed (key down only on one
frame), the program increments the index of the currently selected entity.
Selection wraps around to the first entity when it exceeds the number of
entities in the game. Movement keys only apply to the selected entity, which is
determined by the selected index. When drawing, the selected index is compared
to the index of each entity, and if they match, a bounding box is drawn around
the entity to indicate that it is selected.
Both monolithic and ad-hoc approaches to entity management are fast to setup
since they require the least amount of code/boilerplate to get something
working. However, monolithic entities are easier to scale, since in ad-hoc, you
have to write the entire state and behavior of the entity (its variables,
methods, etc) for each entity that exists. Ad-hoc is faster to work with only
when managing few entities with very different behavior.
For this particular assignment, the monolithic approach is more suitable since
most of the entities share similar behavior (change velocity) and state
(position, speed, heading). This however still became more difficult to manage
when adding the eagle that had an additional pitch variable and flying
behavior.
# Extra Credit
The eagle can move in 3D space with an additional DOF compared to the penguins.

196
as5/as5.cpp 100644
View File

@ -0,0 +1,196 @@
#include "AudioDevice.hpp"
#include "Color.hpp"
#include "Keyboard.hpp"
#include "Matrix.hpp"
#include "Mesh.hpp"
#include "Model.hpp"
#include "RadiansDegrees.hpp"
#include "Vector3.hpp"
#include "raylib.h"
#include <concepts>
#include <functional>
#include <memory>
#include <optional>
#include <raylib-cpp.hpp>
#include <vector>
#define SKYBOX_IMPLEMENTATION
#include "skybox.hpp"
void DrawBoundedModel(raylib::Model &model, 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();
// apply the transform that we get from whatever the transformer callback
// gives us
raylib::Matrix transform = transformer(model.GetTransform());
// apply the transform that we got from the transformer to the model
model.SetTransform(transform);
// draw the model, passing the origin and default scale as arguments since
// the transform is already applied to the model
model.Draw({ 0, 0, 0 }, 1.0f, raylib::Color::White());
// get the bounding box of the model after applying the transform
auto box = model.GetTransformedBoundingBox();
// draw the bounding box of the model using raylib's built in function
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);
}
class Component {
public:
struct Entity *entity;
virtual void Setup() = 0;
virtual void Update(float dt) = 0;
virtual void Cleanup() = 0;
};
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>
std::optional<std::reference_wrapper<T>> GetComponent() const {
for (auto &c : components) {
T *cast = dynamic_cast<T *>(c.get());
if (cast) {
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);
}
return old;
});
}
void Cleanup() override { }
};
template<typename T>
struct Delegate { };
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 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");
for (const auto &func : functions) {
f(std::forward<TArgs>(arg)...);
}
}
};
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);
}
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::Camera3D camera(
{ 0, 400, -40 },
{ 0, -0.25f, 1 },
{ 0, 1, 0 },
45.0f);
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");
std::vector<Entity> entities;
Entity &e = entities.emplace_back();
e.AddComponent<TransformComponent>();
e.AddComponent<DrawModelComponent>();
e.GetComponent<DrawModelComponent>()->get().model = &penguin;
window.SetTargetFPS(60); // save cpu cycles
while (!window.ShouldClose()) {
window.BeginDrawing();
window.ClearBackground(raylib::Color::Gray());
float dt = window.GetFrameTime();
camera.BeginMode();
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);
}
}
camera.EndMode();
window.EndDrawing();
}
return 0;
}

View File

@ -0,0 +1,30 @@
R"for_C++_include(#version 330
// Input vertex attributes (from vertex shader)
in vec3 fragPosition;
// Input uniform values
uniform sampler2D equirectangularMap;
// Output fragment color
out vec4 finalColor;
vec2 SampleSphericalMap(vec3 v)
{
vec2 uv = vec2(atan(v.z, v.x), asin(v.y));
uv *= vec2(0.1591, 0.3183);
uv += 0.5;
return uv;
}
void main()
{
// Normalize local position
vec2 uv = SampleSphericalMap(normalize(fragPosition));
// Fetch color from texture map
vec3 color = texture(equirectangularMap, uv).rgb;
// Calculate final fragment color
finalColor = vec4(color, 1.0);
})for_C++_include"

View File

@ -0,0 +1,20 @@
R"for_C++_include(#version 330
// Input vertex attributes
in vec3 vertexPosition;
// Input uniform values
uniform mat4 matProjection;
uniform mat4 matView;
// Output vertex attributes (to fragment shader)
out vec3 fragPosition;
void main()
{
// Calculate fragment position based on model transformations
fragPosition = vertexPosition;
// Calculate final vertex position
gl_Position = matProjection*matView*vec4(vertexPosition, 1.0);
})for_C++_include"

View File

@ -0,0 +1,30 @@
R"for_C++_include(#version 330
// Input vertex attributes (from vertex shader)
in vec3 fragPosition;
// Input uniform values
uniform samplerCube environmentMap;
uniform bool vflipped;
uniform bool doGamma;
// Output fragment color
out vec4 finalColor;
void main()
{
// Fetch color from texture map
vec3 color = vec3(0.0);
if (vflipped) color = texture(environmentMap, vec3(fragPosition.x, -fragPosition.y, fragPosition.z)).rgb;
else color = texture(environmentMap, fragPosition).rgb;
if (doGamma)// Apply gamma correction
{
color = color/(color + vec3(1.0));
color = pow(color, vec3(1.0/2.2));
}
// Calculate final fragment color
finalColor = vec4(color, 1.0);
})for_C++_include"

View File

@ -0,0 +1,24 @@
R"for_C++_include(#version 330
// Input vertex attributes
in vec3 vertexPosition;
// Input uniform values
uniform mat4 matProjection;
uniform mat4 matView;
// Output vertex attributes (to fragment shader)
out vec3 fragPosition;
void main()
{
// Calculate fragment position based on model transformations
fragPosition = vertexPosition;
// Remove translation from the view matrix
mat4 rotView = mat4(mat3(matView));
vec4 clipPos = matProjection*rotView*vec4(vertexPosition, 1.0);
// Calculate final vertex position
gl_Position = clipPos;
})for_C++_include"

168
as5/skybox.cpp 100644
View File

@ -0,0 +1,168 @@
/*******************************************************************************************
*
* raylib [models] example - Skybox loading and drawing
*
* Example originally created with raylib 1.8, last time updated with raylib 4.0
*
* 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) 2017-2023 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
#include "skybox.hpp"
#include <iostream>
#include "rlgl.h"
namespace cs381 {
SkyBox& SkyBox::Init() {
// Load skybox model
cube = raylib::Mesh::Cube(1.0f, 1.0f, 1.0f).LoadModelFrom();
// Load skybox shader and set required locations
// NOTE: Some locations are automatically set at shader loading
shader = raylib::Shader::LoadFromMemory(vertexShader, fragmentShader);
cube.materials[0].shader = shader;
shader.SetValue("environmentMap", (int)MATERIAL_MAP_CUBEMAP, SHADER_UNIFORM_INT);
return *this;
}
SkyBox& SkyBox::Load(const std::string_view filename, bool isEnviornment/* = false*/) {
if(shader.id == 0) Init();
shader.SetValue("doGamma", int(isEnviornment ? 1 : 0), SHADER_UNIFORM_INT);
shader.SetValue("vflipped", int(isEnviornment ? 1 : 0), SHADER_UNIFORM_INT);
if(isEnviornment) {
if(cubemapShader.id == 0){
cubemapShader = raylib::Shader::LoadFromMemory(cubemapVertexShader, cubemapFragmentShader);
cubemapShader.SetValue("equirectangularMap", int(0), SHADER_UNIFORM_INT);
}
// Load HDR panorama (sphere) texture
texture.Load(filename);
// Make sure that things aren't sampled in a pixelated manor!
texture.SetFilter(TEXTURE_FILTER_BILINEAR);
// Generate cubemap (texture with 6 quads-cube-mapping) from panorama HDR texture
// NOTE 1: New texture is generated rendering to texture, shader calculates the sphere->cube coordinates mapping
// NOTE 2: It seems on some Android devices WebGL, fbo does not properly support a FLOAT-based attachment,
// despite texture can be successfully created.. so using PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 instead of PIXELFORMAT_UNCOMPRESSED_R32G32B32A32
cube.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = GenTextureCubemap(cubemapShader, texture, 1024, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8);
} else {
raylib::Image img(filename);
texture.Load(img, CUBEMAP_LAYOUT_AUTO_DETECT);
// Make sure that things aren't sampled in a pixelated manor!
texture.SetFilter(TEXTURE_FILTER_BILINEAR);
cube.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture = texture; // CUBEMAP_LAYOUT_PANORAMA
}
return *this;
}
SkyBox& SkyBox::Draw() {
// We are inside the cube, we need to disable backface culling!
rlDisableBackfaceCulling();
rlDisableDepthMask();
cube.Draw({});
rlEnableBackfaceCulling();
rlEnableDepthMask();
return *this;
}
// Generate cubemap texture from HDR texture
TextureCubemap SkyBox::GenTextureCubemap(Shader shader, Texture2D panorama, int size, int format) {
TextureCubemap cubemap = { 0 };
rlDisableBackfaceCulling(); // Disable backface culling to render inside the cube
// STEP 1: Setup framebuffer
//------------------------------------------------------------------------------------------
unsigned int rbo = rlLoadTextureDepth(size, size, true);
cubemap.id = rlLoadTextureCubemap(0, size, format, 1);
// unsigned int fbo = rlLoadFramebuffer(size, size);
unsigned int fbo = rlLoadFramebuffer();
rlFramebufferAttach(fbo, rbo, RL_ATTACHMENT_DEPTH, RL_ATTACHMENT_RENDERBUFFER, 0);
rlFramebufferAttach(fbo, cubemap.id, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_CUBEMAP_POSITIVE_X, 0);
// Check if framebuffer is complete with attachments (valid)
if (rlFramebufferComplete(fbo)) TraceLog(LOG_INFO, "FBO: [ID %i] Framebuffer object created successfully", fbo);
//------------------------------------------------------------------------------------------
// STEP 2: Draw to framebuffer
//------------------------------------------------------------------------------------------
// NOTE: Shader is used to convert HDR equirectangular environment map to cubemap equivalent (6 faces)
rlEnableShader(shader.id);
// Define projection matrix and send it to shader
Matrix matFboProjection = MatrixPerspective(90.0*DEG2RAD, 1.0, RL_CULL_DISTANCE_NEAR, RL_CULL_DISTANCE_FAR);
rlSetUniformMatrix(shader.locs[SHADER_LOC_MATRIX_PROJECTION], matFboProjection);
// Define view matrix for every side of the cubemap
Matrix fboViews[6] = {
MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ 1.0f, 0.0f, 0.0f }, Vector3{ 0.0f, -1.0f, 0.0f }),
MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ -1.0f, 0.0f, 0.0f }, Vector3{ 0.0f, -1.0f, 0.0f }),
MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ 0.0f, 1.0f, 0.0f }, Vector3{ 0.0f, 0.0f, 1.0f }),
MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ 0.0f, -1.0f, 0.0f }, Vector3{ 0.0f, 0.0f, -1.0f }),
MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ 0.0f, 0.0f, 1.0f }, Vector3{ 0.0f, -1.0f, 0.0f }),
MatrixLookAt(Vector3{ 0.0f, 0.0f, 0.0f }, Vector3{ 0.0f, 0.0f, -1.0f }, Vector3{ 0.0f, -1.0f, 0.0f })
};
rlViewport(0, 0, size, size); // Set viewport to current fbo dimensions
// Activate and enable texture for drawing to cubemap faces
rlActiveTextureSlot(0);
rlEnableTexture(panorama.id);
for (int i = 0; i < 6; i++) {
// Set the view matrix for the current cube face
rlSetUniformMatrix(shader.locs[SHADER_LOC_MATRIX_VIEW], fboViews[i]);
// Select the current cubemap face attachment for the fbo
// WARNING: This function by default enables->attach->disables fbo!!!
rlFramebufferAttach(fbo, cubemap.id, RL_ATTACHMENT_COLOR_CHANNEL0, RL_ATTACHMENT_CUBEMAP_POSITIVE_X + i, 0);
rlEnableFramebuffer(fbo);
// Load and draw a cube, it uses the current enabled texture
rlClearScreenBuffers();
rlLoadDrawCube();
// ALTERNATIVE: Try to use internal batch system to draw the cube instead of rlLoadDrawCube
// for some reason this method does not work, maybe due to cube triangles definition? normals pointing out?
// TODO: Investigate this issue...
//rlSetTexture(panorama.id); // WARNING: It must be called after enabling current framebuffer if using internal batch system!
//rlClearScreenBuffers();
//DrawCubeV(Vector3Zero(), Vector3One(), WHITE);
//rlDrawRenderBatchActive();
}
//------------------------------------------------------------------------------------------
// STEP 3: Unload framebuffer and reset state
//------------------------------------------------------------------------------------------
rlDisableShader(); // Unbind shader
rlDisableTexture(); // Unbind texture
rlDisableFramebuffer(); // Unbind framebuffer
rlUnloadFramebuffer(fbo); // Unload framebuffer (and automatically attached depth texture/renderbuffer)
// Reset viewport dimensions to default
rlViewport(0, 0, rlGetFramebufferWidth(), rlGetFramebufferHeight());
rlEnableBackfaceCulling();
//------------------------------------------------------------------------------------------
cubemap.width = size;
cubemap.height = size;
cubemap.mipmaps = 1;
cubemap.format = format;
return cubemap;
}
raylib::Shader SkyBox::cubemapShader(0);
}

58
as5/skybox.hpp 100644
View File

@ -0,0 +1,58 @@
/*******************************************************************************************
*
* raylib [models] example - Skybox loading and drawing
*
* Example originally created with raylib 1.8, last time updated with raylib 4.0
*
* 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) 2017-2023 Ramon Santamaria (@raysan5)
*
********************************************************************************************/
#include "raylib-cpp.hpp"
namespace cs381 {
struct SkyBox {
constexpr static std::string_view vertexShader =
#include "generated/skybox.vs"
;
constexpr static std::string_view fragmentShader =
#include "generated/skybox.fs"
;
constexpr static std::string_view cubemapVertexShader =
#include "generated/cubemap.vs"
;
constexpr static std::string_view cubemapFragmentShader =
#include "generated/cubemap.fs"
;
static raylib::Shader cubemapShader;
raylib::Texture texture;
raylib::Shader shader;
raylib::Model cube;
SkyBox() : shader(0) {};
SkyBox(SkyBox&) = delete;
SkyBox(SkyBox&&) = default;
SkyBox(const std::string_view filename, bool isEnviornment = false) : SkyBox() {
Load(filename, isEnviornment);
}
~SkyBox() {
if(cube.IsValid())
UnloadTexture(cube.materials[0].maps[MATERIAL_MAP_CUBEMAP].texture);
}
SkyBox& Init();
SkyBox& Load(const std::string_view filename, bool isEnviornment = false);
SkyBox& Draw();
private:
// Generate cubemap texture from HDR texture
static TextureCubemap GenTextureCubemap(Shader shader, Texture2D panorama, int size, int format);
};
}