First working animations, but lots of bugs and refactoring needed.

This commit is contained in:
Zed A. Shaw 2026-10-07 12:11:15 -04:00
parent 8b41591360
commit a1c8208b47
158 changed files with 17271 additions and 0 deletions

View file

@ -0,0 +1,311 @@
#ifndef __khrplatform_h_
#define __khrplatform_h_
/*
** Copyright (c) 2008-2018 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* Khronos platform-specific types and definitions.
*
* The master copy of khrplatform.h is maintained in the Khronos EGL
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
* The last semantic modification to khrplatform.h was at commit ID:
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
*
* Adopters may modify this file to suit their platform. Adopters are
* encouraged to submit platform specific modifications to the Khronos
* group so that they can be included in future versions of this file.
* Please submit changes by filing pull requests or issues on
* the EGL Registry repository linked above.
*
*
* See the Implementer's Guidelines for information about where this file
* should be located on your system and for more details of its use:
* http://www.khronos.org/registry/implementers_guide.pdf
*
* This file should be included as
* #include <KHR/khrplatform.h>
* by Khronos client API header files that use its types and defines.
*
* The types in khrplatform.h should only be used to define API-specific types.
*
* Types defined in khrplatform.h:
* khronos_int8_t signed 8 bit
* khronos_uint8_t unsigned 8 bit
* khronos_int16_t signed 16 bit
* khronos_uint16_t unsigned 16 bit
* khronos_int32_t signed 32 bit
* khronos_uint32_t unsigned 32 bit
* khronos_int64_t signed 64 bit
* khronos_uint64_t unsigned 64 bit
* khronos_intptr_t signed same number of bits as a pointer
* khronos_uintptr_t unsigned same number of bits as a pointer
* khronos_ssize_t signed size
* khronos_usize_t unsigned size
* khronos_float_t signed 32 bit floating point
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
* nanoseconds
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
* khronos_boolean_enum_t enumerated boolean type. This should
* only be used as a base type when a client API's boolean type is
* an enum. Client APIs which use an integer or other type for
* booleans cannot use this as the base type for their boolean.
*
* Tokens defined in khrplatform.h:
*
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
*
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
*
* Calling convention macros defined in this file:
* KHRONOS_APICALL
* KHRONOS_APIENTRY
* KHRONOS_APIATTRIBUTES
*
* These may be used in function prototypes as:
*
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
* int arg1,
* int arg2) KHRONOS_APIATTRIBUTES;
*/
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
# define KHRONOS_STATIC 1
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APICALL
*-------------------------------------------------------------------------
* This precedes the return type of the function in the function prototype.
*/
#if defined(KHRONOS_STATIC)
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
* header compatible with static linking. */
# define KHRONOS_APICALL
#elif defined(_WIN32)
# define KHRONOS_APICALL __declspec(dllimport)
#elif defined (__SYMBIAN32__)
# define KHRONOS_APICALL IMPORT_C
#elif defined(__ANDROID__)
# define KHRONOS_APICALL __attribute__((visibility("default")))
#else
# define KHRONOS_APICALL
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIENTRY
*-------------------------------------------------------------------------
* This follows the return type of the function and precedes the function
* name in the function prototype.
*/
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
/* Win32 but not WinCE */
# define KHRONOS_APIENTRY __stdcall
#else
# define KHRONOS_APIENTRY
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIATTRIBUTES
*-------------------------------------------------------------------------
* This follows the closing parenthesis of the function prototype arguments.
*/
#if defined (__ARMCC_2__)
#define KHRONOS_APIATTRIBUTES __softfp
#else
#define KHRONOS_APIATTRIBUTES
#endif
/*-------------------------------------------------------------------------
* basic type definitions
*-----------------------------------------------------------------------*/
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
* Using <stdint.h>
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
/*
* To support platform where unsigned long cannot be used interchangeably with
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
* unsigned long long or similar (this results in different C++ name mangling).
* To avoid changes for existing platforms, we restrict usage of intptr_t to
* platforms where the size of a pointer is larger than the size of long.
*/
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
#define KHRONOS_USE_INTPTR_T
#endif
#endif
#elif defined(__VMS ) || defined(__sgi)
/*
* Using <inttypes.h>
*/
#include <inttypes.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
/*
* Win32
*/
typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
/*
* Sun or Digital
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t;
#else
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
/*
* Hypothetical platform with no float or int64 support
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0
#else
/*
* Generic fallback
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
/*
* Types that are (so far) the same on all platforms
*/
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
/*
* Types that differ between LLP64 and LP64 architectures - in LLP64,
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
* to be the only LLP64 architecture in current use.
*/
#ifdef KHRONOS_USE_INTPTR_T
typedef intptr_t khronos_intptr_t;
typedef uintptr_t khronos_uintptr_t;
#elif defined(_WIN64)
typedef signed long long int khronos_intptr_t;
typedef unsigned long long int khronos_uintptr_t;
#else
typedef signed long int khronos_intptr_t;
typedef unsigned long int khronos_uintptr_t;
#endif
#if defined(_WIN64)
typedef signed long long int khronos_ssize_t;
typedef unsigned long long int khronos_usize_t;
#else
typedef signed long int khronos_ssize_t;
typedef unsigned long int khronos_usize_t;
#endif
#if KHRONOS_SUPPORT_FLOAT
/*
* Float type
*/
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
/* Time types
*
* These types can be used to represent a time interval in nanoseconds or
* an absolute Unadjusted System Time. Unadjusted System Time is the number
* of nanoseconds since some arbitrary system event (e.g. since the last
* time the system booted). The Unadjusted System Time is an unsigned
* 64 bit value that wraps back to 0 every 584 years. Time intervals
* may be either signed or unsigned.
*/
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
/*
* Dummy value used to pad enum types to 32 bits.
*/
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
/*
* Enumerated boolean type
*
* Values other than zero should be considered to be true. Therefore
* comparisons should not be made against KHRONOS_TRUE.
*/
typedef enum {
KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
#endif /* __khrplatform_h_ */

View file

@ -0,0 +1 @@
#include "animation.hpp"

View file

@ -0,0 +1,115 @@
#pragma once
#include <vector>
#include <unordered_map>
#include <glm/glm.hpp>
#include <assimp/scene.h>
#include <functional>
#include "animdata.hpp"
#include "bone.hpp"
#include "model.hpp"
#include "assimp_glm_helpers.hpp"
struct Bone;
struct AssimpNodeData {
glm::mat4 transformation;
std::string name;
int children_count;
std::vector<AssimpNodeData> children;
};
struct Animation {
float $duration{0.0f};
int $ticks_per_sec{0};
std::vector<Bone> $bones;
AssimpNodeData $root_node;
std::unordered_map<std::string, BoneInfo> $bone_info_map;
Animation() = default;
Animation(const std::string& animationPath, Model *model) {
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(animationPath, aiProcess_Triangulate);
dbc::check(scene != nullptr, $F("Scene for animation {} failed to load.", animationPath));
dbc::check(scene->mRootNode, $F("Scene {} doesn't have a root node.", animationPath));
auto animation = scene->mAnimations[0];
$duration = animation->mDuration;
$ticks_per_sec = animation->mTicksPerSecond;
aiMatrix4x4 globalTransformation = scene->mRootNode->mTransformation;
globalTransformation = globalTransformation.Inverse();
ReadHierarchyData($root_node, scene->mRootNode);
ReadMissingBones(animation, *model);
}
~Animation() {
}
Bone* FindBone(const std::string& name) {
auto iter = std::find_if($bones.begin(), $bones.end(),
[&](const auto& bone) {
return bone.GetBoneName() == name;
}
);
return iter == $bones.end() ? nullptr : &(*iter);
}
float GetTicksPerSecond() {
return $ticks_per_sec;
}
float GetDuration() {
return $duration;
}
const AssimpNodeData& GetRootNode() {
return $root_node;
}
const std::unordered_map<std::string, BoneInfo>& GetBoneIDMap()
{
return $bone_info_map;
}
void ReadMissingBones(const aiAnimation* animation, Model& model) {
// this code is weird as hell
int size = animation->mNumChannels;
auto& boneInfoMap = model.bone_info_map;
int& boneCount = model.bone_counter;
for(int i = 0; i < size; i++) {
auto channel = animation->mChannels[i];
std::string boneName = channel->mNodeName.data;
if(!boneInfoMap.contains(boneName)) {
boneInfoMap[boneName].id = boneCount;
boneCount++;
}
dbc::check(boneCount < MAX_BONES, "Too many bones!");
$bones.emplace_back(boneName,
boneInfoMap[boneName].id, channel);
}
$bone_info_map = boneInfoMap;
}
void ReadHierarchyData(AssimpNodeData& dest, const aiNode* src) {
dbc::check(src != nullptr, "aiNode src is null?!");
dest.name = src->mName.data;
dest.transformation = AssimpGLMHelpers::ConvertMatrixToGLMFormat(src->mTransformation);
dest.children_count = src->mNumChildren;
for(size_t i = 0; i < src->mNumChildren; i++) {
AssimpNodeData newData;
ReadHierarchyData(newData, src->mChildren[i]);
dest.children.push_back(newData);
}
}
};

View file

@ -0,0 +1 @@
#include "animator.hpp"

View file

@ -0,0 +1,72 @@
#pragma once
#include <glm/glm.hpp>
#include <map>
#include <vector>
#include <assimp/scene.h>
#include <assimp/Importer.hpp>
#include "animation.hpp"
#include "bone.hpp"
struct Bone;
struct Animator {
std::vector<glm::mat4> $final_bone_matrices;
Animation* $current_animation = nullptr;
float $current_time = 0.0f;
Animator(Animation* animation) :
$current_animation(animation)
{
$final_bone_matrices.reserve(MAX_BONES);
for(size_t i = 0; i < MAX_BONES; i++) {
$final_bone_matrices.push_back(glm::mat4(1.0f));
}
}
void UpdateAnimation(float current_time) {
if($current_animation) {
$current_time = $current_animation->GetTicksPerSecond() * current_time;
$current_time = fmod($current_time, $current_animation->GetDuration());
CalculateBoneTransform(&$current_animation->GetRootNode(), glm::mat4(1.0f));
}
}
void PlayAnimation(Animation* animation) {
$current_animation = animation;
$current_time = 0.0f;
}
void CalculateBoneTransform(const AssimpNodeData* node, glm::mat4 parentTransform) {
std::string node_name = node->name;
glm::mat4 node_transform = node->transformation;
Bone *bone = $current_animation->FindBone(node_name);
if(bone) {
bone->Update($current_time);
node_transform = bone->GetLocalTransform();
}
glm::mat4 global_transformation = parentTransform * node_transform;
auto bone_map = $current_animation->GetBoneIDMap();
if(bone_map.contains(node_name)) {
auto& bone_info = bone_map[node_name];
$final_bone_matrices[bone_info.id] = global_transformation * bone_info.offset;
}
for(int i = 0; i < node->children_count; i++) {
CalculateBoneTransform(&node->children[i], global_transformation);
}
}
std::vector<glm::mat4>& GetFinalBoneMatrices() {
return $final_bone_matrices;
}
};

View file

@ -0,0 +1,13 @@
#pragma once
#include<glm/glm.hpp>
struct BoneInfo
{
/*id is index in finalBoneMatrices*/
int id;
/*offset matrix transforms vertex from model space to bone space*/
glm::mat4 offset;
};

View file

@ -0,0 +1,34 @@
#pragma once
#include<assimp/quaternion.h>
#include<assimp/vector3.h>
#include<assimp/matrix4x4.h>
#include<glm/glm.hpp>
#include<glm/gtc/quaternion.hpp>
class AssimpGLMHelpers
{
public:
static inline glm::mat4 ConvertMatrixToGLMFormat(const aiMatrix4x4& from)
{
glm::mat4 to;
//the a,b,c,d in assimp is the row ; the 1,2,3,4 is the column
to[0][0] = from.a1; to[1][0] = from.a2; to[2][0] = from.a3; to[3][0] = from.a4;
to[0][1] = from.b1; to[1][1] = from.b2; to[2][1] = from.b3; to[3][1] = from.b4;
to[0][2] = from.c1; to[1][2] = from.c2; to[2][2] = from.c3; to[3][2] = from.c4;
to[0][3] = from.d1; to[1][3] = from.d2; to[2][3] = from.d3; to[3][3] = from.d4;
return to;
}
static inline glm::vec3 GetGLMVec(const aiVector3D& vec)
{
return glm::vec3(vec.x, vec.y, vec.z);
}
static inline glm::quat GetGLMQuat(const aiQuaternion& pOrientation)
{
return glm::quat(pOrientation.w, pOrientation.x, pOrientation.y, pOrientation.z);
}
};

View file

@ -0,0 +1 @@
#include "bone.hpp"

184
45-animation/src/bone.hpp Normal file
View file

@ -0,0 +1,184 @@
#pragma once
#include <vector>
#include <list>
#include "model.hpp"
#include <glm/glm.hpp>
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>
#include "assimp_glm_helpers.hpp"
#include <string>
struct KeyPosition {
glm::vec3 position;
float timeStamp;
};
struct KeyRotation {
glm::quat orientation;
float timeStamp;
};
struct KeyScale {
glm::vec3 scale;
float timeStamp;
};
struct Bone
{
std::vector<KeyPosition> $positions;
std::vector<KeyRotation> $rotations;
std::vector<KeyScale> $scales;
size_t $num_positions = 0;
size_t $num_rotations = 0;
size_t $num_scalings = 0;
size_t $cur_position = 0;
size_t $cur_rotation = 0;
size_t $cur_scale = 0;
glm::mat4 $local_transform = 1.0f;
std::string $name{};
int $id = 0;
Bone(const std::string& name, int ID, const aiNodeAnim* channel) :
$name(name),
$id(ID)
{
$num_positions = channel->mNumPositionKeys;
for(size_t pos_i = 0; pos_i < $num_positions; pos_i++) {
const auto& ai_position = channel->mPositionKeys[pos_i];
auto position = AssimpGLMHelpers::GetGLMVec(ai_position.mValue);
$positions.emplace_back(position, ai_position.mTime);
}
$num_rotations = channel->mNumRotationKeys;
for(size_t rot_i = 0; rot_i < $num_rotations; rot_i++) {
const auto& ai_orientation = channel->mRotationKeys[rot_i];
auto orientation = AssimpGLMHelpers::GetGLMQuat(ai_orientation.mValue);
$rotations.emplace_back(orientation, ai_orientation.mTime);
}
$num_scalings = channel->mNumScalingKeys;
for(size_t key_i = 0; key_i < $num_scalings; key_i++) {
const auto& ai_scale = channel->mScalingKeys[key_i];
auto scale = AssimpGLMHelpers::GetGLMVec(ai_scale.mValue);
$scales.emplace_back(scale, ai_scale.mTime);
}
}
void Update(float animationTime) {
glm::mat4 translation = InterpolatPosition(animationTime);
glm::mat4 rotation = InterpolateRotation(animationTime);
glm::mat4 scale = InterpolateScaling(animationTime);
$local_transform = translation * rotation * scale;
}
glm::mat4 GetLocalTransform() {
return $local_transform;
}
std::string GetBoneName() const {
return $name;
}
int GetBoneId() {
return $id;
}
int GetPositionIndex(float animationTime) {
for(size_t index = 0; index < $num_positions - 1; ++index) {
auto ts = $positions[index + 1].timeStamp;
if(animationTime < ts) {
return index;
}
}
dbc::sentinel("Failed to find an animation position");
}
int GetRotationIndex(float animationTime) {
for(size_t index = 0; index < $num_rotations - 1; ++index) {
auto ts = $rotations[index + 1].timeStamp;
if(animationTime < ts) {
return index;
}
}
dbc::sentinel("Failed to find an animation rotation");
}
int GetScaleIndex(float animationTime) {
for(size_t index = 0; index < $num_scalings - 1; ++index) {
auto ts = $scales[index + 1].timeStamp;
if(animationTime < ts) {
return index;
}
}
dbc::sentinel("Failed to find an animation scale");
}
float GetScaleFactor(float lastTimeStamp, float nextTimeStamp, float animationTime) {
float scaleFactor = 0.0f;
float midWayLength = animationTime - lastTimeStamp;
float framesDiff = nextTimeStamp - lastTimeStamp;
scaleFactor = midWayLength / framesDiff;
return scaleFactor;
}
glm::mat4 InterpolatPosition(float animationTime) {
if($num_positions == 1) {
return glm::translate(glm::mat4(1.0f), $positions[0].position);
}
int p0Index = GetPositionIndex(animationTime);
auto& p0 = $positions[p0Index];
auto& p1 = $positions[p0Index + 1];
float scaleFactor = GetScaleFactor(
p0.timeStamp, p1.timeStamp, animationTime);
glm::vec3 finalPosition = glm::mix(p0.position,
p1.position, scaleFactor);
return glm::translate(glm::mat4(1.0f), finalPosition);
}
glm::mat4 InterpolateRotation(float animationTime) {
if($num_rotations == 1) {
auto rotation = glm::normalize($rotations[0].orientation);
return glm::toMat4(rotation);
}
int p0Index = GetRotationIndex(animationTime);
auto& p0 = $rotations[p0Index];
auto& p1 = $rotations[p0Index + 1];
float scaleFactor = GetScaleFactor(
p0.timeStamp, p1.timeStamp, animationTime);
glm::quat finalRotation = glm::slerp(
p0.orientation, p1.orientation, scaleFactor);
return glm::toMat4(finalRotation);
}
glm::mat4 InterpolateScaling(float animationTime) {
if($num_scalings == 1) {
return glm::scale(glm::mat4(1.0f), $scales[0].scale);
}
int p0Index = GetScaleIndex(animationTime);
auto& p0 = $scales[p0Index];
auto& p1 = $scales[p0Index + 1];
float scaleFactor = GetScaleFactor(
p0.timeStamp, p1.timeStamp, animationTime);
glm::vec3 finalScale = glm::mix(p0.scale, p1.scale, scaleFactor);
return glm::scale(glm::mat4(1.0f), finalScale);
}
};

View file

@ -0,0 +1,70 @@
#include "camera.hpp"
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
glm::mat4 Camera::look_at() {
return glm::lookAt(position, position + front, up);
}
void Camera::forward() {
dirty = true;
position += speed * front;
}
void Camera::back() {
dirty = true;
position -= speed * front;
}
void Camera::left() {
dirty = true;
position -= glm::normalize(glm::cross(front, up)) * speed;
}
void Camera::right() {
dirty = true;
position += glm::normalize(glm::cross(front, up)) * speed;
}
void Camera::update(float deltaTime) {
speed = movement_speed * deltaTime;
}
void Camera::mouse_move(double xpos, double ypos) {
dirty = true;
if(firstMouse) {
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos;
lastX = xpos;
lastY = ypos;
const float sensitivity = 0.1f;
xoffset *= sensitivity;
yoffset *= sensitivity;
yaw += xoffset;
pitch += yoffset;
if(pitch > 89.0f) pitch = 89.0f;
if(pitch < -89.0f) pitch = -89.0f;
direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
direction.y = sin(glm::radians(pitch));
direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
front = glm::normalize(direction);
}
void Camera::mouse_scroll(double xoffset, double yoffset) {
dirty = true;
fov -= (float)yoffset;
if(fov < 1.0f) fov = 1.0f;
if(fov > 90.0f) fov = 90.0f;
}

View file

@ -0,0 +1,28 @@
#pragma once
#include <glm/glm.hpp>
struct Camera {
glm::vec3 position{0.0f, 0.0f, 3.0f};
glm::vec3 front{0.0f, 0.0f, -1.0f};
glm::vec3 up{0.0f, 1.0f, 0.0f};
glm::vec3 direction{0.0f, 0.0f, 0.0f};
float movement_speed = 20.0f;
float pitch = 0.0f;
float yaw = -90.0f;
float speed = 0.05f;
float lastX = 400;
float lastY = 300;
float fov = 45.0f;
bool firstMouse = true;
bool dirty = true;
glm::mat4 look_at();
void forward();
void back();
void left();
void right();
void update(float deltaTime);
void mouse_move(double xpos, double ypos);
void mouse_scroll(double xoffset, double yoffset);
};

View file

@ -0,0 +1,113 @@
#pragma once
#include <glm/glm.hpp>
#include <vector>
#include <unordered_map>
#include "json.hpp"
#include "config.hpp"
namespace components {
template <typename T> struct NameOf;
struct Material {
glm::vec3 ambient{0.1f,0.1f,0.1f};
float shininess{32.0f};
};
ENROLL_COMPONENT(Material, ambient, shininess);
struct Light {
glm::vec3 position{0.0f, 0.0f, 0.0f};
glm::vec3 direction{0.0f, 0.0f, 0.0f};
glm::vec3 ambient{0.0f, 0.0f, 0.0f};
glm::vec3 diffuse{0.0f, 0.0f, 0.0f};
glm::vec3 specular{0.0f, 0.0f, 0.0f};
float constant=0.0f;
float linear=0.0f;
float quadratic=0.0f;
float cut_off=0.0f;
float outer_cut_off=0.0f;
bool on=true;
void adjust(float amount) {
ambient += amount;
diffuse += amount;
specular += amount;
}
};
ENROLL_COMPONENT(Light,
position, direction,
ambient, diffuse, specular,
constant, linear, quadratic,
cut_off, outer_cut_off, on);
struct Lighting {
Light directional;
std::vector<Light> positioned;
std::vector<Light> spot;
Light camera;
};
ENROLL_COMPONENT(Lighting, directional, positioned, spot, camera);
struct Camera {
glm::vec3 position{0.0f, 0.0f, 3.0f};
glm::vec3 front{0.0f, 0.0f, -1.0f};
glm::vec3 up{0.0f, 1.0f, 0.0f};
glm::vec3 direction{0.0f, 0.0f, 0.0f};
float movement_speed = 20.0f;
};
ENROLL_COMPONENT(Camera, position, front, up, direction, movement_speed);
struct Model {
std::string directory;
std::string model_path;
};
ENROLL_COMPONENT(Model, directory, model_path);
using Position = glm::vec3;
struct Rotation {
float angle = 0;
glm::vec3 axes{1.0f, 0.0f, 0.0f};
};
ENROLL_COMPONENT(Rotation, angle, axes);
struct Thing {
std::string model;
std::string material;
Position position;
Rotation rotation;
float scale;
bool animate;
};
ENROLL_COMPONENT(Thing, model, material, position, rotation, scale, animate);
struct Shader {
std::string vertex_path;
std::string frag_path;
std::string geometry_path;
};
ENROLL_COMPONENT(Shader, vertex_path, frag_path, geometry_path);
struct Scene {
components::Shader shader;
components::Shader reflect_shader;
components::Shader screen_shader;
components::Shader skybox_shader;
std::vector<std::string> skybox_faces;
std::unordered_map<std::string, Material> materials;
std::unordered_map<std::string, Model> models;
std::vector<Thing> things;
Camera camera;
Lighting light;
};
ENROLL_COMPONENT(Scene, shader, reflect_shader, screen_shader, skybox_shader, skybox_faces, materials, models, things, camera, light);
}

View file

@ -0,0 +1,9 @@
#pragma once
#define INSTANCE_COUNT 5
const unsigned int SCR_WIDTH = 1792;
const unsigned int SCR_HEIGHT = 1008;
#define MAX_BONES 100
#define MAX_LIGHTS 4

47
45-animation/src/dbc.cpp Normal file
View file

@ -0,0 +1,47 @@
#include "dbc.hpp"
#include <iostream>
void dbc::log(const string &message, const std::source_location location) {
std::cout << '[' << location.file_name() << ':'
<< location.line() << "|"
<< location.function_name() << "] "
<< message << std::endl;
}
void dbc::sentinel(const string &message, const std::source_location location) {
string err = $F("[SENTINEL!] {}", message);
dbc::log(err, location);
throw dbc::SentinelError(err);
}
void dbc::pre(const string &message, bool test, const std::source_location location) {
if(!test) {
string err = $F("[PRE!] {}", message);
dbc::log(err, location);
throw dbc::PreCondError(err);
}
}
void dbc::pre(const string &message, std::function<bool()> tester, const std::source_location location) {
dbc::pre(message, tester(), location);
}
void dbc::post(const string &message, bool test, const std::source_location location) {
if(!test) {
string err = $F("[POST!] {}", message);
dbc::log(err, location);
throw dbc::PostCondError(err);
}
}
void dbc::post(const string &message, std::function<bool()> tester, const std::source_location location) {
dbc::post(message, tester(), location);
}
void dbc::check(bool test, const string &message, const std::source_location location) {
if(!test) {
string err = $F("[CHECK!] {}\n", message);
dbc::log(err, location);
throw dbc::CheckError(err);
}
}

46
45-animation/src/dbc.hpp Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include <string>
#include <fmt/core.h>
#include <functional>
#include <source_location>
// AKA the Fuckit macro
#define $F(FMT, ...) fmt::format(FMT, ##__VA_ARGS__)
namespace dbc {
using std::string;
using CheckError = std::runtime_error;
using SentinelError = std::runtime_error;
using PreCondError = std::runtime_error;
using PostCondError = std::runtime_error;
void log(const string &message,
const std::source_location location =
std::source_location::current());
[[noreturn]] void sentinel(const string &message,
const std::source_location location =
std::source_location::current());
void pre(const string &message, bool test,
const std::source_location location =
std::source_location::current());
void pre(const string &message, std::function<bool()> tester,
const std::source_location location =
std::source_location::current());
void post(const string &message, bool test,
const std::source_location location =
std::source_location::current());
void post(const string &message, std::function<bool()> tester,
const std::source_location location =
std::source_location::current());
void check(bool test, const string &message,
const std::source_location location =
std::source_location::current());
}

View file

@ -0,0 +1,54 @@
#include "framebuffer.hpp"
#include <glad/glad.h>
#include <GLFW/glfw3.h>
void FrameBuffer::init() {
glGenFramebuffers(1, &target_fb);
glBindFramebuffer(GL_FRAMEBUFFER, target_fb);
glGenTextures(1, &texture_color_buffer);
glBindTexture(GL_TEXTURE_2D, texture_color_buffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_color_buffer, 0);
glGenRenderbuffers(1, &target_rbo);
glBindRenderbuffer(GL_RENDERBUFFER, target_rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, target_rbo);
dbc::check(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Failed to init framebuffer.");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
invariant();
}
void FrameBuffer::begin() {
glBindFramebuffer(GL_FRAMEBUFFER, target_fb);
glEnable(GL_DEPTH_TEST);
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void FrameBuffer::commit() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST);
glClearColor(0.0f, 0.0f, 1.0, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
}
void FrameBuffer::draw(Screen& screen) {
screen.shader.use();
glBindVertexArray(screen.target_VAO);
glActiveTexture(GL_TEXTURE0 + screen.texture_id);
glBindTexture(GL_TEXTURE_2D, texture_color_buffer);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
void FrameBuffer::invariant() {
dbc::check(target_fb != UINT_MAX, "target_fb not initialized");
dbc::check(texture_color_buffer != UINT_MAX, "texture_color_buffer not initialized");
}

View file

@ -0,0 +1,17 @@
#pragma once
#include "shader.hpp"
#include "screen.hpp"
struct FrameBuffer {
unsigned int target_fb = UINT_MAX;
unsigned int target_rbo = UINT_MAX;
unsigned int texture_color_buffer = UINT_MAX;
void init();
void begin();
void commit();
void draw(Screen& screen);
void invariant();
};

File diff suppressed because it is too large Load diff

2129
45-animation/src/glad/glad.h Normal file

File diff suppressed because it is too large Load diff

59
45-animation/src/json.hpp Normal file
View file

@ -0,0 +1,59 @@
#pragma once
#include <nlohmann/json.hpp>
#include <nlohmann/json_fwd.hpp>
#include <optional>
#include <glm/glm.hpp>
#include <vector>
#define ENROLL_COMPONENT(COMPONENT, ...) \
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(COMPONENT, __VA_ARGS__); \
template <> struct NameOf<COMPONENT> { \
static constexpr const char *name = #COMPONENT; \
};
// partial specialization (full specialization works too)
namespace nlohmann {
template <>
struct adl_serializer<glm::vec3> {
static void to_json(json& j, const glm::vec3& opt) {
}
static void from_json(const json& j, glm::vec3& opt) {
opt = glm::vec3(j[0], j[1], j[2]);
}
};
template <typename T>
struct adl_serializer<std::optional<T>> {
static void to_json(json& j, const std::optional<T>& opt) {
if (opt == std::nullopt) {
j = nullptr;
} else {
j = *opt; // this will call adl_serializer<T>::to_json which will
// find the free function to_json in T's namespace!
}
}
static void from_json(const json& j, std::optional<T>& opt) {
if (j.is_null() || j == false) {
opt = std::nullopt;
} else {
opt = std::make_optional<T>(j.template get<T>());
// same as above, but with adl_serializer<T>::from_json
}
}
};
template<>
struct adl_serializer<std::chrono::milliseconds> {
static void to_json(json& j, const std::chrono::milliseconds& opt) {
j = opt.count();
}
static void from_json(const json& j, std::chrono::milliseconds& opt) {
opt = std::chrono::milliseconds{int(j)};
}
};
}

161
45-animation/src/main.cpp Normal file
View file

@ -0,0 +1,161 @@
#define _USE_MATH_DEFINES
#include <stb_image.h>
#include "scene.hpp"
#include "utils.hpp"
#include <print>
#include <format>
#include "text.hpp"
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
std::string text_content{""};
void init_glfw() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
}
void framebuffer_size_callback(GLFWwindow *, int width, int height) {
glViewport(0, 0, width, height);
}
void mouse_callback(GLFWwindow *window, double xpos, double ypos) {
Scene* scene = static_cast<Scene*>(glfwGetWindowUserPointer(window));
scene->camera.mouse_move(xpos, ypos);
}
void scroll_callback(GLFWwindow *window, double xoffset, double yoffset) {
Scene* scene = static_cast<Scene*>(glfwGetWindowUserPointer(window));
scene->camera.mouse_scroll(xoffset, yoffset);
}
GLFWwindow* create_window() {
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
dbc::check(window != NULL, "failed to open window");
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
auto good = gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
dbc::check(good, "failed to load GLAD");
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
return window;
}
void process_input(GLFWwindow *window, std::shared_ptr<Scene> scene) {
if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true);
}
if(glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
scene->camera.forward();
}
if(glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
scene->camera.back();
}
if(glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
scene->camera.left();
}
if(glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
scene->camera.right();
}
if(glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS) {
scene->reflect_on = !scene->reflect_on;
}
if(glfwGetKey(window, GLFW_KEY_J) == GLFW_PRESS) {
scene->debug_on = !scene->debug_on;
}
if(glfwGetKey(window, GLFW_KEY_H) == GLFW_PRESS) {
scene->spawn("human");
glm::vec3 pos = scene->camera.position;
text_content = std::format("Spawn human at {},{},{}", pos.x, pos.y, pos.z);
}
if(glfwGetKey(window, GLFW_KEY_L) == GLFW_PRESS) {
scene->light.camera.on = !scene->light.camera.on;
scene->shader.apply_lighting(scene->light);
scene->shadows_rendered = false;
}
if(glfwGetKey(window, GLFW_KEY_O) == GLFW_PRESS) {
// BUG: this assumes the old shader with different light so broken
scene->light.directional.adjust(-0.01f);
scene->shader.apply_lighting(scene->light);
scene->shadows_rendered = false;
}
if(glfwGetKey(window, GLFW_KEY_P) == GLFW_PRESS) {
// BUG: this assumes the old shader with different light so broken
scene->light.directional.adjust(0.01f);
scene->shader.apply_lighting(scene->light);
scene->shadows_rendered = false;
}
}
std::shared_ptr<Scene> setup() {
glEnable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glEnable(GL_CULL_FACE);
glEnable(GL_MULTISAMPLE);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDepthFunc(GL_LESS);
components::Scene config = utils::load_scene_config("config.json");
return std::make_shared<Scene>(config);
}
void update(GLFWwindow* window, std::shared_ptr<Scene> scene) {
glfwPollEvents();
process_input(window, scene);
scene->update();
}
void render(GLFWwindow* window, std::shared_ptr<Scene> scene, Text &text) {
scene->render();
text.render(text_content, 25.0f, 25.0f, 1.0f, glm::vec3(1.0, 0.0f, 0.0f));
glfwSwapBuffers(window);
}
void quit(GLFWwindow* window, std::shared_ptr<Scene> scene) {
scene->cleanup();
glfwTerminate();
}
int main() {
init_glfw();
auto window = create_window();
std::shared_ptr<Scene> scene = setup();
Text text;
glfwSetWindowUserPointer(window, scene.get());
while(!glfwWindowShouldClose(window)) {
update(window, scene);
render(window, scene, text);
}
quit(window, scene);
return 0;
}

73
45-animation/src/mesh.cpp Normal file
View file

@ -0,0 +1,73 @@
#include "mesh.hpp"
#include "dbc.hpp"
#include "config.hpp"
void Mesh::draw(Shader &shader, bool with_textures) {
if(with_textures) {
apply_textures(shader);
}
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
void Mesh::apply_textures(const Shader& shader) {
int texture_i = 1;
bool has_height = false;
for(auto& texture : textures) {
texture.uniform_id = glGetUniformLocation(shader.ID, texture.target.c_str());
if(texture.uniform_id == -1) continue;
if(texture.target.starts_with("texture_height")) {
has_height = true;
}
glActiveTexture(GL_TEXTURE0 + texture_i);
glBindTexture(GL_TEXTURE_2D, texture.id);
glUniform1i(texture.uniform_id, texture_i);
texture_i++;
}
// quick hack to avoid applying height/normal to things without one
shader.setBool("hasHeight", has_height);
}
void Mesh::setup_mesh() {
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(Vertex), vertices.data(), GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW);
// this matches the locations in the vertex shader
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tex_coords));
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, tangent));
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, bitangent));
// bone ids
glEnableVertexAttribArray(5);
glVertexAttribIPointer(5, 4, GL_INT, sizeof(Vertex), (void*)offsetof(Vertex, bone_ids));
// bone weights
glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, weights));
}

