diff options
39 files changed, 1411 insertions, 302 deletions
diff --git a/assets/shaders/basic.glsl b/assets/shaders/basic.glsl deleted file mode 100644 index f6016f7..0000000 --- a/assets/shaders/basic.glsl +++ /dev/null @@ -1,31 +0,0 @@ -#type vertex -#version 330 core - -layout(location = 0) in vec3 a_Position; -layout(location = 1) in vec4 a_Color; - -uniform mat4 u_ViewProjection; -uniform mat4 u_Transform; - -out vec3 v_Position; -out vec4 v_Color; - -void main() -{ - v_Position = a_Position; - v_Color = a_Color; - gl_Position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0); -} - -#type fragment -#version 330 core - -layout(location = 0) out vec4 color; - -in vec3 v_Position; -in vec4 v_Color; - -void main() -{ - color = v_Color; -} diff --git a/assets/shaders/compute.glsl b/assets/shaders/compute.glsl deleted file mode 100644 index 9a36a84..0000000 --- a/assets/shaders/compute.glsl +++ /dev/null @@ -1,24 +0,0 @@ -#type compute -#version 430 core - -layout(local_size_x = 256) in; - -layout(std430, binding = 0) buffer InputBuffer -{ - float data[]; -} inputBuffer; - -layout(std430, binding = 1) buffer OutputBuffer -{ - float data[]; -} outputBuffer; - -uniform float u_Scale; -uniform float u_Offset; - -void main() -{ - uint index = gl_GlobalInvocationID.x; - float inputValue = inputBuffer.data[index]; - outputBuffer.data[index] = inputValue * u_Scale + u_Offset; -} diff --git a/assets/shaders/geodesic.glsl b/assets/shaders/geodesic.glsl new file mode 100644 index 0000000..6b077be --- /dev/null +++ b/assets/shaders/geodesic.glsl @@ -0,0 +1,215 @@ +#version 430 +layout(local_size_x = 16, local_size_y = 16) in; + +layout(binding = 0, rgba8) writeonly uniform image2D outImage; + +layout(std140, binding = 1) uniform Camera +{ + vec3 camPos; float _pad0; + vec3 camRight; float _pad1; + vec3 camUp; float _pad2; + vec3 camForward; float _pad3; + float tanHalfFov; + float aspect; + bool moving; + int _pad4; +} cam; + +layout(std140, binding = 2) uniform Disk +{ + float disk_r1; + float disk_r2; + float disk_num; + float thickness; +}; + +layout(std140, binding = 3) uniform Objects +{ + int numObjects; + vec4 objPosRadius[16]; + vec4 objColor[16]; + float mass[16]; +}; + +const float SagA_rs = 1.269e10; +const float D_LAMBDA = 1e7; +const double ESCAPE_R = 1e30; + +vec4 objectColor = vec4(0.0); +vec3 hitCenter = vec3(0.0); +float hitRadius = 0.0; + +struct Ray +{ + float x, y, z, radius, theta, phi; + float dRadius, dTheta, dPhi; + float energy, angularMomentum; +}; + +Ray InitRay(vec3 position, vec3 direction) +{ + Ray ray; + ray.x = position.x; + ray.y = position.y; + ray.z = position.z; + ray.radius = length(position); + ray.theta = acos(position.z / ray.radius); + ray.phi = atan(position.y, position.x); + + float dx = direction.x, dy = direction.y, dz = direction.z; + ray.dRadius = sin(ray.theta) * cos(ray.phi) * dx + sin(ray.theta) * sin(ray.phi) * dy + cos(ray.theta) * dz; + ray.dTheta = (cos(ray.theta) * cos(ray.phi) * dx + cos(ray.theta) * sin(ray.phi) * dy - sin(ray.theta) * dz) / ray.radius; + ray.dPhi = (-sin(ray.phi) * dx + cos(ray.phi) * dy) / (ray.radius * sin(ray.theta)); + + ray.angularMomentum = ray.radius * ray.radius * sin(ray.theta) * ray.dPhi; + float f = 1.0 - SagA_rs / ray.radius; + float dt_dL = sqrt((ray.dRadius * ray.dRadius) / f + ray.radius * ray.radius * (ray.dTheta * ray.dTheta + sin(ray.theta) * sin(ray.theta) * ray.dPhi * ray.dPhi)); + ray.energy = f * dt_dL; + + return ray; +} + +bool Intercept(Ray ray, float schwarzschildRadius) +{ + return ray.radius <= schwarzschildRadius; +} + +bool InterceptObject(Ray ray) +{ + vec3 position = vec3(ray.x, ray.y, ray.z); + + for (int i = 0; i < numObjects; ++i) + { + vec3 center = objPosRadius[i].xyz; + float radius = objPosRadius[i].w; + + if (distance(position, center) <= radius) + { + objectColor = objColor[i]; + hitCenter = center; + hitRadius = radius; + return true; + } + } + + return false; +} + +void GeodesicRHS(Ray ray, out vec3 derivatives1, out vec3 derivatives2) +{ + float radius = ray.radius, theta = ray.theta; + float dRadius = ray.dRadius, dTheta = ray.dTheta, dPhi = ray.dPhi; + float f = 1.0 - SagA_rs / radius; + float dt_dL = ray.energy / f; + + derivatives1 = vec3(dRadius, dTheta, dPhi); + derivatives2.x = -(SagA_rs / (2.0 * radius * radius)) * f * dt_dL * dt_dL + + (SagA_rs / (2.0 * radius * radius * f)) * dRadius * dRadius + + radius * (dTheta * dTheta + sin(theta) * sin(theta) * dPhi * dPhi); + derivatives2.y = -2.0 * dRadius * dTheta / radius + sin(theta) * cos(theta) * dPhi * dPhi; + derivatives2.z = -2.0 * dRadius * dPhi / radius - 2.0 * cos(theta) / sin(theta) * dTheta * dPhi; +} + +void RK4Step(inout Ray ray, float deltaLambda) +{ + vec3 k1a, k1b; + GeodesicRHS(ray, k1a, k1b); + + ray.radius += deltaLambda * k1a.x; + ray.theta += deltaLambda * k1a.y; + ray.phi += deltaLambda * k1a.z; + ray.dRadius += deltaLambda * k1b.x; + ray.dTheta += deltaLambda * k1b.y; + ray.dPhi += deltaLambda * k1b.z; + + ray.x = ray.radius * sin(ray.theta) * cos(ray.phi); + ray.y = ray.radius * sin(ray.theta) * sin(ray.phi); + ray.z = ray.radius * cos(ray.theta); +} + +bool CrossesEquatorialPlane(vec3 oldPosition, vec3 newPosition) +{ + bool crossed = (oldPosition.y * newPosition.y < 0.0); + float radius = length(vec2(newPosition.x, newPosition.z)); + return crossed && (radius >= disk_r1 && radius <= disk_r2); +} + +void main() +{ + ivec2 pixel = ivec2(gl_GlobalInvocationID.xy); + int width = imageSize(outImage).x; + int height = imageSize(outImage).y; + + if (pixel.x >= width || pixel.y >= height) + return; + + float u = (2.0 * (pixel.x + 0.5) / width - 1.0) * cam.aspect * cam.tanHalfFov; + float v = (1.0 - 2.0 * (pixel.y + 0.5) / height) * cam.tanHalfFov; + vec3 direction = normalize(u * cam.camRight - v * cam.camUp + cam.camForward); + Ray ray = InitRay(cam.camPos, direction); + + vec4 color = vec4(0.0); + vec3 prevPosition = vec3(ray.x, ray.y, ray.z); + float lambda = 0.0; + + bool hitBlackHole = false; + bool hitDisk = false; + bool hitObject = false; + + int steps = cam.moving ? 60000 : 60000; + + for (int i = 0; i < steps; ++i) + { + if (Intercept(ray, SagA_rs)) + { + hitBlackHole = true; + break; + } + + RK4Step(ray, D_LAMBDA); + lambda += D_LAMBDA; + + vec3 newPosition = vec3(ray.x, ray.y, ray.z); + + if (CrossesEquatorialPlane(prevPosition, newPosition)) + { + hitDisk = true; + break; + } + + if (InterceptObject(ray)) + { + hitObject = true; + break; + } + + prevPosition = newPosition; + + if (ray.radius > ESCAPE_R) + break; + } + + if (hitDisk) + { + double radius = length(vec3(ray.x, ray.y, ray.z)) / disk_r2; + vec3 diskColor = vec3(1.0, radius, 0.2); + color = vec4(diskColor, radius); + } + else if (hitBlackHole) + color = vec4(0.0, 0.0, 0.0, 1.0); + else if (hitObject) + { + vec3 position = vec3(ray.x, ray.y, ray.z); + vec3 normal = normalize(position - hitCenter); + vec3 viewDirection = normalize(cam.camPos - position); + float ambient = 0.1; + float diffuse = max(dot(normal, viewDirection), 0.0); + float intensity = ambient + (1.0 - ambient) * diffuse; + vec3 shaded = objectColor.rgb * intensity; + color = vec4(shaded, objectColor.a); + } + else + color = vec4(0.0); + + imageStore(outImage, pixel, color); +} diff --git a/assets/shaders/grid.glsl b/assets/shaders/grid.glsl new file mode 100644 index 0000000..66096c4 --- /dev/null +++ b/assets/shaders/grid.glsl @@ -0,0 +1,19 @@ +#type vertex +#version 330 core +layout (location = 0) in vec3 aPos; + +uniform mat4 viewProj; + +void main() +{ + gl_Position = viewProj * vec4(aPos, 1.0); +} + +#type fragment +#version 330 core +out vec4 FragColor; + +void main() +{ + FragColor = vec4(0.5, 0.5, 0.5, 0.3); +} diff --git a/assets/shaders/image_processing.glsl b/assets/shaders/image_processing.glsl deleted file mode 100644 index 3a05425..0000000 --- a/assets/shaders/image_processing.glsl +++ /dev/null @@ -1,31 +0,0 @@ -#type compute -#version 430 core - -layout(local_size_x = 16, local_size_y = 16) in; - -layout(binding = 0) uniform sampler2D u_InputTexture; -layout(binding = 1, rgba8) uniform image2D u_OutputTexture; - -uniform vec2 u_TextureSize; -uniform float u_Brightness; -uniform float u_Contrast; -uniform float u_Saturation; - -void main() -{ - ivec2 pixelCoord = ivec2(gl_GlobalInvocationID.xy); - - if (pixelCoord.x >= int(u_TextureSize.x) || pixelCoord.y >= int(u_TextureSize.y)) - return; - - vec2 texCoord = (vec2(pixelCoord) + 0.5) / u_TextureSize; - vec4 color = texture(u_InputTexture, texCoord); - - color.rgb = (color.rgb - 0.5) * u_Contrast + 0.5 + u_Brightness; - - float luminance = dot(color.rgb, vec3(0.299, 0.587, 0.114)); - color.rgb = mix(vec3(luminance), color.rgb, u_Saturation); - color.rgb = clamp(color.rgb, 0.0, 1.0); - - imageStore(u_OutputTexture, pixelCoord, color); -} diff --git a/assets/shaders/particle_system.glsl b/assets/shaders/particle_system.glsl deleted file mode 100644 index 1e9d435..0000000 --- a/assets/shaders/particle_system.glsl +++ /dev/null @@ -1,58 +0,0 @@ -#type compute -#version 430 core - -layout(local_size_x = 256) in; - -struct Particle -{ - vec4 position; - vec4 velocity; - vec4 color; -}; - -layout(std430, binding = 0) buffer ParticleBuffer -{ - Particle particles[]; -} particleBuffer; - -uniform float u_DeltaTime; -uniform vec3 u_Gravity; -uniform vec3 u_EmitterPosition; -uniform float u_EmissionRate; -uniform float u_ParticleLifetime; -uniform float u_Time; - -void main() -{ - uint index = gl_GlobalInvocationID.x; - - if (index >= particles.length()) - return; - - Particle particle = particleBuffer.particles[index]; - - particle.position.w -= u_DeltaTime; - - if (particle.position.w <= 0.0) - { - particle.position.xyz = u_EmitterPosition; - particle.position.w = u_ParticleLifetime; - - float angle = fract(sin(dot(vec2(index, u_Time), vec2(12.9898, 78.233))) * 43758.5453) * 2.0 * 3.14159; - float speed = 2.0 + fract(sin(dot(vec2(index + 1, u_Time), vec2(12.9898, 78.233))) * 43758.5453) * 3.0; - particle.velocity.xyz = vec3(cos(angle) * speed, 5.0, sin(angle) * speed); - particle.velocity.w = 1.0; - - particle.color = vec4(1.0, 0.5 + 0.5 * fract(sin(index * 123.456) * 43758.5453), 0.2, 1.0); - } - else - { - particle.velocity.xyz += u_Gravity * u_DeltaTime; - particle.position.xyz += particle.velocity.xyz * u_DeltaTime; - - float lifeRatio = particle.position.w / u_ParticleLifetime; - particle.color.a = lifeRatio; - } - - particleBuffer.particles[index] = particle; -} diff --git a/assets/shaders/quad.glsl b/assets/shaders/quad.glsl new file mode 100644 index 0000000..57693a6 --- /dev/null +++ b/assets/shaders/quad.glsl @@ -0,0 +1,25 @@ +#type vertex +#version 330 core +layout(location = 0) in vec2 a_Pos; +layout(location = 1) in vec2 a_TexCoord; + +out vec2 v_TexCoord; + +void main() +{ + gl_Position = vec4(a_Pos.xy, 0.0, 1.0); + v_TexCoord = a_TexCoord; +} + +#type fragment +#version 330 core +out vec4 FragColor; + +in vec2 v_TexCoord; + +uniform sampler2D screenTexture; + +void main() +{ + FragColor = texture(screenTexture, v_TexCoord); +} diff --git a/assets/shaders/texture_processor.glsl b/assets/shaders/texture_processor.glsl deleted file mode 100644 index a234ac8..0000000 --- a/assets/shaders/texture_processor.glsl +++ /dev/null @@ -1,43 +0,0 @@ -#type compute -#version 430 core - -layout(local_size_x = 16, local_size_y = 16) in; - -layout(binding = 0) uniform sampler2D u_InputTexture; -layout(binding = 1, rgba8) uniform image2D u_OutputTexture; - -uniform vec2 u_TextureSize; -uniform float u_Time; -uniform float u_Brightness; -uniform float u_Contrast; -uniform float u_Saturation; - -void main() -{ - ivec2 pixelCoord = ivec2(gl_GlobalInvocationID.xy); - - if (pixelCoord.x >= int(u_TextureSize.x) || pixelCoord.y >= int(u_TextureSize.y)) - return; - - vec2 texCoord = (vec2(pixelCoord) + 0.5) / u_TextureSize; - vec4 color = texture(u_InputTexture, texCoord); - - float time = u_Time * 2.0f; - float u = texCoord.x; - float v = texCoord.y; - - float wave = sin(u * 10.0f + time) * cos(v * 10.0f + time * 0.5f); - wave = (wave + 1.0f) * 0.5f; - - vec4 animatedColor = vec4(wave, 1.0f - wave, (u + v) * 0.5f, 1.0f); - color = mix(color, animatedColor, 0.3f); - - color.rgb = (color.rgb - 0.5) * u_Contrast + 0.5 + u_Brightness; - - float luminance = dot(color.rgb, vec3(0.299, 0.587, 0.114)); - color.rgb = mix(vec3(luminance), color.rgb, u_Saturation); - - color.rgb = clamp(color.rgb, 0.0, 1.0); - - imageStore(u_OutputTexture, pixelCoord, color); -} diff --git a/assets/shaders/textured.glsl b/assets/shaders/textured.glsl deleted file mode 100644 index ad135fe..0000000 --- a/assets/shaders/textured.glsl +++ /dev/null @@ -1,41 +0,0 @@ -#type vertex -#version 330 core - -layout(location = 0) in vec3 a_Position; -layout(location = 1) in vec4 a_Color; -layout(location = 2) in vec2 a_TexCoord; - -uniform mat4 u_ViewProjection; -uniform mat4 u_Transform; - -out vec3 v_Position; -out vec4 v_Color; -out vec2 v_TexCoord; - -void main() -{ - v_Position = a_Position; - v_Color = a_Color; - v_TexCoord = a_TexCoord; - gl_Position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0); -} - -#type fragment -#version 330 core - -layout(location = 0) out vec4 color; - -in vec3 v_Position; -in vec4 v_Color; -in vec2 v_TexCoord; - -uniform sampler2D u_Texture; - -void main() -{ - vec4 texColor = texture(u_Texture, v_TexCoord); - if (texColor.rgb == vec3(0.0)) - color = v_Color; - else - color = v_Color * texColor; -} diff --git a/src/core/camera.cpp b/src/core/camera.cpp index f580191..43cb1ee 100644 --- a/src/core/camera.cpp +++ b/src/core/camera.cpp @@ -28,22 +28,32 @@ namespace Donut void Camera::recalculate_view_matrix() { - float pitch = glm::radians(m_rotation.x); - float yaw = glm::radians(m_rotation.y); - float roll = glm::radians(m_rotation.z); + if (m_camera_mode == CameraMode::FPS) + { + float pitch = glm::radians(m_rotation.x); + float yaw = glm::radians(m_rotation.y); + float roll = glm::radians(m_rotation.z); - glm::vec3 direction; - direction.x = cos(yaw) * cos(pitch); - direction.y = sin(pitch); - direction.z = sin(yaw) * cos(pitch); + glm::vec3 direction; + direction.x = cos(yaw) * cos(pitch); + direction.y = sin(pitch); + direction.z = sin(yaw) * cos(pitch); - glm::vec3 world_up(0.0f, 1.0f, 0.0f); - glm::vec3 front = glm::normalize(direction); - glm::vec3 right = glm::normalize(glm::cross(front, world_up)); - glm::vec3 up = glm::normalize(glm::cross(right, front)); + glm::vec3 world_up(0.0f, 1.0f, 0.0f); + glm::vec3 front = glm::normalize(direction); + glm::vec3 right = glm::normalize(glm::cross(front, world_up)); + glm::vec3 up = glm::normalize(glm::cross(right, front)); - m_view_matrix = glm::lookAt(m_position, m_position + front, up); - m_view_projection_matrix = m_projection_matrix * m_view_matrix; + m_view_matrix = glm::lookAt(m_position, m_position + front, up); + m_view_projection_matrix = m_projection_matrix * m_view_matrix; + } + else if (m_camera_mode == CameraMode::Orbital) + { + glm::vec3 position = get_orbital_position(); + glm::vec3 world_up(0.0f, 1.0f, 0.0f); + m_view_matrix = glm::lookAt(position, m_target, world_up); + m_view_projection_matrix = m_projection_matrix * m_view_matrix; + } } glm::vec3 Camera::get_forward_direction() const @@ -72,58 +82,152 @@ namespace Donut void Camera::on_mouse_move(float x_offset, float y_offset, bool constrain_pitch) { - x_offset *= m_mouse_sensitivity; - y_offset *= m_mouse_sensitivity; + if (m_camera_mode == CameraMode::FPS) + { + x_offset *= m_mouse_sensitivity; + y_offset *= m_mouse_sensitivity; - m_rotation.y += x_offset; - m_rotation.x += y_offset; + m_rotation.y += x_offset; + m_rotation.x += y_offset; - if (constrain_pitch) - { - if (m_rotation.x > 89.0f) - m_rotation.x = 89.0f; - if (m_rotation.x < -89.0f) - m_rotation.x = -89.0f; - } + if (constrain_pitch) + { + if (m_rotation.x > 89.0f) + m_rotation.x = 89.0f; + if (m_rotation.x < -89.0f) + m_rotation.x = -89.0f; + } - recalculate_view_matrix(); + recalculate_view_matrix(); + } } void Camera::move_forward(float delta_time) { - m_position += get_forward_direction() * m_movement_speed * delta_time; - recalculate_view_matrix(); + if (m_camera_mode == CameraMode::FPS) + { + m_position += get_forward_direction() * m_movement_speed * delta_time; + recalculate_view_matrix(); + } } void Camera::move_backward(float delta_time) { - m_position -= get_forward_direction() * m_movement_speed * delta_time; - recalculate_view_matrix(); + if (m_camera_mode == CameraMode::FPS) + { + m_position -= get_forward_direction() * m_movement_speed * delta_time; + recalculate_view_matrix(); + } } void Camera::move_right(float delta_time) { - m_position += get_right_direction() * m_movement_speed * delta_time; - recalculate_view_matrix(); + if (m_camera_mode == CameraMode::FPS) + { + m_position += get_right_direction() * m_movement_speed * delta_time; + recalculate_view_matrix(); + } } void Camera::move_left(float delta_time) { - m_position -= get_right_direction() * m_movement_speed * delta_time; - recalculate_view_matrix(); + if (m_camera_mode == CameraMode::FPS) + { + m_position -= get_right_direction() * m_movement_speed * delta_time; + recalculate_view_matrix(); + } } void Camera::move_up(float delta_time) { - glm::vec3 world_up(0.0f, 1.0f, 0.0f); - m_position += world_up * m_movement_speed * delta_time; - recalculate_view_matrix(); + if (m_camera_mode == CameraMode::FPS) + { + glm::vec3 world_up(0.0f, 1.0f, 0.0f); + m_position += world_up * m_movement_speed * delta_time; + recalculate_view_matrix(); + } } void Camera::move_down(float delta_time) { - glm::vec3 world_up(0.0f, 1.0f, 0.0f); - m_position -= world_up * m_movement_speed * delta_time; + if (m_camera_mode == CameraMode::FPS) + { + glm::vec3 world_up(0.0f, 1.0f, 0.0f); + m_position -= world_up * m_movement_speed * delta_time; + recalculate_view_matrix(); + } + } + + void Camera::set_orbital_mode(bool enabled) + { + m_camera_mode = enabled ? CameraMode::Orbital : CameraMode::FPS; + recalculate_view_matrix(); + } + + glm::vec3 Camera::get_orbital_position() const + { + float clamped_elevation = glm::clamp(m_elevation, 0.01f, float(M_PI) - 0.01f); + return glm::vec3( + m_radius * sin(clamped_elevation) * cos(m_azimuth), + m_radius * cos(clamped_elevation), + m_radius * sin(clamped_elevation) * sin(m_azimuth) + ); + } + + void Camera::process_orbital_mouse_move(double x, double y) + { + if (m_dragging && !m_panning) + { + float dx = float(x - m_orbital_last_x); + float dy = float(y - m_orbital_last_y); + + m_azimuth += dx * m_orbit_speed; + m_elevation -= dy * m_orbit_speed; + m_elevation = glm::clamp(m_elevation, 0.01f, float(M_PI) - 0.01f); + } + + m_orbital_last_x = x; + m_orbital_last_y = y; + update_orbital_state(); recalculate_view_matrix(); } + + void Camera::process_orbital_mouse_button(int button, int action, int mods) + { + if (button == GLFW_MOUSE_BUTTON_LEFT) + { + if (action == GLFW_PRESS) + { + m_dragging = true; + m_panning = false; + } + else if (action == GLFW_RELEASE) + { + m_dragging = false; + m_panning = false; + } + } + update_orbital_state(); + } + + void Camera::process_orbital_scroll(double x_offset, double y_offset) + { + m_radius -= y_offset * m_zoom_speed; + m_radius = glm::clamp(m_radius, m_min_radius, m_max_radius); + update_orbital_state(); + recalculate_view_matrix(); + } + + void Camera::update_orbital_state() + { + m_target = glm::vec3(0.0f, 0.0f, 0.0f); + if (m_dragging || m_panning) + { + m_moving = true; + } + else + { + m_moving = false; + } + } } diff --git a/src/core/camera.h b/src/core/camera.h index 25f7e2a..7ea5472 100644 --- a/src/core/camera.h +++ b/src/core/camera.h @@ -2,9 +2,21 @@ #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> +#include <GLFW/glfw3.h> + +#define _USE_MATH_DEFINES +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif namespace Donut { + enum class CameraMode + { + FPS, + Orbital + }; + class Camera { public: @@ -42,10 +54,51 @@ namespace Donut void set_movement_speed(float speed) { m_movement_speed = speed; } float get_movement_speed() const { return m_movement_speed; } + void set_orbital_mode(bool enabled); + bool is_orbital_mode() const { return m_camera_mode == CameraMode::Orbital; } + + void set_target(const glm::vec3& target) { m_target = target; } + const glm::vec3& get_target() const { return m_target; } + + void set_radius(double radius) { m_radius = radius; } + double get_radius() const { return m_radius; } + + void set_min_radius(double min_radius) { m_min_radius = min_radius; } + double get_min_radius() const { return m_min_radius; } + + void set_max_radius(double max_radius) { m_max_radius = max_radius; } + double get_max_radius() const { return m_max_radius; } + + void set_azimuth(float azimuth) { m_azimuth = azimuth; } + float get_azimuth() const { return m_azimuth; } + + void set_elevation(float elevation) { m_elevation = elevation; } + float get_elevation() const { return m_elevation; } + + void set_orbit_speed(float orbit_speed) { m_orbit_speed = orbit_speed; } + float get_orbit_speed() const { return m_orbit_speed; } + + void set_zoom_speed(double zoom_speed) { m_zoom_speed = zoom_speed; } + double get_zoom_speed() const { return m_zoom_speed; } + + glm::vec3 get_orbital_position() const; + + void process_orbital_mouse_move(double x, double y); + void process_orbital_mouse_button(int button, int action, int mods); + void process_orbital_scroll(double x_offset, double y_offset); + + bool is_dragging() const { return m_dragging; } + bool is_panning() const { return m_panning; } + bool is_moving() const { return m_moving; } + private: void recalculate_view_matrix(); void recalculate_projection_matrix(); + void update_orbital_state(); + private: + CameraMode m_camera_mode = CameraMode::FPS; + glm::mat4 m_projection_matrix; glm::mat4 m_view_matrix; glm::mat4 m_view_projection_matrix; @@ -63,5 +116,19 @@ namespace Donut bool m_first_mouse = true; float m_last_x = 0.0f; float m_last_y = 0.0f; + + glm::vec3 m_target = glm::vec3(0.0f, 0.0f, 0.0f); + double m_radius = 6.34194e10; + double m_min_radius = 1e10; + double m_max_radius = 1e12; + float m_azimuth = 0.0f; + float m_elevation = static_cast<float>(M_PI) / 2.0f; + float m_orbit_speed = 0.01f; + double m_zoom_speed = 25e9f; + bool m_dragging = false; + bool m_panning = false; + bool m_moving = false; + double m_orbital_last_x = 0.0; + double m_orbital_last_y = 0.0; }; } diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp new file mode 100644 index 0000000..29fa65a --- /dev/null +++ b/src/engine/engine.cpp @@ -0,0 +1,347 @@ +#include "engine.h" +#include <GLFW/glfw3.h> +#include <glad/glad.h> +#include <iostream> +#include <fstream> +#include <sstream> +#include <limits> +#include "rendering/vertex_buffer.h" +#include "rendering/index_buffer.h" + +namespace Donut +{ + Engine::Engine() + : m_sag_a(glm::vec3(0.0f, 0.0f, 0.0f), + static_cast<float>(8.54e36)) + { + GLFWwindow* window = static_cast<GLFWwindow*>(Application::get().get_window().get_native_window()); + glfwGetFramebufferSize(window, &m_width, &m_height); + m_compute_height = 420; + m_compute_width = (m_width * 420) / m_height; + + m_camera.set_orbital_mode(true); + m_camera.set_radius(6.34194e10); + m_camera.set_min_radius(1e10); + m_camera.set_max_radius(1e12); + m_camera.set_orbit_speed(0.01f); + m_camera.set_zoom_speed(25e9f); + + m_objects = + { + { glm::vec4(4e11f, 0.0f, 0.0f, 4e10f), glm::vec4(1,1,0,1), static_cast<float>(1.98892e30) }, + { glm::vec4(0.0f, 0.0f, 4e11f, 4e10f), glm::vec4(1,0,0,1), static_cast<float>(1.98892e30) }, + { glm::vec4(0.0f, 0.0f, 0.0f, m_sag_a.m_rs), glm::vec4(0,0,0,1), static_cast<float>(m_sag_a.m_mass) } + }; + + m_quad_shader = Shader::create("assets/shaders/quad.glsl"); + m_grid_shader = Shader::create("assets/shaders/grid.glsl"); + m_compute_shader = Shader::create_compute("Geodesic", load_compute_shader("assets/shaders/geodesic.glsl")); + + m_camera_ubo = UniformBuffer::create(128, 1); + m_disk_ubo = UniformBuffer::create(sizeof(float) * 4, 2); + + uint32_t obj_ubo_size = sizeof(int) + 3 * sizeof(float) + + 16 * (sizeof(glm::vec4) + sizeof(glm::vec4)) + + 16 * sizeof(float); + m_objects_ubo = UniformBuffer::create(obj_ubo_size, 3); + + float quad_vertices[] = + { + -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 + }; + + m_quad_vao = VertexArray::create(); + + auto vertex_buffer = VertexBuffer::create(quad_vertices, static_cast<uint32_t>(sizeof(quad_vertices))); + VertexBufferLayout layout; + layout.push<float>(2); + layout.push<float>(2); + vertex_buffer->set_layout(layout); + m_quad_vao->add_vertex_buffer(vertex_buffer); + + m_texture = Texture2D::create(m_width, m_height); + } + + void Engine::generate_grid(const std::vector<ObjectData>& objects) + { + const int grid_size = 25; + const float spacing = 1e10f; + + std::vector<glm::vec3> vertices; + std::vector<uint32_t> indices; + + for (int z = 0; z <= grid_size; ++z) + { + for (int x = 0; x <= grid_size; ++x) + { + float world_x = (x - grid_size / 2) * spacing; + float world_z = (z - grid_size / 2) * spacing; + float y = 0.0f; + + for (const auto& obj : objects) + { + glm::vec3 obj_pos = glm::vec3(obj.m_pos_radius); + double mass = obj.m_mass; + double radius = obj.m_pos_radius.w; + + double r_s = 2.0 * G * mass / (c * c); + double dx = world_x - obj_pos.x; + double dz = world_z - obj_pos.z; + double dist = sqrt(dx * dx + dz * dz); + + if (dist > r_s) + { + double delta_y = 2.0 * sqrt(r_s * (dist - r_s)); + y += static_cast<float>(delta_y) - 3e10f; + } + else + { + y += 2.0f * static_cast<float>(sqrt(r_s * r_s)) - 3e10f; + } + } + + vertices.emplace_back(world_x, y, world_z); + } + } + + for (int z = 0; z < grid_size; ++z) + { + for (int x = 0; x < grid_size; ++x) + { + int i = z * (grid_size + 1) + x; + indices.push_back(i); + indices.push_back(i + 1); + indices.push_back(i); + indices.push_back(i + grid_size + 1); + } + } + + if (!m_grid_vao) + { + m_grid_vao = Ref<VertexArray>(VertexArray::create()); + + auto vertex_buffer = Ref<VertexBuffer>(VertexBuffer::create(vertices.data(), static_cast<uint32_t>(vertices.size() * sizeof(glm::vec3)))); + VertexBufferLayout layout; + layout.push<float>(3); + vertex_buffer->set_layout(layout); + m_grid_vao->add_vertex_buffer(vertex_buffer); + + auto index_buffer = Ref<IndexBuffer>(IndexBuffer::create(indices.data(), static_cast<uint32_t>(indices.size()))); + m_grid_vao->set_index_buffer(index_buffer); + } + else + { + auto vertex_buffer = m_grid_vao->get_vertex_buffers()[0]; + vertex_buffer->set_data(vertices.data(), static_cast<uint32_t>(vertices.size() * sizeof(glm::vec3))); + } + + m_grid_index_count = static_cast<int>(indices.size()); + } + + void Engine::draw_grid(const glm::mat4& view_proj) + { + if (!m_grid_shader || !m_grid_vao) + return; + + m_grid_shader->bind(); + m_grid_shader->set_mat4("viewProj", view_proj); + + RenderCommand::disable_depth_test(); + RenderCommand::enable_blending(); + + m_grid_vao->bind(); + glDrawElements(GL_LINES, m_grid_index_count, GL_UNSIGNED_INT, nullptr); + + RenderCommand::enable_depth_test(); + } + + void Engine::update_window_dimensions() + { + GLFWwindow* window = static_cast<GLFWwindow*>(Application::get().get_window().get_native_window()); + int old_width = m_width; + int old_height = m_height; + glfwGetFramebufferSize(window, &m_width, &m_height); + m_compute_height = 420; + m_compute_width = (m_width * 420) / m_height; + + if (old_width != m_width || old_height != m_height) + m_texture = Texture2D::create(m_width, m_height); + } + + void Engine::draw_full_screen_quad() + { + RenderCommand::set_viewport(0, 0, m_width, m_height); + + m_quad_shader->bind(); + std::cout << "Texture ID: " << m_texture->get_renderer_id() << ", Size: " << m_texture->get_width() << "x" << m_texture->get_height() << std::endl; + m_texture->bind(0); + m_quad_shader->set_int("screenTexture", 0); + std::cout << "Set screenTexture uniform to slot 0\n"; + + RenderCommand::disable_depth_test(); + RenderCommand::draw_indexed(m_quad_vao, 6); + RenderCommand::enable_depth_test(); + } + + void Engine::dispatch_compute(const Camera& cam) + { + int cw = m_compute_width; + int ch = m_compute_height; + + if (m_texture->get_width() != cw || m_texture->get_height() != ch) + m_texture = Texture2D::create(cw, ch); + + m_compute_shader->bind(); + upload_camera_ubo(cam); + upload_disk_ubo(); + upload_objects_ubo(m_objects); + + m_texture->bind_as_image(0, false); + + uint32_t groups_x = (uint32_t)std::ceil(cw / 16.0f); + uint32_t groups_y = (uint32_t)std::ceil(ch / 16.0f); + m_compute_shader->dispatch(groups_x, groups_y, 1); + m_compute_shader->memory_barrier(IMAGE_ACCESS_BARRIER_BIT); + + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); + glMemoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT); + } + + void Engine::upload_camera_ubo(const Camera& cam) + { + struct UBOData + { + glm::vec3 pos; float _pad0; + glm::vec3 right; float _pad1; + glm::vec3 up; float _pad2; + glm::vec3 forward; float _pad3; + float tan_half_fov; + float aspect; + bool moving; + int _pad4; + } data; + + glm::vec3 fwd = glm::normalize(cam.get_target() - cam.get_orbital_position()); + glm::vec3 up = glm::vec3(0, 1, 0); + glm::vec3 right = glm::normalize(glm::cross(fwd, up)); + up = glm::cross(right, fwd); + + data.pos = cam.get_orbital_position(); + data.right = right; + data.up = up; + data.forward = fwd; + data.tan_half_fov = static_cast<float>(tan(glm::radians(60.0f * 0.5f))); + data.aspect = static_cast<float>(m_width) / static_cast<float>(m_height); + data.moving = cam.is_dragging() || cam.is_panning(); + + m_camera_ubo->set_data(&data, sizeof(UBOData)); + } + + void Engine::upload_objects_ubo(const std::vector<ObjectData>& objs) + { + struct UBOData + { + int num_objects; + float _pad0, _pad1, _pad2; + glm::vec4 pos_radius[16]; + glm::vec4 color[16]; + float mass[16]; + } data; + + size_t count = std::min(objs.size(), size_t(16)); + data.num_objects = static_cast<int>(count); + + for (size_t i = 0; i < count; ++i) + { + data.pos_radius[i] = objs[i].m_pos_radius; + data.color[i] = objs[i].m_color; + data.mass[i] = objs[i].m_mass; + } + + m_objects_ubo->set_data(&data, sizeof(data)); + } + + void Engine::upload_disk_ubo() + { + float r1 = static_cast<float>(m_sag_a.m_rs * 2.2); + float r2 = static_cast<float>(m_sag_a.m_rs * 5.2); + float num = 2.0f; + float thickness = 1e9f; + float disk_data[4] = { r1, r2, num, thickness }; + + m_disk_ubo->set_data(disk_data, sizeof(disk_data)); + } + + void Engine::update_physics(float delta_time) + { + for (auto& obj : m_objects) + { + for (auto& obj2 : m_objects) + { + if (&obj == &obj2) continue; + float dx = obj2.m_pos_radius.x - obj.m_pos_radius.x; + float dy = obj2.m_pos_radius.y - obj.m_pos_radius.y; + float dz = obj2.m_pos_radius.z - obj.m_pos_radius.z; + float distance = sqrt(dx * dx + dy * dy + dz * dz); + + if (distance > 0) + { + std::vector<double> direction = {dx / distance, dy / distance, dz / distance}; + double Gforce = (G * obj.m_mass * obj2.m_mass) / (distance * distance); + double acc1 = Gforce / obj.m_mass; + std::vector<double> acc = {direction[0] * acc1, direction[1] * acc1, direction[2] * acc1}; + + if (m_gravity) + { + obj.m_velocity.x += static_cast<float>(acc[0]); + obj.m_velocity.y += static_cast<float>(acc[1]); + obj.m_velocity.z += static_cast<float>(acc[2]); + + obj.m_pos_radius.x += static_cast<float>(obj.m_velocity.x); + obj.m_pos_radius.y += static_cast<float>(obj.m_velocity.y); + obj.m_pos_radius.z += static_cast<float>(obj.m_velocity.z); + } + } + } + } + } + + void Engine::render_scene() + { + glm::mat4 view = glm::lookAt(m_camera.get_orbital_position(), m_camera.get_target(), glm::vec3(0,1,0)); + glm::mat4 proj = glm::perspective(glm::radians(60.0f), float(m_width)/m_height, 1e9f, 1e14f); + glm::mat4 view_proj = proj * view; + + generate_grid(m_objects); + draw_grid(view_proj); + + RenderCommand::set_viewport(0, 0, m_width, m_height); + dispatch_compute(m_camera); + draw_full_screen_quad(); + } + + std::string Engine::load_compute_shader(const std::string& path) + { + std::ifstream in(path); + if(!in.is_open()) + { + std::cerr << "Failed to open compute shader: " << path << "\n"; + return ""; + } + + std::stringstream ss; + ss << in.rdbuf(); + std::string result = ss.str(); + if (result.empty()) + std::cerr << "Warning: Compute shader file is empty: " << path << "\n"; + else + std::cout << "Successfully loaded compute shader: " << path << " (size: " << result.size() << " bytes)\n"; + return result; + } +} diff --git a/src/engine/engine.h b/src/engine/engine.h new file mode 100644 index 0000000..6f90efb --- /dev/null +++ b/src/engine/engine.h @@ -0,0 +1,118 @@ +#pragma once + +#include <vector> +#include <iostream> +#include <GLFW/glfw3.h> +#include <glm/glm.hpp> +#include <glm/gtc/matrix_transform.hpp> +#include <glm/gtc/type_ptr.hpp> +#include <fstream> +#include <sstream> +#include <chrono> +#include <cmath> + +#include "core/application.h" +#include "core/camera.h" +#include "rendering/renderer.h" +#include "rendering/shader.h" +#include "rendering/texture.h" +#include "rendering/vertex_array.h" +#include "rendering/uniform_buffer.h" + +namespace Donut +{ + const double c = 299792458.0; + const double G = 6.67430e-11; + + struct BlackHole + { + glm::vec3 m_position; + double m_mass; + double m_radius; + double m_rs; + + BlackHole(glm::vec3 pos, float mass) + : m_position(pos), + m_mass(mass) + { + m_rs = 2.0 * G * m_mass / (c * c); + } + + bool intercept(float px, float py, float pz) const + { + double dx = double(px) - double(m_position.x); + double dy = double(py) - double(m_position.y); + double dz = double(pz) - double(m_position.z); + double dist2 = dx * dx + dy * dy + dz * dz; + return dist2 < m_rs * m_rs; + } + }; + + struct ObjectData + { + glm::vec4 m_pos_radius; + glm::vec4 m_color; + float m_mass; + glm::vec3 m_velocity = glm::vec3(0.0f, 0.0f, 0.0f); + }; + + class Engine + { + public: + Engine(); + ~Engine() = default; + + void generate_grid(const std::vector<ObjectData>& objects); + void draw_grid(const glm::mat4& view_proj); + void draw_full_screen_quad(); + void dispatch_compute(const Camera& cam); + void upload_camera_ubo(const Camera& cam); + void upload_objects_ubo(const std::vector<ObjectData>& objs); + void upload_disk_ubo(); + void render_scene(); + void update_physics(float delta_time); + void update_window_dimensions(); + + Camera& get_camera() { return m_camera; } + const Camera& get_camera() const { return m_camera; } + + const std::vector<ObjectData>& get_objects() const { return m_objects; } + std::vector<ObjectData>& get_objects() { return m_objects; } + + bool get_gravity() const { return m_gravity; } + void set_gravity(bool gravity) { m_gravity = gravity; } + + int get_width() const { return m_width; } + int get_height() const { return m_height; } + int get_compute_width() const { return m_compute_width; } + int get_compute_height() const { return m_compute_height; } + + private: + std::string create_basic_vertex_shader(); + std::string create_basic_fragment_shader(); + std::string load_compute_shader(const std::string& path); + private: + Ref<Shader> m_grid_shader; + Ref<Shader> m_quad_shader; + Ref<Shader> m_compute_shader; + Ref<Texture2D> m_texture; + Ref<VertexArray> m_quad_vao; + Ref<VertexArray> m_grid_vao; + Ref<UniformBuffer> m_camera_ubo; + Ref<UniformBuffer> m_disk_ubo; + Ref<UniformBuffer> m_objects_ubo; + + int m_grid_index_count = 0; + int m_width; + int m_height; + int m_compute_width; + int m_compute_height; + float m_width_scale = 100000000000.0f; + float m_height_scale = 75000000000.0f; + + std::vector<ObjectData> m_objects; + BlackHole m_sag_a; + Camera m_camera; + bool m_gravity = false; + }; +} diff --git a/src/engine/object.cpp b/src/engine/object.cpp new file mode 100644 index 0000000..2e76b16 --- /dev/null +++ b/src/engine/object.cpp @@ -0,0 +1,46 @@ +#include "object.h" + +namespace Donut +{ + Ray::Ray(glm::vec3 o, glm::vec3 d) + : m_origin(o), + m_direction(glm::normalize(d)) { } + + Material::Material(glm::vec3 c, float s, float e) + : m_color(c), + m_specular(s), + m_emission(e) { } + + Object::Object(glm::vec3 c, float r, Material m) + : m_centre(c), + m_radius(r), + m_material(m) { } + + bool Object::intersect(Ray &ray, float &t) + { + glm::vec3 oc = ray.m_origin - m_centre; + float a = glm::dot(ray.m_direction, ray.m_direction); + float b = 2.0f * glm::dot(oc, ray.m_direction); + float c = glm::dot(oc, oc) - m_radius * m_radius; + double discriminant = b*b - 4*a*c; + + if(discriminant < 0) + return false; + + float intercept = (-b - sqrt(discriminant)) / (2.0f*a); + if(intercept < 0) + { + intercept = (-b + sqrt(discriminant)) / (2.0f*a); + if(intercept<0) + return false; + } + + t = intercept; + return true; + } + + glm::vec3 Object::get_normal(glm::vec3 &point) const + { + return glm::normalize(point - m_centre); + } +} diff --git a/src/engine/object.h b/src/engine/object.h new file mode 100644 index 0000000..5f5d86f --- /dev/null +++ b/src/engine/object.h @@ -0,0 +1,38 @@ +#pragma once + +#include <glm/glm.hpp> + +namespace Donut +{ + class Ray + { + public: + glm::vec3 m_direction; + glm::vec3 m_origin; + + Ray(glm::vec3 o, glm::vec3 d); + }; + + class Material + { + public: + glm::vec3 m_color; + float m_specular; + float m_emission; + + Material(glm::vec3 c, float s, float e); + }; + + class Object + { + public: + glm::vec3 m_centre; + float m_radius; + Material m_material; + + Object(glm::vec3 c, float r, Material m); + + bool intersect(Ray &ray, float &t); + glm::vec3 get_normal(glm::vec3 &point) const; + }; +}; diff --git a/src/engine/scene.cpp b/src/engine/scene.cpp new file mode 100644 index 0000000..bfac1aa --- /dev/null +++ b/src/engine/scene.cpp @@ -0,0 +1,57 @@ +#include "scene.h" + +namespace Donut +{ + Scene::Scene() + : m_light_pos(5.0f, 5.0f, 5.0f) { } + + glm::vec3 Scene::trace(Ray &ray) + { + float closest = std::numeric_limits<float>::infinity(); + const Object* hit_obj = nullptr; + + for(auto& obj : objs) + { + float t; + + if(obj.intersect(ray, t)) + if(t < closest) + { + closest = t; + hit_obj = &obj; + } + } + + if(hit_obj) + { + glm::vec3 hit_point = ray.m_origin + ray.m_direction * closest; + glm::vec3 normal = hit_obj->get_normal(hit_point); + glm::vec3 light_dir = glm::normalize(m_light_pos - hit_point); + + float diff = std::max(glm::dot(normal, light_dir), 0.0f); + + Ray shadow_ray(hit_point + normal * 0.001f, light_dir); + bool in_shadow = false; + + for(auto& obj : objs) + { + float t; + + if(obj.intersect(shadow_ray, t)) + { + in_shadow = true; + break; + } + } + + glm::vec3 color = hit_obj->m_material.m_color; + float ambient = 0.1f; + + if (in_shadow) + return color * ambient; + return color * (ambient + diff * 0.9f); + } + + return glm::vec3(0.0f, 0.0f, 0.1f); + } +} diff --git a/src/engine/scene.h b/src/engine/scene.h new file mode 100644 index 0000000..abe582e --- /dev/null +++ b/src/engine/scene.h @@ -0,0 +1,20 @@ +#pragma once + +#include <vector> +#include <limits> +#include <glm/glm.hpp> + +#include "object.h" + +namespace Donut +{ + class Scene + { + public: + std::vector<Object> objs; + glm::vec3 m_light_pos; + + Scene(); + glm::vec3 trace(Ray &ray); + }; +}; diff --git a/src/platform/opengl/opengl_renderer_api.cpp b/src/platform/opengl/opengl_renderer_api.cpp index 4542231..628c0af 100644 --- a/src/platform/opengl/opengl_renderer_api.cpp +++ b/src/platform/opengl/opengl_renderer_api.cpp @@ -49,6 +49,11 @@ namespace Donut glEnable(GL_DEPTH_TEST); } + void OpenGLRendererAPI::disable_depth_test() + { + glDisable(GL_DEPTH_TEST); + } + void OpenGLRendererAPI::set_face_culling(bool enabled) { if (enabled) @@ -61,6 +66,17 @@ namespace Donut glDisable(GL_CULL_FACE); } + void OpenGLRendererAPI::enable_blending() + { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + void OpenGLRendererAPI::disable_blending() + { + glDisable(GL_BLEND); + } + void OpenGLRendererAPI::draw_indexed(const Ref<VertexArray>& vertex_array, uint32_t index_count) { uint32_t count = index_count ? index_count : vertex_array->get_index_buffer()->get_count(); diff --git a/src/platform/opengl/opengl_renderer_api.h b/src/platform/opengl/opengl_renderer_api.h index 25accbb..fd446c6 100644 --- a/src/platform/opengl/opengl_renderer_api.h +++ b/src/platform/opengl/opengl_renderer_api.h @@ -19,7 +19,10 @@ namespace Donut virtual void set_clear_color(const glm::vec4& color) override; virtual void clear() override; virtual void enable_depth_test() override; + virtual void disable_depth_test() override; virtual void set_face_culling(bool enabled) override; + virtual void enable_blending() override; + virtual void disable_blending() override; virtual void draw_indexed(const Ref<VertexArray>& vertex_array, uint32_t index_count = 0) override; diff --git a/src/platform/opengl/opengl_uniform_buffer.cpp b/src/platform/opengl/opengl_uniform_buffer.cpp new file mode 100644 index 0000000..ca7d3d2 --- /dev/null +++ b/src/platform/opengl/opengl_uniform_buffer.cpp @@ -0,0 +1,29 @@ +#include "opengl_uniform_buffer.h" + +namespace Donut +{ + OpenGLUniformBuffer::OpenGLUniformBuffer(uint32_t size, uint32_t binding) + : m_size(size), m_binding(binding) + { + glGenBuffers(1, &m_renderer_id); + glBindBuffer(GL_UNIFORM_BUFFER, m_renderer_id); + glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, binding, m_renderer_id); + } + + OpenGLUniformBuffer::~OpenGLUniformBuffer() + { + glDeleteBuffers(1, &m_renderer_id); + } + + void OpenGLUniformBuffer::set_data(const void* data, uint32_t size, uint32_t offset) + { + glBindBuffer(GL_UNIFORM_BUFFER, m_renderer_id); + glBufferSubData(GL_UNIFORM_BUFFER, offset, size, data); + } + + void OpenGLUniformBuffer::bind(uint32_t binding) + { + glBindBufferBase(GL_UNIFORM_BUFFER, binding, m_renderer_id); + } +}; diff --git a/src/platform/opengl/opengl_uniform_buffer.h b/src/platform/opengl/opengl_uniform_buffer.h new file mode 100644 index 0000000..2a9131f --- /dev/null +++ b/src/platform/opengl/opengl_uniform_buffer.h @@ -0,0 +1,22 @@ +#pragma once + +#include "rendering/uniform_buffer.h" +#include <glad/glad.h> + +namespace Donut +{ + class OpenGLUniformBuffer : public UniformBuffer + { + public: + OpenGLUniformBuffer(uint32_t size, uint32_t binding); + virtual ~OpenGLUniformBuffer(); + + virtual void set_data(const void* data, uint32_t size, uint32_t offset = 0) override; + virtual void bind(uint32_t binding) override; + + private: + uint32_t m_renderer_id = 0; + uint32_t m_size = 0; + uint32_t m_binding = 0; + }; +}; diff --git a/src/platform/vulkan/vulkan_renderer_api.cpp b/src/platform/vulkan/vulkan_renderer_api.cpp index f91a0ea..6273d31 100644 --- a/src/platform/vulkan/vulkan_renderer_api.cpp +++ b/src/platform/vulkan/vulkan_renderer_api.cpp @@ -22,10 +22,22 @@ namespace Donut { } + void VulkanRendererAPI::disable_depth_test() + { + } + void VulkanRendererAPI::set_face_culling(bool enabled) { } + void VulkanRendererAPI::enable_blending() + { + } + + void VulkanRendererAPI::disable_blending() + { + } + void VulkanRendererAPI::draw_indexed(const Ref<VertexArray>& vertex_array, uint32_t index_count) { } diff --git a/src/platform/vulkan/vulkan_renderer_api.h b/src/platform/vulkan/vulkan_renderer_api.h index d1394b3..c0075a2 100644 --- a/src/platform/vulkan/vulkan_renderer_api.h +++ b/src/platform/vulkan/vulkan_renderer_api.h @@ -15,7 +15,10 @@ namespace Donut virtual void set_clear_color(const glm::vec4& color) override; virtual void clear() override; virtual void enable_depth_test() override; + virtual void disable_depth_test() override; virtual void set_face_culling(bool enabled) override; + virtual void enable_blending() override; + virtual void disable_blending() override; virtual void draw_indexed(const Ref<VertexArray>& vertex_array, uint32_t index_count = 0) override; diff --git a/src/platform/vulkan/vulkan_uniform_buffer.cpp b/src/platform/vulkan/vulkan_uniform_buffer.cpp new file mode 100644 index 0000000..6fafeee --- /dev/null +++ b/src/platform/vulkan/vulkan_uniform_buffer.cpp @@ -0,0 +1,21 @@ +#include "vulkan_uniform_buffer.h" + +namespace Donut +{ + VulkanUniformBuffer::VulkanUniformBuffer(uint32_t size, uint32_t binding) + : m_size(size), m_binding(binding) + { + } + + VulkanUniformBuffer::~VulkanUniformBuffer() + { + } + + void VulkanUniformBuffer::set_data(const void* data, uint32_t size, uint32_t offset) + { + } + + void VulkanUniformBuffer::bind(uint32_t binding) + { + } +}; diff --git a/src/platform/vulkan/vulkan_uniform_buffer.h b/src/platform/vulkan/vulkan_uniform_buffer.h new file mode 100644 index 0000000..e3c9196 --- /dev/null +++ b/src/platform/vulkan/vulkan_uniform_buffer.h @@ -0,0 +1,20 @@ +#pragma once + +#include "rendering/uniform_buffer.h" + +namespace Donut +{ + class VulkanUniformBuffer : public UniformBuffer + { + public: + VulkanUniformBuffer(uint32_t size, uint32_t binding); + virtual ~VulkanUniformBuffer(); + + virtual void set_data(const void* data, uint32_t size, uint32_t offset = 0) override; + virtual void bind(uint32_t binding) override; + + private: + uint32_t m_size = 0; + uint32_t m_binding = 0; + }; +}; diff --git a/src/rendering/index_buffer.cpp b/src/rendering/index_buffer.cpp index 1095e3b..3271a5a 100644 --- a/src/rendering/index_buffer.cpp +++ b/src/rendering/index_buffer.cpp @@ -6,14 +6,14 @@ namespace Donut { - IndexBuffer* IndexBuffer::create(const uint32_t* indices, uint32_t count) + Ref<IndexBuffer> IndexBuffer::create(const uint32_t* indices, uint32_t count) { switch (Renderer::get_api()) { case RendererAPI::API::OpenGL: - return new OpenGLIndexBuffer(indices, count); + return create_ref<OpenGLIndexBuffer>(indices, count); case RendererAPI::API::Vulkan: - return new VulkanIndexBuffer((uint32_t*)indices, count); + return create_ref<VulkanIndexBuffer>((uint32_t*)indices, count); default: return nullptr; } diff --git a/src/rendering/index_buffer.h b/src/rendering/index_buffer.h index 856559a..9859c20 100644 --- a/src/rendering/index_buffer.h +++ b/src/rendering/index_buffer.h @@ -1,5 +1,6 @@ #pragma once +#include "core/memory.h" #include <cstdint> namespace Donut @@ -13,6 +14,6 @@ namespace Donut virtual void unbind() const = 0; virtual uint32_t get_count() const = 0; - static IndexBuffer* create(const uint32_t* indices, uint32_t count); + static Ref<IndexBuffer> create(const uint32_t* indices, uint32_t count); }; }; diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 5e4682c..f34e2b0 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -7,14 +7,14 @@ namespace Donut { - Scope<RendererAPI> RendererAPI::create() + Ref<RendererAPI> RendererAPI::create() { switch (s_api) { case API::OpenGL: - return create_scope<OpenGLRendererAPI>(); + return create_ref<OpenGLRendererAPI>(); case API::Vulkan: - return create_scope<VulkanRendererAPI>(); + return create_ref<VulkanRendererAPI>(); default: return nullptr; } @@ -50,7 +50,7 @@ namespace Donut RenderCommand::draw_indexed(vertex_array); } - Scope<RendererAPI> RenderCommand::s_renderer_api = RendererAPI::create(); + Ref<RendererAPI> RenderCommand::s_renderer_api = RendererAPI::create(); void Renderer::set_clear_color(const glm::vec4& color) { diff --git a/src/rendering/renderer.h b/src/rendering/renderer.h index 6212b21..4a028e5 100644 --- a/src/rendering/renderer.h +++ b/src/rendering/renderer.h @@ -26,14 +26,17 @@ namespace Donut virtual void set_clear_color(const glm::vec4& color) = 0; virtual void clear() = 0; virtual void enable_depth_test() = 0; + virtual void disable_depth_test() = 0; virtual void set_face_culling(bool enabled) = 0; + virtual void enable_blending() = 0; + virtual void disable_blending() = 0; virtual void draw_indexed(const Ref<VertexArray>& vertex_array, uint32_t index_count = 0) = 0; inline static API get_api() { return s_api; } inline static void set_api(API api) { s_api = api; } - static Scope<RendererAPI> create(); + static Ref<RendererAPI> create(); private: static API s_api; }; @@ -66,18 +69,33 @@ namespace Donut s_renderer_api->enable_depth_test(); } + inline static void disable_depth_test() + { + s_renderer_api->disable_depth_test(); + } + inline static void set_face_culling(bool enabled) { s_renderer_api->set_face_culling(enabled); } + inline static void enable_blending() + { + s_renderer_api->enable_blending(); + } + + inline static void disable_blending() + { + s_renderer_api->disable_blending(); + } + inline static void draw_indexed(const Ref<VertexArray>& vertex_array, uint32_t index_count = 0) { s_renderer_api->draw_indexed(vertex_array, index_count); } private: - static Scope<RendererAPI> s_renderer_api; + static Ref<RendererAPI> s_renderer_api; }; class Renderer diff --git a/src/rendering/shader.cpp b/src/rendering/shader.cpp index 1bbd0fc..833b719 100644 --- a/src/rendering/shader.cpp +++ b/src/rendering/shader.cpp @@ -6,40 +6,40 @@ namespace Donut { - Shader* Shader::create(const std::string& filepath) + Ref<Shader> Shader::create(const std::string& filepath) { switch (Renderer::get_api()) { case RendererAPI::API::OpenGL: - return new OpenGLShader(filepath); + return create_ref<OpenGLShader>(filepath); case RendererAPI::API::Vulkan: - return new VulkanShader(filepath); + return create_ref<VulkanShader>(filepath); default: return nullptr; } } - Shader* Shader::create(const std::string& name, const std::string& vertex_src, const std::string& fragment_src) + Ref<Shader> Shader::create(const std::string& name, const std::string& vertex_src, const std::string& fragment_src) { switch (Renderer::get_api()) { case RendererAPI::API::OpenGL: - return new OpenGLShader(name, vertex_src, fragment_src); + return create_ref<OpenGLShader>(name, vertex_src, fragment_src); case RendererAPI::API::Vulkan: - return new VulkanShader(name, vertex_src, fragment_src); + return create_ref<VulkanShader>(name, vertex_src, fragment_src); default: return nullptr; } } - Shader* Shader::create_compute(const std::string& name, const std::string& compute_src) + Ref<Shader> Shader::create_compute(const std::string& name, const std::string& compute_src) { switch (Renderer::get_api()) { case RendererAPI::API::OpenGL: - return new OpenGLShader(name, compute_src); + return create_ref<OpenGLShader>(name, compute_src); case RendererAPI::API::Vulkan: - return new VulkanShader(name, compute_src); + return create_ref<VulkanShader>(name, compute_src); default: return nullptr; } @@ -58,14 +58,14 @@ namespace Donut Ref<Shader> ShaderLibrary::load(const std::string& filepath) { - auto shader = Ref<Shader>(Shader::create(filepath)); + auto shader = Shader::create(filepath); add(shader); return shader; } Ref<Shader> ShaderLibrary::load(const std::string& name, const std::string& filepath) { - auto shader = Ref<Shader>(Shader::create(filepath)); + auto shader = Shader::create(filepath); add(name, shader); return shader; } diff --git a/src/rendering/shader.h b/src/rendering/shader.h index 814f0cf..7f024bd 100644 --- a/src/rendering/shader.h +++ b/src/rendering/shader.h @@ -35,9 +35,9 @@ namespace Donut virtual const std::string& get_name() const = 0; - static Shader* create(const std::string& filepath); - static Shader* create(const std::string& name, const std::string& vertex_src, const std::string& fragment_src); - static Shader* create_compute(const std::string& name, const std::string& compute_src); + static Ref<Shader> create(const std::string& filepath); + static Ref<Shader> create(const std::string& name, const std::string& vertex_src, const std::string& fragment_src); + static Ref<Shader> create_compute(const std::string& name, const std::string& compute_src); }; class ShaderLibrary diff --git a/src/rendering/uniform_buffer.cpp b/src/rendering/uniform_buffer.cpp new file mode 100644 index 0000000..4e693a2 --- /dev/null +++ b/src/rendering/uniform_buffer.cpp @@ -0,0 +1,23 @@ +#include "uniform_buffer.h" +#include "renderer.h" + +#include "platform/opengl/opengl_uniform_buffer.h" +#include "platform/vulkan/vulkan_uniform_buffer.h" + +namespace Donut +{ + Ref<UniformBuffer> UniformBuffer::create(uint32_t size, uint32_t binding) + { + switch (Renderer::get_api()) + { + case RendererAPI::API::OpenGL: + return create_ref<OpenGLUniformBuffer>(size, binding); + case RendererAPI::API::Vulkan: + return create_ref<VulkanUniformBuffer>(size, binding); + case RendererAPI::API::None: + return nullptr; + default: + return nullptr; + } + } +}; diff --git a/src/rendering/uniform_buffer.h b/src/rendering/uniform_buffer.h new file mode 100644 index 0000000..4df39d9 --- /dev/null +++ b/src/rendering/uniform_buffer.h @@ -0,0 +1,18 @@ +#pragma once + +#include "core/memory.h" +#include <cstdint> + +namespace Donut +{ + class UniformBuffer + { + public: + virtual ~UniformBuffer() = default; + + virtual void set_data(const void* data, uint32_t size, uint32_t offset = 0) = 0; + virtual void bind(uint32_t binding) = 0; + + static Ref<UniformBuffer> create(uint32_t size, uint32_t binding); + }; +}; diff --git a/src/rendering/vertex_array.cpp b/src/rendering/vertex_array.cpp index bb92ff3..d0cd4b0 100644 --- a/src/rendering/vertex_array.cpp +++ b/src/rendering/vertex_array.cpp @@ -17,14 +17,14 @@ namespace Donut } } - VertexArray* VertexArray::create() + Ref<VertexArray> VertexArray::create() { switch (Renderer::get_api()) { case RendererAPI::API::OpenGL: - return new OpenGLVertexArray(); + return create_ref<OpenGLVertexArray>(); case RendererAPI::API::Vulkan: - return new VulkanVertexArray(); + return create_ref<VulkanVertexArray>(); default: return nullptr; } diff --git a/src/rendering/vertex_array.h b/src/rendering/vertex_array.h index a39849f..3729167 100644 --- a/src/rendering/vertex_array.h +++ b/src/rendering/vertex_array.h @@ -23,6 +23,6 @@ namespace Donut virtual const std::vector<Ref<VertexBuffer>>& get_vertex_buffers() const = 0; virtual const Ref<IndexBuffer>& get_index_buffer() const = 0; - static VertexArray* create(); + static Ref<VertexArray> create(); }; }; diff --git a/src/rendering/vertex_buffer.cpp b/src/rendering/vertex_buffer.cpp index 4929711..ef2b288 100644 --- a/src/rendering/vertex_buffer.cpp +++ b/src/rendering/vertex_buffer.cpp @@ -6,14 +6,14 @@ namespace Donut { - VertexBuffer* VertexBuffer::create(const void* data, uint32_t size) + Ref<VertexBuffer> VertexBuffer::create(const void* data, uint32_t size) { switch (Renderer::get_api()) { case RendererAPI::API::OpenGL: - return new OpenGLVertexBuffer(data, size); + return create_ref<OpenGLVertexBuffer>(data, size); case RendererAPI::API::Vulkan: - return new VulkanVertexBuffer((float*)data, size); + return create_ref<VulkanVertexBuffer>((float*)data, size); default: return nullptr; } diff --git a/src/rendering/vertex_buffer.h b/src/rendering/vertex_buffer.h index 77a76c9..39f43ba 100644 --- a/src/rendering/vertex_buffer.h +++ b/src/rendering/vertex_buffer.h @@ -1,5 +1,7 @@ #pragma once +#include "core/memory.h" + #include <cstdint> #include <vector> @@ -22,7 +24,7 @@ namespace Donut ~VertexBufferLayout() = default; template<typename T> - void push(uint32_t count) + void push(uint32_t count) { static_assert(false); } @@ -68,6 +70,6 @@ namespace Donut virtual const VertexBufferLayout& get_layout() const = 0; virtual void set_layout(const VertexBufferLayout& layout) = 0; - static VertexBuffer* create(const void* data, uint32_t size); + static Ref<VertexBuffer> create(const void* data, uint32_t size); }; }; diff --git a/src/states/simulation_state.cpp b/src/states/simulation_state.cpp index 7e9cb76..fc235ff 100644 --- a/src/states/simulation_state.cpp +++ b/src/states/simulation_state.cpp @@ -1,14 +1,18 @@ #include "simulation_state.h" #include "rendering/renderer.h" #include "core/application.h" +#include "core/window.h" +#include "core/event.h" #include <imgui.h> +#include <GLFW/glfw3.h> namespace Donut { void SimulationState::on_enter() { DONUT_INFO("Entering Simulation State"); + m_initialized = true; } void SimulationState::on_exit() @@ -18,23 +22,79 @@ namespace Donut void SimulationState::on_update(float delta_time) { + + m_engine.update_window_dimensions(); + m_engine.update_physics(delta_time); + + if (m_engine.get_camera().is_dragging()) + { + GLFWwindow* window = static_cast<GLFWwindow*>(Application::get().get_window().get_native_window()); + double xpos, ypos; + glfwGetCursorPos(window, &xpos, &ypos); + m_engine.get_camera().process_orbital_mouse_move(xpos, ypos); + } } void SimulationState::on_render() { - Renderer::set_clear_color({ 0.0f, 0.0f, 0.0f, 1.0f }); - Renderer::clear(); + if (!m_initialized) return; + + RenderCommand::set_clear_color(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); + RenderCommand::clear(); + + m_engine.generate_grid(m_engine.get_objects()); + + glm::mat4 view = glm::lookAt(m_engine.get_camera().get_orbital_position(), m_engine.get_camera().get_target(), glm::vec3(0,1,0)); + glm::mat4 proj = glm::perspective(glm::radians(60.0f), float(m_engine.get_width())/m_engine.get_height(), 1e9f, 1e14f); + glm::mat4 view_proj = proj * view; + + m_engine.draw_grid(view_proj); + + RenderCommand::set_viewport(0, 0, m_engine.get_width(), m_engine.get_height()); + m_engine.dispatch_compute(m_engine.get_camera()); + m_engine.draw_full_screen_quad(); } void SimulationState::on_event(Event& event) { + if (!m_initialized) return; + + if (event.get_event_type() == EventType::MouseButtonPressed) + { + MouseButtonPressedEvent& e = (MouseButtonPressedEvent&)event; + int button = e.get_mouse_button(); + + m_engine.get_camera().process_orbital_mouse_button(button, GLFW_PRESS, 0); + } + else if (event.get_event_type() == EventType::MouseButtonReleased) + { + MouseButtonReleasedEvent& e = (MouseButtonReleasedEvent&)event; + int button = e.get_mouse_button(); + m_engine.get_camera().process_orbital_mouse_button(button, GLFW_RELEASE, 0); + } + + if (event.get_event_type() == EventType::MouseScrolled) + { + MouseScrolledEvent& e = (MouseScrolledEvent&)event; + m_engine.get_camera().process_orbital_scroll(e.get_x_offset(), e.get_y_offset()); + } + + if (event.get_event_type() == EventType::KeyPressed) + { + KeyPressedEvent& e = (KeyPressedEvent&)event; + if (e.get_key_code() == GLFW_KEY_G) + { + m_engine.set_gravity(!m_engine.get_gravity()); + DONUT_INFO("Gravity turned {}", m_engine.get_gravity() ? "ON" : "OFF"); + } + } } void SimulationState::on_im_ui_render() { ImGui::Begin("Simulation Controls"); - ImGui::Text("Simulation State"); + ImGui::Text("Black Hole Simulation"); if (ImGui::Button("Back to Config")) Application::get().get_state_manager().switch_to_state("Config"); @@ -43,6 +103,33 @@ namespace Donut ImGui::Text("Simulation Info:"); ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate); ImGui::Text("Frame Time: %.3f ms", 1000.0f / ImGui::GetIO().Framerate); + ImGui::Text("Resolution: %dx%d", m_engine.get_width(), m_engine.get_height()); + ImGui::Text("Compute Resolution: %dx%d", m_engine.get_compute_width(), m_engine.get_compute_height()); + ImGui::Text("Objects: %zu", m_engine.get_objects().size()); + + ImGui::Separator(); + + ImGui::Text("Controls:"); + ImGui::Text("Left Mouse: Orbit camera"); + ImGui::Text("Scroll: Zoom in/out"); + ImGui::Text("G: Toggle gravity"); + ImGui::Text("Right Mouse: Enable gravity (hold)"); + + ImGui::Separator(); + + ImGui::Text("Physics:"); + bool gravity = m_engine.get_gravity(); + if (ImGui::Checkbox("Gravity Enabled", &gravity)) + m_engine.set_gravity(gravity); + + ImGui::Text("Camera:"); + ImGui::Text("Position: (%.2e, %.2e, %.2e)", + m_engine.get_camera().get_orbital_position().x, + m_engine.get_camera().get_orbital_position().y, + m_engine.get_camera().get_orbital_position().z); + ImGui::Text("Radius: %.2e", m_engine.get_camera().get_radius()); + ImGui::Text("Azimuth: %.2f", m_engine.get_camera().get_azimuth()); + ImGui::Text("Elevation: %.2f", m_engine.get_camera().get_elevation()); ImGui::End(); } diff --git a/src/states/simulation_state.h b/src/states/simulation_state.h index 4bb1c1b..68788e8 100644 --- a/src/states/simulation_state.h +++ b/src/states/simulation_state.h @@ -3,6 +3,8 @@ #include "core/state.h" #include "core/event.h" #include "core/log.h" +#include "engine/engine.h" +#include <vector> namespace Donut { @@ -18,5 +20,9 @@ namespace Donut void on_render() override; void on_im_ui_render() override; void on_event(Event& event) override; + + private: + Engine m_engine; + bool m_initialized = false; }; }; |
