710 lines
26 KiB
C++
710 lines
26 KiB
C++
#include <GL/glew.h>
|
||
#include <GLFW/glfw3.h>
|
||
#include <glm/glm.hpp>
|
||
#include <glm/gtc/matrix_transform.hpp>
|
||
#include <glm/gtc/type_ptr.hpp>
|
||
#include <vector>
|
||
#include <iostream>
|
||
#define _USE_MATH_DEFINES
|
||
#include <cmath>
|
||
#include <sstream>
|
||
#include <iomanip>
|
||
#include <cstring>
|
||
#include <chrono>
|
||
#include <fstream>
|
||
#include <sstream>
|
||
#ifndef M_PI
|
||
#define M_PI 3.14159265358979323846
|
||
#endif
|
||
using namespace glm;
|
||
using namespace std;
|
||
using Clock = std::chrono::high_resolution_clock;
|
||
|
||
// VARS
|
||
double lastPrintTime = 0.0;
|
||
int framesCount = 0;
|
||
double c = 299792458.0;
|
||
double G = 6.67430e-11;
|
||
struct Ray;
|
||
bool Gravity = false;
|
||
|
||
struct Camera {
|
||
// Center the camera orbit on the black hole at (0, 0, 0)
|
||
vec3 target = vec3(0.0f, 0.0f, 0.0f); // Always look at the black hole center
|
||
float radius = 6.34194e10f;
|
||
float minRadius = 1e10f, maxRadius = 1e12f;
|
||
|
||
float azimuth = 0.0f;
|
||
float elevation = M_PI / 2.0f;
|
||
|
||
float orbitSpeed = 0.01f;
|
||
float panSpeed = 0.01f;
|
||
double zoomSpeed = 25e9f;
|
||
|
||
bool dragging = false;
|
||
bool panning = false;
|
||
bool moving = false; // For compute shader optimization
|
||
double lastX = 0.0, lastY = 0.0;
|
||
|
||
// Calculate camera position in world space
|
||
vec3 position() const {
|
||
float clampedElevation = clamp(elevation, 0.01f, float(M_PI) - 0.01f);
|
||
// Orbit around (0,0,0) always
|
||
return vec3(
|
||
radius * sin(clampedElevation) * cos(azimuth),
|
||
radius * cos(clampedElevation),
|
||
radius * sin(clampedElevation) * sin(azimuth)
|
||
);
|
||
}
|
||
void update() {
|
||
// Always keep target at black hole center
|
||
target = vec3(0.0f, 0.0f, 0.0f);
|
||
if(dragging | panning) {
|
||
moving = true;
|
||
} else {
|
||
moving = false;
|
||
}
|
||
}
|
||
|
||
void processMouseMove(double x, double y) {
|
||
float dx = float(x - lastX);
|
||
float dy = float(y - lastY);
|
||
|
||
if (dragging && panning) {
|
||
// Pan: Shift + Left or Middle Mouse
|
||
// Disable panning to keep camera centered on black hole
|
||
}
|
||
else if (dragging && !panning) {
|
||
// Orbit: Left mouse only
|
||
azimuth += dx * orbitSpeed;
|
||
elevation -= dy * orbitSpeed;
|
||
elevation = clamp(elevation, 0.01f, float(M_PI) - 0.01f);
|
||
}
|
||
|
||
lastX = x;
|
||
lastY = y;
|
||
update();
|
||
}
|
||
void processMouseButton(int button, int action, int mods, GLFWwindow* win) {
|
||
if (button == GLFW_MOUSE_BUTTON_LEFT || button == GLFW_MOUSE_BUTTON_MIDDLE) {
|
||
if (action == GLFW_PRESS) {
|
||
dragging = true;
|
||
// Disable panning so camera always orbits center
|
||
panning = false;
|
||
glfwGetCursorPos(win, &lastX, &lastY);
|
||
} else if (action == GLFW_RELEASE) {
|
||
dragging = false;
|
||
panning = false;
|
||
}
|
||
}
|
||
if (button == GLFW_MOUSE_BUTTON_RIGHT) {
|
||
if (action == GLFW_PRESS) {
|
||
Gravity = true;
|
||
} else if (action == GLFW_RELEASE) {
|
||
Gravity = false;
|
||
}
|
||
}
|
||
}
|
||
void processScroll(double xoffset, double yoffset) {
|
||
radius -= yoffset * zoomSpeed;
|
||
radius = clamp(radius, minRadius, maxRadius);
|
||
update();
|
||
}
|
||
void processKey(int key, int scancode, int action, int mods) {
|
||
if (action == GLFW_PRESS && key == GLFW_KEY_G) {
|
||
Gravity = !Gravity;
|
||
cout << "[INFO] Gravity turned " << (Gravity ? "ON" : "OFF") << endl;
|
||
}
|
||
}
|
||
};
|
||
Camera camera;
|
||
|
||
struct BlackHole {
|
||
vec3 position;
|
||
double mass;
|
||
double radius;
|
||
double r_s;
|
||
|
||
BlackHole(vec3 pos, float m) : position(pos), mass(m) {r_s = 2.0 * G * mass / (c*c);}
|
||
bool Intercept(float px, float py, float pz) const {
|
||
double dx = double(px) - double(position.x);
|
||
double dy = double(py) - double(position.y);
|
||
double dz = double(pz) - double(position.z);
|
||
double dist2 = dx * dx + dy * dy + dz * dz;
|
||
return dist2 < r_s * r_s;
|
||
}
|
||
};
|
||
BlackHole SagA(vec3(0.0f, 0.0f, 0.0f), 8.54e36); // Sagittarius A black hole
|
||
struct ObjectData {
|
||
vec4 posRadius; // xyz = position, w = radius
|
||
vec4 color; // rgb = color, a = unused
|
||
float mass;
|
||
vec3 velocity = vec3(0.0f, 0.0f, 0.0f); // Initial velocity
|
||
};
|
||
vector<ObjectData> objects = {
|
||
{ vec4(4e11f, 0.0f, 0.0f, 4e10f) , vec4(1,1,0,1), 1.98892e30 },
|
||
{ vec4(0.0f, 0.0f, 4e11f, 4e10f) , vec4(1,0,0,1), 1.98892e30 },
|
||
{ vec4(0.0f, 0.0f, 0.0f, SagA.r_s) , vec4(0,0,0,1), SagA.mass },
|
||
//{ vec4(6e10f, 0.0f, 0.0f, 5e10f), vec4(0,1,0,1) }
|
||
};
|
||
|
||
struct Engine {
|
||
GLuint gridShaderProgram;
|
||
// -- Quad & Texture render -- //
|
||
GLFWwindow* window;
|
||
GLuint quadVAO;
|
||
GLuint texture;
|
||
GLuint shaderProgram;
|
||
GLuint computeProgram = 0;
|
||
// -- UBOs -- //
|
||
GLuint cameraUBO = 0;
|
||
GLuint diskUBO = 0;
|
||
GLuint objectsUBO = 0;
|
||
// -- grid mess vars -- //
|
||
GLuint gridVAO = 0;
|
||
GLuint gridVBO = 0;
|
||
GLuint gridEBO = 0;
|
||
int gridIndexCount = 0;
|
||
|
||
int WIDTH = 800; // Window width
|
||
int HEIGHT = 600; // Window height
|
||
int COMPUTE_WIDTH = 200; // Compute resolution width
|
||
int COMPUTE_HEIGHT = 150; // Compute resolution height
|
||
float width = 100000000000.0f; // Width of the viewport in meters
|
||
float height = 75000000000.0f; // Height of the viewport in meters
|
||
|
||
Engine() {
|
||
if (!glfwInit()) {
|
||
cerr << "GLFW init failed\n";
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
|
||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
|
||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||
window = glfwCreateWindow(WIDTH, HEIGHT, "Black Hole", nullptr, nullptr);
|
||
if (!window) {
|
||
cerr << "Failed to create GLFW window\n";
|
||
glfwTerminate();
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
glfwMakeContextCurrent(window);
|
||
glewExperimental = GL_TRUE;
|
||
GLenum glewErr = glewInit();
|
||
if (glewErr != GLEW_OK) {
|
||
cerr << "Failed to initialize GLEW: "
|
||
<< (const char*)glewGetErrorString(glewErr)
|
||
<< "\n";
|
||
glfwTerminate();
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
cout << "OpenGL " << glGetString(GL_VERSION) << "\n";
|
||
this->shaderProgram = CreateShaderProgram();
|
||
gridShaderProgram = CreateShaderProgram("grid.vert", "grid.frag");
|
||
|
||
computeProgram = CreateComputeProgram("geodesic.comp");
|
||
glGenBuffers(1, &cameraUBO);
|
||
glBindBuffer(GL_UNIFORM_BUFFER, cameraUBO);
|
||
glBufferData(GL_UNIFORM_BUFFER, 128, nullptr, GL_DYNAMIC_DRAW); // alloc ~128 bytes
|
||
glBindBufferBase(GL_UNIFORM_BUFFER, 1, cameraUBO); // binding = 1 matches shader
|
||
|
||
glGenBuffers(1, &diskUBO);
|
||
glBindBuffer(GL_UNIFORM_BUFFER, diskUBO);
|
||
glBufferData(GL_UNIFORM_BUFFER, sizeof(float) * 4, nullptr, GL_DYNAMIC_DRAW); // 3 values + 1 padding
|
||
glBindBufferBase(GL_UNIFORM_BUFFER, 2, diskUBO); // binding = 2 matches compute shader
|
||
|
||
glGenBuffers(1, &objectsUBO);
|
||
glBindBuffer(GL_UNIFORM_BUFFER, objectsUBO);
|
||
// allocate space for 16 objects:
|
||
// sizeof(int) + padding + 16×(vec4 posRadius + vec4 color)
|
||
GLsizeiptr objUBOSize = sizeof(int) + 3 * sizeof(float)
|
||
+ 16 * (sizeof(vec4) + sizeof(vec4))
|
||
+ 16 * sizeof(float); // 16 floats for mass
|
||
glBufferData(GL_UNIFORM_BUFFER, objUBOSize, nullptr, GL_DYNAMIC_DRAW);
|
||
glBindBufferBase(GL_UNIFORM_BUFFER, 3, objectsUBO); // binding = 3 matches shader
|
||
|
||
auto result = QuadVAO();
|
||
this->quadVAO = result[0];
|
||
this->texture = result[1];
|
||
}
|
||
void generateGrid(const vector<ObjectData>& objects) {
|
||
const int gridSize = 25;
|
||
const float spacing = 1e10f; // tweak this
|
||
|
||
vector<vec3> vertices;
|
||
vector<GLuint> indices;
|
||
|
||
for (int z = 0; z <= gridSize; ++z) {
|
||
for (int x = 0; x <= gridSize; ++x) {
|
||
float worldX = (x - gridSize / 2) * spacing;
|
||
float worldZ = (z - gridSize / 2) * spacing;
|
||
|
||
float y = 0.0f;
|
||
|
||
// ✅ Warp grid using Schwarzschild geometry
|
||
for (const auto& obj : objects) {
|
||
vec3 objPos = vec3(obj.posRadius);
|
||
double mass = obj.mass;
|
||
double radius = obj.posRadius.w;
|
||
|
||
double r_s = 2.0 * G * mass / (c * c);
|
||
double dx = worldX - objPos.x;
|
||
double dz = worldZ - objPos.z;
|
||
double dist = sqrt(dx * dx + dz * dz);
|
||
|
||
// prevent sqrt of negative or divide-by-zero (inside or at the black hole center)
|
||
if (dist > r_s) {
|
||
double deltaY = 2.0 * sqrt(r_s * (dist - r_s));
|
||
y += static_cast<float>(deltaY) - 3e10f;
|
||
} else {
|
||
// 🔴 For points inside or at r_s: make it dip down sharply
|
||
y += 2.0f * static_cast<float>(sqrt(r_s * r_s)) - 3e10f; // or add a deep pit
|
||
}
|
||
}
|
||
|
||
vertices.emplace_back(worldX, y, worldZ);
|
||
}
|
||
}
|
||
|
||
// 🧩 Add indices for GL_LINE rendering
|
||
for (int z = 0; z < gridSize; ++z) {
|
||
for (int x = 0; x < gridSize; ++x) {
|
||
int i = z * (gridSize + 1) + x;
|
||
indices.push_back(i);
|
||
indices.push_back(i + 1);
|
||
|
||
indices.push_back(i);
|
||
indices.push_back(i + gridSize + 1);
|
||
}
|
||
}
|
||
|
||
// 🔌 Upload to GPU
|
||
if (gridVAO == 0) glGenVertexArrays(1, &gridVAO);
|
||
if (gridVBO == 0) glGenBuffers(1, &gridVBO);
|
||
if (gridEBO == 0) glGenBuffers(1, &gridEBO);
|
||
|
||
glBindVertexArray(gridVAO);
|
||
|
||
glBindBuffer(GL_ARRAY_BUFFER, gridVBO);
|
||
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(vec3), vertices.data(), GL_DYNAMIC_DRAW);
|
||
|
||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gridEBO);
|
||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), indices.data(), GL_STATIC_DRAW);
|
||
|
||
glEnableVertexAttribArray(0); // location = 0
|
||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(vec3), (void*)0);
|
||
|
||
gridIndexCount = indices.size();
|
||
|
||
glBindVertexArray(0);
|
||
}
|
||
void drawGrid(const mat4& viewProj) {
|
||
glUseProgram(gridShaderProgram);
|
||
glUniformMatrix4fv(glGetUniformLocation(gridShaderProgram, "viewProj"),
|
||
1, GL_FALSE, glm::value_ptr(viewProj));
|
||
glBindVertexArray(gridVAO);
|
||
|
||
glDisable(GL_DEPTH_TEST);
|
||
glEnable(GL_BLEND);
|
||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||
|
||
glDrawElements(GL_LINES, gridIndexCount, GL_UNSIGNED_INT, 0);
|
||
|
||
glBindVertexArray(0);
|
||
glEnable(GL_DEPTH_TEST);
|
||
}
|
||
void drawFullScreenQuad() {
|
||
glUseProgram(shaderProgram); // fragment + vertex shader
|
||
glBindVertexArray(quadVAO);
|
||
|
||
glActiveTexture(GL_TEXTURE0);
|
||
glBindTexture(GL_TEXTURE_2D, texture);
|
||
glUniform1i(glGetUniformLocation(shaderProgram, "screenTexture"), 0);
|
||
|
||
glDisable(GL_DEPTH_TEST); // draw as background
|
||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 6); // 2 triangles
|
||
glEnable(GL_DEPTH_TEST);
|
||
}
|
||
GLuint CreateShaderProgram(){
|
||
const char* vertexShaderSource = R"(
|
||
#version 330 core
|
||
layout (location = 0) in vec2 aPos; // Changed to vec2
|
||
layout (location = 1) in vec2 aTexCoord;
|
||
out vec2 TexCoord;
|
||
void main() {
|
||
gl_Position = vec4(aPos, 0.0, 1.0); // Explicit z=0
|
||
TexCoord = aTexCoord;
|
||
})";
|
||
|
||
const char* fragmentShaderSource = R"(
|
||
#version 330 core
|
||
in vec2 TexCoord;
|
||
out vec4 FragColor;
|
||
uniform sampler2D screenTexture;
|
||
void main() {
|
||
FragColor = texture(screenTexture, TexCoord);
|
||
})";
|
||
|
||
// vertex shader
|
||
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||
glShaderSource(vertexShader, 1, &vertexShaderSource, nullptr);
|
||
glCompileShader(vertexShader);
|
||
|
||
// fragment shader
|
||
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
|
||
glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);
|
||
glCompileShader(fragmentShader);
|
||
|
||
GLuint shaderProgram = glCreateProgram();
|
||
glAttachShader(shaderProgram, vertexShader);
|
||
glAttachShader(shaderProgram, fragmentShader);
|
||
glLinkProgram(shaderProgram);
|
||
|
||
glDeleteShader(vertexShader);
|
||
glDeleteShader(fragmentShader);
|
||
|
||
return shaderProgram;
|
||
};
|
||
GLuint CreateShaderProgram(const char* vertPath, const char* fragPath) {
|
||
auto loadShader = [](const char* path, GLenum type) -> GLuint {
|
||
std::ifstream in(path);
|
||
if (!in.is_open()) {
|
||
std::cerr << "Failed to open shader: " << path << "\n";
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
std::stringstream ss;
|
||
ss << in.rdbuf();
|
||
std::string srcStr = ss.str();
|
||
const char* src = srcStr.c_str();
|
||
|
||
GLuint shader = glCreateShader(type);
|
||
glShaderSource(shader, 1, &src, nullptr);
|
||
glCompileShader(shader);
|
||
|
||
GLint success;
|
||
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
||
if (!success) {
|
||
GLint logLen;
|
||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLen);
|
||
std::vector<char> log(logLen);
|
||
glGetShaderInfoLog(shader, logLen, nullptr, log.data());
|
||
std::cerr << "Shader compile error (" << path << "):\n" << log.data() << "\n";
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
return shader;
|
||
};
|
||
|
||
GLuint vertShader = loadShader(vertPath, GL_VERTEX_SHADER);
|
||
GLuint fragShader = loadShader(fragPath, GL_FRAGMENT_SHADER);
|
||
|
||
GLuint program = glCreateProgram();
|
||
glAttachShader(program, vertShader);
|
||
glAttachShader(program, fragShader);
|
||
glLinkProgram(program);
|
||
|
||
GLint linkSuccess;
|
||
glGetProgramiv(program, GL_LINK_STATUS, &linkSuccess);
|
||
if (!linkSuccess) {
|
||
GLint logLen;
|
||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLen);
|
||
std::vector<char> log(logLen);
|
||
glGetProgramInfoLog(program, logLen, nullptr, log.data());
|
||
std::cerr << "Shader link error:\n" << log.data() << "\n";
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
|
||
glDeleteShader(vertShader);
|
||
glDeleteShader(fragShader);
|
||
|
||
return program;
|
||
}
|
||
GLuint CreateComputeProgram(const char* path) {
|
||
// 1) read GLSL source
|
||
std::ifstream in(path);
|
||
if(!in.is_open()) {
|
||
std::cerr << "Failed to open compute shader: " << path << "\n";
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
std::stringstream ss;
|
||
ss << in.rdbuf();
|
||
std::string srcStr = ss.str();
|
||
const char* src = srcStr.c_str();
|
||
|
||
// 2) compile
|
||
GLuint cs = glCreateShader(GL_COMPUTE_SHADER);
|
||
glShaderSource(cs, 1, &src, nullptr);
|
||
glCompileShader(cs);
|
||
GLint ok;
|
||
glGetShaderiv(cs, GL_COMPILE_STATUS, &ok);
|
||
if(!ok) {
|
||
GLint logLen;
|
||
glGetShaderiv(cs, GL_INFO_LOG_LENGTH, &logLen);
|
||
std::vector<char> log(logLen);
|
||
glGetShaderInfoLog(cs, logLen, nullptr, log.data());
|
||
std::cerr << "Compute shader compile error:\n" << log.data() << "\n";
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
|
||
// 3) link
|
||
GLuint prog = glCreateProgram();
|
||
glAttachShader(prog, cs);
|
||
glLinkProgram(prog);
|
||
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
|
||
if(!ok) {
|
||
GLint logLen;
|
||
glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLen);
|
||
std::vector<char> log(logLen);
|
||
glGetProgramInfoLog(prog, logLen, nullptr, log.data());
|
||
std::cerr << "Compute shader link error:\n" << log.data() << "\n";
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
|
||
glDeleteShader(cs);
|
||
return prog;
|
||
}
|
||
void dispatchCompute(const Camera& cam) {
|
||
// determine target compute‐res
|
||
int cw = cam.moving ? COMPUTE_WIDTH : 200;
|
||
int ch = cam.moving ? COMPUTE_HEIGHT : 150;
|
||
|
||
// 1) reallocate the texture if needed
|
||
glBindTexture(GL_TEXTURE_2D, texture);
|
||
glTexImage2D(GL_TEXTURE_2D,
|
||
0, // mip
|
||
GL_RGBA8, // internal format
|
||
cw, // width
|
||
ch, // height
|
||
0, GL_RGBA,
|
||
GL_UNSIGNED_BYTE,
|
||
nullptr);
|
||
|
||
// 2) bind compute program & UBOs
|
||
glUseProgram(computeProgram);
|
||
uploadCameraUBO(cam);
|
||
uploadDiskUBO();
|
||
uploadObjectsUBO(objects);
|
||
|
||
// 3) bind it as image unit 0
|
||
glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8);
|
||
|
||
// 4) dispatch grid
|
||
GLuint groupsX = (GLuint)std::ceil(cw / 16.0f);
|
||
GLuint groupsY = (GLuint)std::ceil(ch / 16.0f);
|
||
glDispatchCompute(groupsX, groupsY, 1);
|
||
|
||
// 5) sync
|
||
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
|
||
}
|
||
void uploadCameraUBO(const Camera& cam) {
|
||
struct UBOData {
|
||
vec3 pos; float _pad0;
|
||
vec3 right; float _pad1;
|
||
vec3 up; float _pad2;
|
||
vec3 forward; float _pad3;
|
||
float tanHalfFov;
|
||
float aspect;
|
||
bool moving;
|
||
int _pad4;
|
||
} data;
|
||
vec3 fwd = normalize(cam.target - cam.position());
|
||
vec3 up = vec3(0, 1, 0); // y axis is up, so disk is in x-z plane
|
||
vec3 right = normalize(cross(fwd, up));
|
||
up = cross(right, fwd);
|
||
|
||
data.pos = cam.position();
|
||
data.right = right;
|
||
data.up = up;
|
||
data.forward = fwd;
|
||
data.tanHalfFov = tan(radians(60.0f * 0.5f));
|
||
data.aspect = float(WIDTH) / float(HEIGHT);
|
||
data.moving = cam.dragging || cam.panning;
|
||
|
||
glBindBuffer(GL_UNIFORM_BUFFER, cameraUBO);
|
||
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(UBOData), &data);
|
||
}
|
||
void uploadObjectsUBO(const vector<ObjectData>& objs) {
|
||
struct UBOData {
|
||
int numObjects;
|
||
float _pad0, _pad1, _pad2; // <-- pad out to 16 bytes
|
||
vec4 posRadius[16];
|
||
vec4 color[16];
|
||
float mass[16];
|
||
} data;
|
||
|
||
size_t count = std::min(objs.size(), size_t(16));
|
||
data.numObjects = static_cast<int>(count);
|
||
|
||
for (size_t i = 0; i < count; ++i) {
|
||
data.posRadius[i] = objs[i].posRadius;
|
||
data.color[i] = objs[i].color;
|
||
data.mass[i] = objs[i].mass;
|
||
}
|
||
|
||
// Upload
|
||
glBindBuffer(GL_UNIFORM_BUFFER, objectsUBO);
|
||
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(data), &data);
|
||
}
|
||
void uploadDiskUBO() {
|
||
// disk
|
||
float r1 = SagA.r_s * 2.2f; // inner radius just outside the event horizon
|
||
float r2 = SagA.r_s * 5.2f; // outer radius of the disk
|
||
float num = 2.0; // number of rays
|
||
float thickness = 1e9f; // padding for std140 alignment
|
||
float diskData[4] = { r1, r2, num, thickness };
|
||
|
||
glBindBuffer(GL_UNIFORM_BUFFER, diskUBO);
|
||
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(diskData), diskData);
|
||
}
|
||
|
||
vector<GLuint> QuadVAO(){
|
||
float quadVertices[] = {
|
||
// positions // texCoords
|
||
-1.0f, 1.0f, 0.0f, 1.0f, // top left
|
||
-1.0f, -1.0f, 0.0f, 0.0f, // bottom left
|
||
1.0f, -1.0f, 1.0f, 0.0f, // bottom right
|
||
|
||
-1.0f, 1.0f, 0.0f, 1.0f, // top left
|
||
1.0f, -1.0f, 1.0f, 0.0f, // bottom right
|
||
1.0f, 1.0f, 1.0f, 1.0f // top right
|
||
};
|
||
|
||
GLuint VAO, VBO;
|
||
glGenVertexArrays(1, &VAO);
|
||
glGenBuffers(1, &VBO);
|
||
|
||
glBindVertexArray(VAO);
|
||
glBindBuffer(GL_ARRAY_BUFFER, VBO);
|
||
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices, GL_STATIC_DRAW);
|
||
|
||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
|
||
glEnableVertexAttribArray(0);
|
||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
|
||
glEnableVertexAttribArray(1);
|
||
|
||
GLuint texture;
|
||
glGenTextures(1, &texture);
|
||
glBindTexture(GL_TEXTURE_2D, texture);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||
glBindTexture(GL_TEXTURE_2D, texture);
|
||
glTexImage2D(GL_TEXTURE_2D,
|
||
0, // mip
|
||
GL_RGBA8, // internal format
|
||
COMPUTE_WIDTH,
|
||
COMPUTE_HEIGHT,
|
||
0,
|
||
GL_RGBA,
|
||
GL_UNSIGNED_BYTE,
|
||
nullptr);
|
||
vector<GLuint> VAOtexture = {VAO, texture};
|
||
return VAOtexture;
|
||
}
|
||
void renderScene() {
|
||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||
glUseProgram(shaderProgram);
|
||
glBindVertexArray(quadVAO);
|
||
// make sure your fragment shader samples from texture unit 0:
|
||
glActiveTexture(GL_TEXTURE0);
|
||
glBindTexture(GL_TEXTURE_2D, texture);
|
||
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||
glfwSwapBuffers(window);
|
||
glfwPollEvents();
|
||
};
|
||
};
|
||
Engine engine;
|
||
void setupCameraCallbacks(GLFWwindow* window) {
|
||
glfwSetWindowUserPointer(window, &camera);
|
||
|
||
glfwSetMouseButtonCallback(window, [](GLFWwindow* win, int button, int action, int mods) {
|
||
Camera* cam = (Camera*)glfwGetWindowUserPointer(win);
|
||
cam->processMouseButton(button, action, mods, win);
|
||
});
|
||
|
||
glfwSetCursorPosCallback(window, [](GLFWwindow* win, double x, double y) {
|
||
Camera* cam = (Camera*)glfwGetWindowUserPointer(win);
|
||
cam->processMouseMove(x, y);
|
||
});
|
||
|
||
glfwSetScrollCallback(window, [](GLFWwindow* win, double xoffset, double yoffset) {
|
||
Camera* cam = (Camera*)glfwGetWindowUserPointer(win);
|
||
cam->processScroll(xoffset, yoffset);
|
||
});
|
||
|
||
glfwSetKeyCallback(window, [](GLFWwindow* win, int key, int scancode, int action, int mods) {
|
||
Camera* cam = (Camera*)glfwGetWindowUserPointer(win);
|
||
cam->processKey(key, scancode, action, mods);
|
||
});
|
||
}
|
||
|
||
|
||
// -- MAIN -- //
|
||
int main() {
|
||
setupCameraCallbacks(engine.window);
|
||
vector<unsigned char> pixels(engine.WIDTH * engine.HEIGHT * 3);
|
||
|
||
auto t0 = Clock::now();
|
||
lastPrintTime = chrono::duration<double>(t0.time_since_epoch()).count();
|
||
|
||
double lastTime = glfwGetTime();
|
||
int renderW = 800, renderH = 600, numSteps = 80000;
|
||
while (!glfwWindowShouldClose(engine.window)) {
|
||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // optional, but good practice
|
||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||
|
||
double now = glfwGetTime();
|
||
double dt = now - lastTime; // seconds since last frame
|
||
lastTime = now;
|
||
|
||
// Gravity
|
||
for (auto& obj : objects) {
|
||
for (auto& obj2 : objects) {
|
||
if (&obj == &obj2) continue; // skip self-interaction
|
||
float dx = obj2.posRadius.x - obj.posRadius.x;
|
||
float dy = obj2.posRadius.y - obj.posRadius.y;
|
||
float dz = obj2.posRadius.z - obj.posRadius.z;
|
||
float distance = sqrt(dx * dx + dy * dy + dz * dz);
|
||
if (distance > 0) {
|
||
vector<double> direction = {dx / distance, dy / distance, dz / distance};
|
||
//distance *= 1000;
|
||
double Gforce = (G * obj.mass * obj2.mass) / (distance * distance);
|
||
|
||
double acc1 = Gforce / obj.mass;
|
||
std::vector<double> acc = {direction[0] * acc1, direction[1] * acc1, direction[2] * acc1};
|
||
if (Gravity) {
|
||
obj.velocity.x += acc[0];
|
||
obj.velocity.y += acc[1];
|
||
obj.velocity.z += acc[2];
|
||
|
||
obj.posRadius.x += obj.velocity.x;
|
||
obj.posRadius.y += obj.velocity.y;
|
||
obj.posRadius.z += obj.velocity.z;
|
||
cout << "velocity: " <<obj.velocity.x<<", " <<obj.velocity.y<<", " <<obj.velocity.z<<endl;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// ---------- GRID ------------- //
|
||
// 2) rebuild grid mesh on CPU
|
||
engine.generateGrid(objects);
|
||
// 5) overlay the bent grid
|
||
mat4 view = lookAt(camera.position(), camera.target, vec3(0,1,0));
|
||
mat4 proj = perspective(radians(60.0f), float(engine.COMPUTE_WIDTH)/engine.COMPUTE_HEIGHT, 1e9f, 1e14f);
|
||
mat4 viewProj = proj * view;
|
||
engine.drawGrid(viewProj);
|
||
|
||
// ---------- RUN RAYTRACER ------------- //
|
||
glViewport(0, 0, engine.WIDTH, engine.HEIGHT);
|
||
engine.dispatchCompute(camera);
|
||
engine.drawFullScreenQuad();
|
||
|
||
// 6) present to screen
|
||
glfwSwapBuffers(engine.window);
|
||
glfwPollEvents();
|
||
}
|
||
|
||
glfwDestroyWindow(engine.window);
|
||
glfwTerminate();
|
||
return 0;
|
||
}
|