80
45-animation/src/mesh.hpp Normal file
View file

@ -0,0 +1,80 @@
#pragma once
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "shader.hpp"
#include "animdata.hpp"
#include <string>
#include <vector>
#define MAX_BONE_INFLUENCE 4
struct Vertex {
glm::vec3 position;
glm::vec3 normal;
glm::vec2 tex_coords;
glm::vec3 tangent;
glm::vec3 bitangent;
int bone_ids[MAX_BONE_INFLUENCE]{0};
float weights[MAX_BONE_INFLUENCE]{0.0f};
size_t bone_count = 0;
};
struct TextureCounts {
unsigned int diffuseNr = 1;
unsigned int specularNr = 1;
unsigned int normalNr = 1;
unsigned int heightNr = 1;
};
struct Texture {
unsigned int id;
std::string type;
std::string path;
std::string target;
int uniform_id=-1;
Texture(unsigned int id, const std::string& type, const std::string& path, TextureCounts& count) :
id(id), type(type)
{
unsigned int number = 0;
if(type == "texture_diffuse") {
number = count.diffuseNr++;
} else if(type == "texture_specular") {
number = count.specularNr++;
} else if(type == "texture_normal") {
number = count.normalNr++;
} else if(type == "texture_height") {
number = count.heightNr++;
} else {
dbc::sentinel($F("Invalid texture type={} for file={}", type, path));
}
target = std::format("{}{}", type, number);
}
};
struct Mesh {
std::vector<Vertex> vertices;
std::vector<unsigned int> indices;
std::vector<Texture> textures;
unsigned int VAO = 0;
unsigned int VBO = 0;
unsigned int EBO = 0;
Mesh(std::vector<Vertex>& vertices, std::vector<unsigned int>& indices, std::vector<Texture>& textures) :
vertices(vertices),
indices(indices),
textures(textures)
{
setup_mesh();
}
void draw(Shader &shader, bool with_textures);
void setup_mesh();
void apply_textures(const Shader& shader);
};

276
45-animation/src/model.cpp Normal file
View file

@ -0,0 +1,276 @@
#include "model.hpp"
#include "dbc.hpp"
#include <print>
#include "assimp_glm_helpers.hpp"
unsigned int texture_from_file(const std::string& directory, const std::string& path);
unsigned int texture_from_internal(const aiScene *scene, size_t index);
unsigned int image_to_texture(unsigned char *data, int width, int height, int nrComponents) {
unsigned int textureID;
glGenTextures(1, &textureID);
GLenum format = GL_RGB;
switch(nrComponents) {
case 1:
format = GL_RED;
break;
case 3:
format = GL_RGB;
break;
case 4:
format = GL_RGBA;
break;
default:
dbc::sentinel($F("Impossible nrComponents={} when loading image", nrComponents));
}
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
return textureID;
}
unsigned int texture_from_internal(const aiScene *scene, size_t index) {
const aiTexture *aiTex = scene->mTextures[index];
unsigned int textureID = 0;
int width = 0;
int height = 0;
int nrComponents = 0;
unsigned char *data = nullptr;
const unsigned char* dataBytes = (const unsigned char*)aiTex->pcData;
if(aiTex->mHeight == 0) {
// compressed texture (PNG or JPG)
size_t dataSize = aiTex->mWidth;
data = stbi_load_from_memory(dataBytes, dataSize, &width, &height, &nrComponents, 0);
textureID = image_to_texture(data, width, height, nrComponents);
stbi_image_free(data);
} else {
data = (unsigned char*)aiTex->pcData;
width = aiTex->mWidth;
height = aiTex->mHeight;
nrComponents = 4; // either BGRA8888 or RGBA8888
textureID = image_to_texture(data, width, height, nrComponents);
}
return textureID;
}
unsigned int texture_from_file(const std::string& directory, const std::string& path)
{
std::string filename = directory + "/" + path;
int width = 0;
int height = 0;
int nrComponents = 0;
unsigned char *data = stbi_load(filename.c_str(), &width, &height, &nrComponents, 0);
dbc::check(data != nullptr, $F("Failed to load texture {}", filename));
unsigned int textureID = image_to_texture(data, width, height, nrComponents);
stbi_image_free(data);
return textureID;
}
void Model::draw(Shader &shader, bool with_textures) {
for(auto& mesh : meshes) {
mesh.draw(shader, with_textures);
}
}
void Model::load_model() {
std::string path = directory + "/" + model_path;
fmt::println("Loading model {}", path);
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(path,
aiProcess_Triangulate |
aiProcess_GenSmoothNormals |
aiProcess_FlipUVs |
aiProcess_CalcTangentSpace);
dbc::check(scene != nullptr, "Assimp ReadFile return null");
dbc::check(!(scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE), "Assimp says incomplete.");
dbc::check(scene->mRootNode != nullptr, "Assimp loaded scene doesn't have a root node");
process_node(scene, scene->mRootNode);
}
void Model::process_node(const aiScene *scene, aiNode *node) {
for(unsigned int i = 0; i < node->mNumMeshes; i++) {
aiMesh* mesh = scene->mMeshes[node->mMeshes[i]];
meshes.push_back(process_mesh(scene, mesh));
}
for(unsigned int i = 0; i < node->mNumChildren; i++) {
process_node(scene, node->mChildren[i]);
}
}
void Model::SetVertexBoneData(Vertex& vertex, int boneID, float weight)
{
// BUG: this will ignore bones after the max, but it seems to always be 4, so is there an off-by-one?
if(vertex.bone_count < MAX_BONE_INFLUENCE) {
vertex.bone_ids[vertex.bone_count] = boneID;
vertex.weights[vertex.bone_count] = weight;
vertex.bone_count++;
}
}
void Model::ExtractBoneWeightForVertices(std::vector<Vertex>& vertices, aiMesh *mesh, const aiScene* scene) {
for(size_t boneIndex = 0; boneIndex < mesh->mNumBones; ++boneIndex) {
int boneID = -1;
std::string bone_name = mesh->mBones[boneIndex]->mName.C_Str();
if(bone_info_map.contains(bone_name)) {
boneID = bone_info_map.at(bone_name).id;
} else {
fmt::println("FOUND BONE: {}", bone_name);
bone_info_map.try_emplace(bone_name,
bone_counter,
AssimpGLMHelpers::ConvertMatrixToGLMFormat(mesh->mBones[boneIndex]->mOffsetMatrix));
boneID = bone_counter;
bone_counter++;
}
dbc::check(boneID != -1, "There's no bones!");
auto weights = mesh->mBones[boneIndex]->mWeights;
int numWeights = mesh->mBones[boneIndex]->mNumWeights;
for(int weightIndex = 0; weightIndex < numWeights; ++weightIndex) {
int vertexId = weights[weightIndex].mVertexId;
float weight = weights[weightIndex].mWeight;
dbc::check((size_t)vertexId <= vertices.size(), "vertexID is too large");
SetVertexBoneData(vertices[vertexId], boneID, weight);
}
}
}
Mesh Model::process_mesh(const aiScene *scene, aiMesh *mesh) {
std::vector<Vertex> vertices;
std::vector<unsigned int> indices;
std::vector<Texture> textures;
for(unsigned int i = 0; i < mesh->mNumVertices; i++) {
glm::vec3 position{
mesh->mVertices[i].x,
mesh->mVertices[i].y,
mesh->mVertices[i].z};
glm::vec3 normal{};
glm::vec2 tex_coords{0.0f, 0.0f};
glm::vec3 tangent;
glm::vec3 bitangent;
if(mesh->HasNormals()) {
normal = glm::vec3(
mesh->mNormals[i].x,
mesh->mNormals[i].y,
mesh->mNormals[i].z);
}
if(mesh->mTextureCoords[0]) {
tex_coords = glm::vec2(
mesh->mTextureCoords[0][i].x,
mesh->mTextureCoords[0][i].y
);
}
// if has height/normal map
if(mesh->HasTangentsAndBitangents()) {
tangent = glm::vec3(
mesh->mTangents[i].x,
mesh->mTangents[i].y,
mesh->mTangents[i].z
);
bitangent = glm::vec3(
mesh->mBitangents[i].x,
mesh->mBitangents[i].y,
mesh->mBitangents[i].z
);
}
vertices.emplace_back(position, normal,
tex_coords, tangent, bitangent);
}
ExtractBoneWeightForVertices(vertices, mesh, scene);
for(unsigned int i = 0; i < mesh->mNumFaces; i++) {
aiFace& face = mesh->mFaces[i];
for(unsigned int j = 0; j < face.mNumIndices; j++) {
indices.emplace_back(face.mIndices[j]);
}
}
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
// we assume a convention for sampler names in the shaders. Each diffuse texture should be named
// as 'texture_diffuseN' where N is a sequential number ranging from 1 to MAX_SAMPLER_NUMBER.
// Same applies to other texture as the following list summarizes:
// diffuse: texture_diffuseN
// specular: texture_specularN
// normal: texture_normalN
// 1. diffuse maps
load_material_textures(scene, textures, material, aiTextureType_DIFFUSE, "texture_diffuse");
// 2. specular maps
load_material_textures(scene, textures, material, aiTextureType_SPECULAR, "texture_specular");
// 3. normal maps, possibly wrong?
load_material_textures(scene, textures, material, aiTextureType_NORMALS, "texture_normal");
// 4. height maps
load_material_textures(scene, textures, material, aiTextureType_HEIGHT, "texture_height");
return Mesh(vertices, indices, textures);
}
void Model::load_material_textures(const aiScene *scene, std::vector<Texture>& textures, aiMaterial *mat, aiTextureType type, std::string type_name)
{
for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) {
aiString str;
mat->GetTexture(type, i, &str);
std::string tx_path{str.C_Str()};
dbc::check(!tx_path.empty(), "Texture has empty path, should be impossible?");
if(textures_loaded.contains(tx_path)) {
textures.push_back(textures_loaded.at(tx_path));
} else {
unsigned int tx_id = 0;
// if it's a *# style path then it's internal
if(tx_path[0] == '*') {
// get the texture from the internal version
tx_id = texture_from_internal(scene, std::stoi(tx_path.substr(1)));
} else {
// else get it from a file
tx_id = texture_from_file(directory, tx_path);
}
// dubious code here, but seems to be right
auto& result = textures.emplace_back(tx_id, type_name, tx_path, texture_counts);
textures_loaded.try_emplace(tx_path, result);
}
}
}

View file

@ -0,0 +1,46 @@
#pragma once
#include <glad/glad.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <stb_image.h>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <mesh.hpp>
#include <shader.hpp>
#include <vector>
#include <string>
#include <fstream>
#include <sstream>
#include <iostream>
#include <unordered_map>
#include <vector>
struct Model {
std::string directory;
std::string model_path;
std::unordered_map<std::string, Texture> textures_loaded;
std::vector<Mesh> meshes;
TextureCounts texture_counts;
std::unordered_map<std::string, BoneInfo> bone_info_map;
int bone_counter = 0;
Model(const std::string& directory, const std::string& model_path) :
directory(directory), model_path(model_path)
{
load_model();
}
void draw(Shader &shader, bool with_textures);
void load_model();
void process_node(const aiScene *scene, aiNode *node);
Mesh process_mesh(const aiScene *scene, aiMesh *mesh);
void load_material_textures(const aiScene *scene, std::vector<Texture>& textures, aiMaterial *mat, aiTextureType type, std::string typeName);
void ExtractBoneWeightForVertices(std::vector<Vertex>& vertices, aiMesh *mesh, const aiScene* scene);
void SetVertexBoneData(Vertex& vertex, int boneID, float weight);
};

185
45-animation/src/scene.cpp Normal file
View file

@ -0,0 +1,185 @@
#include "scene.hpp"
#include "dbc.hpp"
#include <math.h>
#include "skybox.hpp"
#include <print>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
void Scene::update() {
float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// for now just detect the camera moved and do spotlight update
if(camera.dirty) {
light.camera.position = camera.position;
light.camera.direction = camera.front;
// just update the camera's light
shader.apply_spot_light(light.camera, 0);
camera.dirty = false;
}
camera.update(deltaTime);
animator.UpdateAnimation(currentFrame);
}
void Scene::draw_thing(Shader& with_shader, Thing& thing)
{
with_shader.apply_material(thing.material);
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, thing.position);
model = glm::rotate(model, glm::radians(thing.rotation.angle), thing.rotation.axes);
model = glm::scale(model, glm::vec3(thing.scale));
with_shader.setMat4("model", model);
if(thing.animation) {
auto& transforms = animator.GetFinalBoneMatrices();
for(size_t i = 0; i < transforms.size(); i++) {
with_shader.setMat4(std::format("finalBonesMatrices[{}]", (int)i), transforms[i]);
}
}
with_shader.setBool("hasBones", thing.animation != nullptr);
// !reflect_on turns it on? yeah weird
thing.model.draw(with_shader, !reflect_on);
}
void Scene::render_scene(Shader& with_shader) {
glm::mat4 view = camera.look_at();
// fov, aspect, near plane, far plane
projection = glm::perspective(glm::radians(camera.fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, near_plane, far_plane);
// render the scene
with_shader.use();
with_shader.setMat4("view", view);
with_shader.setMat4("projection", projection);
for(auto& thing : things) {
draw_thing(with_shader, thing);
}
}
void Scene::render_shadows() {
// BUG: right now there's only one directional light to simplify things
// but I might update this to support point lights so don't refactor this out
shadows.begin();
shadows.render_light(light.directional.position);
render_scene(shadows.depth_shader);
shadows.commit();
}
void Scene::render() {
auto& with_shader = reflect_on ? reflect_shader : shader;
if(!shadows_rendered) {
shadows_rendered = true;
render_shadows();
}
shadows.use();
with_shader.use();
with_shader.setInt("shadowMap", 0);
// NOTE: in the shader is it viewPos or cameraPos?
with_shader.setVec3("viewPos", camera.position);
with_shader.setVec3("lightPos", light.directional.position);
with_shader.setMat4("lightSpaceMatrix", shadows.light_space);
framebuffer.begin();
render_scene(with_shader);
render_skybox();
framebuffer.commit();
framebuffer.draw(screen);
if(debug_on) {
shadows.render_debug();
}
}
void Scene::cleanup() {
shader.cleanup();
reflect_shader.cleanup();
skybox_shader.cleanup();
}
void Scene::init_skybox() {
glBindVertexArray(skyboxVAO);
glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &skyboxVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
load_skybox_textures();
}
// BUG: Use this to spawn in the constructor. Duh.
void Scene::spawn(const std::string& name) {
if(models.contains(name)) {
components::Rotation rot{0.0, glm::vec3(1.0, 1.0, 1.0)};
things.emplace_back(
name,
models.at(name),
materials.at("default"),
camera.position,
rot,
1.0f);
// rebuild the shadows
shadows_rendered = false;
} else {
dbc::log($F("No model named: {}", name));
}
}
void Scene::load_skybox_textures() {
int width = 0;
int height = 0;
int nrChannels = 0;
size_t face_i = 0;
glBindTexture(GL_TEXTURE_CUBE_MAP, skybox_texture_id);
for(const auto& face : skybox_faces) {
unsigned char *data = stbi_load(face.c_str(), &width, &height, &nrChannels, 0);
dbc::check(data != nullptr, $F("Failed to load skybox face {}", face));
dbc::check(nrChannels == 3, $F("Skybox face {} must be RGB but you have {} channels", face, nrChannels));
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face_i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
face_i++;
stbi_image_free(data);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
}
void Scene::render_skybox() {
glDepthFunc(GL_LEQUAL);
skybox_shader.use();
glm::mat4 view = glm::mat4(glm::mat3(camera.look_at()));
skybox_shader.setMat4("view", view);
skybox_shader.setMat4("projection", projection);
glBindVertexArray(skyboxVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, skybox_texture_id);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glDepthFunc(GL_LESS);
}

135
45-animation/src/scene.hpp Normal file
View file

@ -0,0 +1,135 @@
#pragma once
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "shader.hpp"
#include "model.hpp"
#include "shader.hpp"
#include "camera.hpp"
#include "components.hpp"
#include "framebuffer.hpp"
#include <vector>
#include <unordered_map>
#include "config.hpp"
#include "shadows.hpp"
#include "animation.hpp"
#include "animator.hpp"
struct Animation;
struct Animator;
struct Thing {
std::string name;
Model& model;
Material& material;
components::Position position;
components::Rotation rotation;
float scale;
Animation* animation = nullptr;
float distance = 0;
};
struct Scene {
Shader shader;
Shader reflect_shader;
Shader skybox_shader;
std::vector<std::string> skybox_faces;
Screen screen;
FrameBuffer framebuffer;
std::unordered_map<std::string, components::Material> materials;
Camera camera;
components::Lighting light;
std::unordered_map<std::string, Model> models;
std::vector<Thing> things;
float deltaTime = 0.0f;
float lastFrame = 0.0f;
unsigned int skyboxVAO = UINT_MAX;
unsigned int skyboxVBO = UINT_MAX;
unsigned int skybox_texture_id = UINT_MAX;
glm::mat4 projection = glm::mat4(1.0f);
bool reflect_on = false;
Shadows shadows;
Animator animator{nullptr};
float near_plane = 1.0f;
float far_plane = 100.0f;
bool debug_on = false;
bool shadows_rendered = false;
Scene(components::Scene& config):
shader{config.shader},
reflect_shader{config.reflect_shader},
skybox_shader{config.skybox_shader},
skybox_faces{config.skybox_faces},
screen{.shader={config.screen_shader}},
materials{config.materials},
camera{
.position=config.camera.position,
.front=config.camera.front,
.up=config.camera.up,
.direction=config.camera.direction,
.movement_speed=config.camera.movement_speed,
},
light{config.light}
{
for(auto& [name, model] : config.models) {
models.try_emplace(name, model.directory, model.model_path);
}
for(auto& thing : config.things) {
Animation *anim = nullptr;
auto &model = models.at(thing.model);
if(thing.animate) {
// LEAK! Just temporary until I can clean up the original code
anim = new Animation(model.model_path, &model);
// WARNING! Only works with one right now
animator.PlayAnimation(anim);
}
things.emplace_back(
thing.model,
model,
materials.at(thing.material),
thing.position,
thing.rotation,
thing.scale,
anim);
}
glGenVertexArrays(1, &skyboxVAO);
glGenBuffers(1, &skyboxVBO);
glGenTextures(1, &skybox_texture_id);
shader.use();
shader.setInt("texture1", 0);
shader.apply_lighting(light);
skybox_shader.use();
skybox_shader.setInt("skybox", 0);
framebuffer.init();
screen.init();
shadows.init();
init_skybox();
}
void update();
void draw_thing(Shader& with_shader, Thing& thing);
void render();
void cleanup();
void init_skybox();
void init_shadows();
void init_debug();
void load_skybox_textures();
void render_skybox();
void spawn(const std::string& name);
void render_scene(Shader& with_shader);
void render_shadows();
};

View file

@ -0,0 +1,18 @@
#include "screen.hpp"
#include <glad/glad.h>
#include <GLFW/glfw3.h>
void Screen::init() {
shader.use();
shader.setInt("screenTexture", texture_id);
glGenVertexArrays(1, &target_VAO);
glGenBuffers(1, &target_VBO);
glBindVertexArray(target_VAO);
glBindBuffer(GL_ARRAY_BUFFER, target_VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
}

View file

@ -0,0 +1,23 @@
#pragma once
#include "shader.hpp"
struct Screen {
Shader shader;
unsigned int target_VAO = 0;
unsigned int target_VBO = 0;
unsigned int texture_id = 10;
float vertices[24] = {
// vertex attributes for a quad that fills the entire screen in Normalized Device Coordinates.
// positions // texCoords
-1.0f, 1.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f,
1.0f, 1.0f, 1.0f, 1.0f
};
void init();
};

224
45-animation/src/shader.cpp Normal file
View file

@ -0,0 +1,224 @@
#include "shader.hpp"
#include <iostream>
#include <fstream>
#include <sstream>
#include <print>
#include <filesystem>
#include "dbc.hpp"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
namespace fs = std::filesystem;
inline std::string read_file(const std::string& filename) {
// load the file
std::ifstream in_file{filename, std::ios::binary};
// get the size of the file
std::stringstream in_str;
in_str << in_file.rdbuf();
return in_str.str();
}
void check_error(const std::string& what, unsigned int thing, GLenum check_type) {
int success = 0;
char infoLog[512] = {0};
if(check_type == GL_LINK_STATUS) {
glGetProgramiv(thing, check_type, &success);
if(!success) {
glGetProgramInfoLog(thing, 512, NULL, infoLog);
dbc::sentinel(std::format("ERROR: Program {} compile failed: {}", what, infoLog));
}
} else {
glGetShaderiv(thing, check_type, &success);
if(!success) {
glGetShaderInfoLog(thing, 512, NULL, infoLog);
dbc::sentinel(std::format("ERROR: Shader {} compile failed: {}", what, infoLog));
}
}
}
unsigned int Shader::load_shader(const std::string& filename, GLenum shader_type) {
dbc::log($F("Loading shader {}", filename));
dbc::check(fs::exists(filename),
std::format("shader file {} does not exist", filename));
// create the shader
std::string shader_code = read_file(filename);
const char* shader_code_ptr = shader_code.c_str();
int shader_id = glCreateShader(shader_type);
glShaderSource(shader_id, 1, &shader_code_ptr, NULL);
glCompileShader(shader_id);
check_error(filename, shader_id, GL_COMPILE_STATUS);
// check compile error
return shader_id;
}
Shader::Shader(components::Shader c) : config(c) {
dbc::log($F("Loading shaders vert={}, frag={}, geom={}", config.vertex_path, config.frag_path, config.geometry_path));
unsigned int vertex = load_shader(config.vertex_path, GL_VERTEX_SHADER);
unsigned int fragment = load_shader(config.frag_path, GL_FRAGMENT_SHADER);
unsigned int geometry = UINT_MAX;
ID = glCreateProgram();
dbc::log($F("Created shader program ID: {}", ID));
glAttachShader(ID, vertex);
glAttachShader(ID, fragment);
if(config.geometry_path != "") {
geometry = load_shader(config.geometry_path, GL_GEOMETRY_SHADER);
glAttachShader(ID, geometry);
}
glLinkProgram(ID);
check_error("link", ID, GL_LINK_STATUS);
glDeleteShader(vertex);
glDeleteShader(fragment);
if(config.geometry_path != "") {
glDeleteShader(geometry);
}
}
void Shader::use() const {
glUseProgram(ID);
}
void Shader::cleanup() {
glDeleteProgram(ID);
}
void Shader::setBool(const std::string &name, bool value) const {
auto uniform = glGetUniformLocation(ID, name.c_str());
glUniform1i(uniform, (int)value);
}
void Shader::setInt(const std::string &name, int value) const {
auto uniform = glGetUniformLocation(ID, name.c_str());
glUniform1i(uniform, value);
}
void Shader::setFloat(const std::string &name, float value) const {
auto uniform = glGetUniformLocation(ID, name.c_str());
glUniform1f(uniform, value);
}
void Shader::setVec4(const std::string &name, float v1, float v2, float v3, float v4) const {
auto uniform = glGetUniformLocation(ID, name.c_str());
glUniform4f(uniform, v1, v2, v3, v4);
}
void Shader::setVec4(const std::string &name, const glm::vec4& value) const
{
auto uniform = glGetUniformLocation(ID, name.c_str());
glUniform4fv(uniform, 1, &value[0]);
}
void Shader::setVec2(const std::string &name, const glm::vec2& value) const
{
auto uniform = glGetUniformLocation(ID, name.c_str());
glUniform2fv(uniform, 1, &value[0]);
}
void Shader::setVec3(const std::string &name, const glm::vec3& value) const
{
auto uniform = glGetUniformLocation(ID, name.c_str());
glUniform3fv(uniform, 1, &value[0]);
}
void Shader::setVec3(const std::string &name, float v1, float v2, float v3) const {
auto uniform = glGetUniformLocation(ID, name.c_str());
glUniform3f(uniform, v1, v2, v3);
}
void Shader::setMat4(const std::string &name, const glm::mat4& mat) const {
unsigned int loc = glGetUniformLocation(ID, name.c_str());
glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value_ptr(mat));
}
void Shader::apply_material(const Material& material) {
setVec3("material.ambient", material.ambient);
setFloat("material.shininess", material.shininess);
}
void Shader::apply_lighting(const Lighting& lighting) {
setInt("pointLightCount", lighting.positioned.size());
setInt("dirLightCount", 1); // always 1 directional light
apply_dir_light(lighting.directional, 0);
for(size_t i = 0; i < lighting.positioned.size(); i++) {
apply_point_light(lighting.positioned[i], i);
}
// set the first spotlight to the camera light if given
size_t i = 0;
// camera is optional, so only add if used
if(lighting.camera.on) {
apply_spot_light(lighting.camera, 0);
i++;
}
// gross but that's fine for now
for(; i < lighting.spot.size(); i++) {
// need to be off by one because the camera is a spot light
if(lighting.spot[i].on) {
apply_spot_light(lighting.spot[i], i+1);
}
}
// need +1 for the camera spot light
setInt("spotLightCount", (int)i);
}
void Shader::apply_dir_light(const Light& light, size_t index) {
dbc::check(index < MAX_LIGHTS, "too many directional lights");
// nasty, this needs to go
setVec3(std::format("dirLights[{}].direction", index), light.direction);
setVec3(std::format("dirLights[{}].ambient", index), light.ambient);
setVec3(std::format("dirLights[{}].diffuse", index), light.diffuse);
setVec3(std::format("dirLights[{}].specular", index), light.specular);
}
void Shader::apply_point_light(const Light& light, size_t index) {
dbc::check(index < MAX_LIGHTS, "too many positioned lights");
// disgusting, crafting a string on every render?
setVec3(std::format("pointLights[{}].position", index), light.position);
setVec3(std::format("pointLights[{}].ambient", index), light.ambient);
setVec3(std::format("pointLights[{}].diffuse", index), light.diffuse);
setVec3(std::format("pointLights[{}].specular", index), light.specular);
setFloat(std::format("pointLights[{}].constant", index), light.constant);
setFloat(std::format("pointLights[{}].linear", index), light.linear);
setFloat(std::format("pointLights[{}].quadratic", index), light.quadratic);
}
void Shader::apply_spot_light(const Light& light, size_t index) {
dbc::check(index < MAX_LIGHTS, "too many spot lights");
// find a way to not make strings all the time
setVec3(std::format("spotLights[{}].position", index), light.position);
setVec3(std::format("spotLights[{}].direction", index), light.direction);
setVec3(std::format("spotLights[{}].ambient", index), light.ambient);
setVec3(std::format("spotLights[{}].diffuse", index), light.diffuse);
setVec3(std::format("spotLights[{}].specular", index), light.specular);
setFloat(std::format("spotLights[{}].constant", index), light.constant);
setFloat(std::format("spotLights[{}].linear", index), light.linear);
setFloat(std::format("spotLights[{}].quadratic", index), light.quadratic);
setFloat(std::format("spotLights[{}].cut_off", index), glm::cos(glm::radians(light.cut_off)));
setFloat(std::format("spotLights[{}].outer_cut_off", index), glm::cos(glm::radians(light.outer_cut_off)));
}

View file

@ -0,0 +1,40 @@
#pragma once
#include <glad/glad.h>
#include <string>
#include <climits>
#include <glm/glm.hpp>
#include <vector>
#include <format>
#include "dbc.hpp"
#include "components.hpp"
using components::Material, components::Lighting, components::Light;
class Shader
{
public:
unsigned int ID = UINT_MAX;
components::Shader config;
Shader(components::Shader config);
unsigned int load_shader(const std::string& filename, GLenum shader_type);
void use() const;
void setBool(const std::string &name, bool value) const;
void setInt(const std::string &name, int value) const;
void setFloat(const std::string &name, float value) const;
void setVec4(const std::string &name, float v1, float v2, float v3, float v4) const;
void setVec4(const std::string &name, const glm::vec4& value) const;
void setVec3(const std::string &name, float v1, float v2, float v3) const;
void setVec3(const std::string &name, const glm::vec3& value) const;
void setVec2(const std::string &name, const glm::vec2& value) const;
void setMat4(const std::string &name, const glm::mat4& what) const;
void cleanup();
void apply_material(const Material& material);
void apply_lighting(const Lighting& lighting);
void apply_dir_light(const Light& light, size_t index);
void apply_point_light(const Light& light, size_t index);
void apply_spot_light(const Light& light, size_t index);
};

View file

@ -0,0 +1,96 @@
#include "shadows.hpp"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
void Shadows::init() {
glGenFramebuffers(1, &target_fb);
glBindFramebuffer(GL_FRAMEBUFFER, target_fb);
glGenTextures(1, &depth_map);
glBindTexture(GL_TEXTURE_2D, depth_map);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// set to clamp border with a color of one to remove the frustrum culled part of the shadow
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float border_color[4]{1.0f, 1.0f, 1.0f, 1.0f};
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border_color);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_map, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
dbc::check(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Frame buffer not complete.");
glBindFramebuffer(GL_FRAMEBUFFER, 0);
init_debug();
}
void Shadows::begin() {
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, target_fb);
glEnable(GL_DEPTH_TEST);
glClear(GL_DEPTH_BUFFER_BIT);
glCullFace(GL_FRONT);
}
void Shadows::commit() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDisable(GL_DEPTH_TEST);
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glCullFace(GL_BACK);
}
void Shadows::use() {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depth_map);
}
void Shadows::render_debug() {
// set depth_map on TEXTURE0
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, depth_map);
depth_dbg_shader.use();
depth_dbg_shader.setInt("shadowMap", 0);
depth_dbg_shader.setFloat("near_plane", depth_near_plane);
depth_dbg_shader.setFloat("far_plane", depth_far_plane);
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glBindVertexArray(0);
}
void Shadows::init_debug() {
// setup plane VAO
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
}
void Shadows::render_light(glm::vec3& light_pos) {
glm::mat4 proj = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, depth_near_plane, depth_far_plane);
glm::mat4 view = glm::lookAt(light_pos, glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
light_space = proj * view;
depth_shader.use();
depth_shader.setMat4("lightSpaceMatrix", light_space);
depth_shader.setFloat("near_plane", depth_near_plane);
depth_shader.setFloat("far_plane", depth_far_plane);
}

View file

@ -0,0 +1,37 @@
#pragma once
#include "shader.hpp"
#include "screen.hpp"
#include "components.hpp"
#include <GLFW/glfw3.h>
struct Shadows {
unsigned int target_fb = UINT_MAX;
unsigned int depth_map = UINT_MAX;
const unsigned int SHADOW_WIDTH = 1024 * 8, SHADOW_HEIGHT = 1024 * 8;
float depth_near_plane = 1.0f;
float depth_far_plane = 7.5f;
float quadVertices[20] = {
// positions // texture Coords
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f,
1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
1.0f, -1.0f, 0.0f, 1.0f, 0.0f,
};
unsigned int quadVAO = UINT_MAX;
unsigned int quadVBO = UINT_MAX;
glm::mat4 light_space;
// shadow debug stuff
Shader depth_shader{{"shaders/33-depth.vert.glsl", "shaders/33-depth.frag.glsl", ""}};
Shader depth_dbg_shader{{"shaders/33-shadow_dbg.vert.glsl", "shaders/33-shadow_dbg.frag.glsl", ""}};
void init();
void init_debug();
void begin();
void commit();
void use();
void render_debug();
void render_light(glm::vec3& light_pos);
};

View file

@ -0,0 +1,46 @@
#pragma once
float skyboxVertices[] = {
// positions
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, -1.0f, 1.0f,
-1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
1.0f, 1.0f, -1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, -1.0f,
1.0f, -1.0f, -1.0f,
-1.0f, -1.0f, 1.0f,
1.0f, -1.0f, 1.0f
};

View file

@ -0,0 +1,2 @@
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

7988
45-animation/src/stb_image.h Normal file

File diff suppressed because it is too large Load diff

114
45-animation/src/text.cpp Normal file
View file

@ -0,0 +1,114 @@
#include "text.hpp"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include "config.hpp"
#include <ft2build.h>
#include FT_FREETYPE_H
void Text::render(const std::string& text, float x, float y, float scale, glm::vec3 color) {
text_shader.use();
glm::mat4 projection = glm::ortho(0.0f, static_cast<float>(SCR_WIDTH), 0.0f, static_cast<float>(SCR_HEIGHT));
text_shader.setMat4("projection", projection);
glUniform3f(glGetUniformLocation(text_shader.ID, "textColor"), color.x, color.y, color.z);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(text_vao);
for(auto c : text) {
auto& ch = characters.at(c);
float xpos = x + ch.bearing.x * scale;
float ypos = y - (ch.size.y - ch.bearing.y) * scale;
float w = ch.size.x * scale;
float h = ch.size.y * scale;
float vertices[6][4] = {
{ xpos, ypos + h, 0.0f, 0.0f },
{ xpos, ypos, 0.0f, 1.0f },
{ xpos + w, ypos, 1.0f, 1.0f },
{ xpos, ypos + h, 0.0f, 0.0f },
{ xpos + w, ypos, 1.0f, 1.0f },
{ xpos + w, ypos + h, 1.0f, 0.0f }
};
// rander glyph texture over quad
glBindTexture(GL_TEXTURE_2D, ch.texture_id);
// update content of VBO memory
glBindBuffer(GL_ARRAY_BUFFER, text_vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// render quad
glDrawArrays(GL_TRIANGLES, 0, 6);
x += (ch.advance >> 6) * scale;
}
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
void Text::load_font() {
FT_Library ft;
int fail = FT_Init_FreeType(&ft);
dbc::check(fail == 0, "FreeType failed.");
FT_Face face;
fail = FT_New_Face(ft, font.c_str(), 0, &face);
dbc::check(fail == 0, "Failed to load font.");
// load the font bitmaps
FT_Set_Pixel_Sizes(face, 0, 48);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
for(unsigned char c = 0; c < 255; c++) {
fail = FT_Load_Char(face, c, FT_LOAD_RENDER);
dbc::check(fail == 0, $F("Failed to load char {}", int(c)));
unsigned int texture = UINT_MAX;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RED,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
0,
GL_RED,
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
characters.try_emplace(c,
texture,
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
static_cast<unsigned int>(face->glyph->advance.x));
}
// unbind
glBindTexture(GL_TEXTURE_2D, 0);
// cleanup
FT_Done_Face(face);
FT_Done_FreeType(ft);
// create teh vao/vbo
glGenVertexArrays(1, &text_vao);
glGenBuffers(1, &text_vbo);
glBindVertexArray(text_vao);
glBindBuffer(GL_ARRAY_BUFFER, text_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
glBindVertexArray(0);
}

27
45-animation/src/text.hpp Normal file
View file

@ -0,0 +1,27 @@
#pragma once
#include "shader.hpp"
#include <string>
#include <unordered_map>
#include <glm/glm.hpp>
struct Character {
unsigned int texture_id;
glm::ivec2 size;
glm::ivec2 bearing;
unsigned int advance;
};
struct Text {
std::string font{"assets/fonts/text.ttf"};
Shader text_shader{{"shaders/32-text.vert.glsl", "shaders/32-text.frag.glsl", ""}};
unsigned int text_vao = UINT_MAX;
unsigned int text_vbo = UINT_MAX;
std::unordered_map<unsigned char, Character> characters;
Text() {
load_font();
}
void render(const std::string& text, float x, float y, float scale, glm::vec3 color);
void load_font();
};

View file

@ -0,0 +1,11 @@
#include <fstream>
#include "json.hpp"
#include "utils.hpp"
namespace utils {
components::Scene load_scene_config(const std::string& config_file) {
std::ifstream f{"config.json"};
auto j = json::parse(f);
return j["scene"].template get<components::Scene>();
}
}

View file

@ -0,0 +1,9 @@
#pragma once
#include "components.hpp"
using json = nlohmann::json;
namespace utils {
components::Scene load_scene_config(const std::string& config_file);
}