diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6a51eb9 --- /dev/null +++ b/Makefile @@ -0,0 +1,61 @@ +CXX ?= g++ +CXXFLAGS ?= -std=c++17 -O2 -Wall -Wextra +LDFLAGS ?= + +SRC_DIR := src +BIN_DIR := bin + +ATOM_SRC := $(SRC_DIR)/atom.cpp +REALTIME_SRC := $(SRC_DIR)/atom_realtime.cpp +RAYTRACER_SRC := $(SRC_DIR)/atom_raytracer.cpp +WAVE2D_SRC := $(SRC_DIR)/wave_atom_2d.cpp + +UNAME_S := $(shell uname -s) + +ifeq ($(OS),Windows_NT) + EXE := .exe + LDLIBS := -lglfw3 -lglew32 -lopengl32 -lgdi32 +else ifeq ($(UNAME_S),Darwin) + EXE := + BREW_PREFIX := $(shell command -v brew >/dev/null 2>&1 && brew --prefix) + ifneq ($(strip $(BREW_PREFIX)),) + CXXFLAGS += -I$(BREW_PREFIX)/include + LDFLAGS += -L$(BREW_PREFIX)/lib + endif + LDLIBS := -lglfw -lGLEW -framework OpenGL +else + EXE := + LDLIBS := -lglfw -lGLEW -lGL -lGLU -ldl -lpthread -lm +endif + +ATOM_BIN := $(BIN_DIR)/atom$(EXE) +REALTIME_BIN := $(BIN_DIR)/atom_realtime$(EXE) +RAYTRACER_BIN := $(BIN_DIR)/atom_raytracer$(EXE) +WAVE2D_BIN := $(BIN_DIR)/wave_atom_2d$(EXE) + +.PHONY: all build atom realtime raytracer wave2d clean + +all: build +build: atom realtime raytracer wave2d + +$(BIN_DIR): + mkdir -p $(BIN_DIR) + +atom: $(ATOM_BIN) +$(ATOM_BIN): $(ATOM_SRC) | $(BIN_DIR) + $(CXX) $(CXXFLAGS) $< -o $@ $(LDFLAGS) $(LDLIBS) + +realtime: $(REALTIME_BIN) +$(REALTIME_BIN): $(REALTIME_SRC) | $(BIN_DIR) + $(CXX) $(CXXFLAGS) $< -o $@ $(LDFLAGS) $(LDLIBS) + +raytracer: $(RAYTRACER_BIN) +$(RAYTRACER_BIN): $(RAYTRACER_SRC) | $(BIN_DIR) + $(CXX) $(CXXFLAGS) $< -o $@ $(LDFLAGS) $(LDLIBS) + +wave2d: $(WAVE2D_BIN) +$(WAVE2D_BIN): $(WAVE2D_SRC) | $(BIN_DIR) + $(CXX) $(CXXFLAGS) $< -o $@ $(LDFLAGS) $(LDLIBS) + +clean: + rm -f $(ATOM_BIN) $(REALTIME_BIN) $(RAYTRACER_BIN) $(WAVE2D_BIN) diff --git a/bin/atom b/bin/atom new file mode 100755 index 0000000..afdc2e0 Binary files /dev/null and b/bin/atom differ diff --git a/bin/atom_raytracer b/bin/atom_raytracer new file mode 100755 index 0000000..9087546 Binary files /dev/null and b/bin/atom_raytracer differ diff --git a/bin/atom_realtime b/bin/atom_realtime new file mode 100755 index 0000000..bd329f1 Binary files /dev/null and b/bin/atom_realtime differ diff --git a/bin/wave_atom_2d b/bin/wave_atom_2d new file mode 100755 index 0000000..bfe7630 Binary files /dev/null and b/bin/wave_atom_2d differ diff --git a/src/atom_raytracer.cpp b/src/atom_raytracer.cpp index 47c7bd4..81375a5 100644 --- a/src/atom_raytracer.cpp +++ b/src/atom_raytracer.cpp @@ -248,7 +248,7 @@ vec4 inferno2(double r, double theta, double phi, int n, int l, int m) double t = log10(intensity + 1e-12) + 12.0; t /= 12.0; - t = clamp(t, 0.0, 1.0); + t = glm::clamp(t, 0.0, 1.0); // --- inferno-style ramp --- float rC = smoothstep(0.15f, 1.0f, static_cast(t)); @@ -376,7 +376,7 @@ struct Camera { double lastX = 0.0, lastY = 0.0; vec3 position() const { - float clampedElevation = clamp(elevation, 0.01f, float(M_PI) - 0.01f); + float clampedElevation = glm::clamp(elevation, 0.01f, float(M_PI) - 0.01f); return vec3( radius * sin(clampedElevation) * cos(azimuth), radius * cos(clampedElevation), @@ -787,4 +787,4 @@ int main () { glfwDestroyWindow(engine.window); glfwTerminate(); return 0; -} \ No newline at end of file +} diff --git a/src/atom_realtime.cpp b/src/atom_realtime.cpp index 98995a2..a29377d 100644 --- a/src/atom_realtime.cpp +++ b/src/atom_realtime.cpp @@ -1,6 +1,8 @@ #include #include +#ifndef __APPLE__ #include +#endif #include #include #include @@ -15,6 +17,11 @@ #include #include #include +#include +#include +#include +#include +#include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif @@ -31,6 +38,143 @@ const double zmSpeed = 10.0; // --- Global quantum numbers --- int n = 2, l = 1, m = 0, N = 100000; +struct QuantumCombo { + int n; + int l; + int m; +}; + +vector quantumCombos; +int currentComboIndex = -1; +constexpr int selectorMaxN = 7; + +struct ElementQuantum { + int atomicNumber; + string name; + int n; + int l; + int m; +}; + +vector elementMappings; +int currentElementIndex = -1; + +int magneticQuantumForLastElectron(int lValue, int electronInSubshell) { + const int orbitals = 2 * lValue + 1; + if (electronInSubshell <= orbitals) { + return -lValue + (electronInSubshell - 1); + } + + return -lValue + (electronInSubshell - orbitals - 1); +} + +QuantumCombo quantumFromAtomicNumber(int atomicNumber) { + static const vector> orbitalOrder = { + {1, 0}, {2, 0}, {2, 1}, {3, 0}, {3, 1}, {4, 0}, {3, 2}, {4, 1}, {5, 0}, {4, 2}, + {5, 1}, {6, 0}, {4, 3}, {5, 2}, {6, 1}, {7, 0}, {5, 3}, {6, 2}, {7, 1}, {8, 0} + }; + + int remainingElectrons = atomicNumber; + + for (const auto& orbital : orbitalOrder) { + const int nValue = orbital.first; + const int lValue = orbital.second; + const int capacity = 2 * (2 * lValue + 1); + const int usedHere = std::min(remainingElectrons, capacity); + + if (remainingElectrons <= capacity) { + return { + nValue, + lValue, + magneticQuantumForLastElectron(lValue, usedHere) + }; + } + + remainingElectrons -= usedHere; + } + + return {1, 0, 0}; +} + +void buildElementMappings() { + static const array elementNames = {{ + "Hydrogen", "Helium", "Lithium", "Beryllium", "Boron", "Carbon", "Nitrogen", "Oxygen", "Fluorine", "Neon", + "Sodium", "Magnesium", "Aluminum", "Silicon", "Phosphorus", "Sulfur", "Chlorine", "Argon", "Potassium", "Calcium", + "Scandium", "Titanium", "Vanadium", "Chromium", "Manganese", "Iron", "Cobalt", "Nickel", "Copper", "Zinc", + "Gallium", "Germanium", "Arsenic", "Selenium", "Bromine", "Krypton", "Rubidium", "Strontium", "Yttrium", "Zirconium", + "Niobium", "Molybdenum", "Technetium", "Ruthenium", "Rhodium", "Palladium", "Silver", "Cadmium", "Indium", "Tin", + "Antimony", "Tellurium", "Iodine", "Xenon", "Cesium", "Barium", "Lanthanum", "Cerium", "Praseodymium", "Neodymium", + "Promethium", "Samarium", "Europium", "Gadolinium", "Terbium", "Dysprosium", "Holmium", "Erbium", "Thulium", "Ytterbium", + "Lutetium", "Hafnium", "Tantalum", "Tungsten", "Rhenium", "Osmium", "Iridium", "Platinum", "Gold", "Mercury", + "Thallium", "Lead", "Bismuth", "Polonium", "Astatine", "Radon", "Francium", "Radium", "Actinium", "Thorium", + "Protactinium", "Uranium", "Neptunium", "Plutonium", "Americium", "Curium", "Berkelium", "Californium", "Einsteinium", "Fermium", + "Mendelevium", "Nobelium", "Lawrencium", "Rutherfordium", "Dubnium", "Seaborgium", "Bohrium", "Hassium", "Meitnerium", "Darmstadtium", + "Roentgenium", "Copernicium", "Nihonium", "Flerovium", "Moscovium", "Livermorium", "Tennessine", "Oganesson" + }}; + + elementMappings.clear(); + elementMappings.reserve(elementNames.size()); + + for (size_t i = 0; i < elementNames.size(); ++i) { + const int atomicNumber = static_cast(i) + 1; + const QuantumCombo combo = quantumFromAtomicNumber(atomicNumber); + elementMappings.push_back({ + atomicNumber, + elementNames[i], + combo.n, + combo.l, + combo.m + }); + } +} + +int findElementIndexByQuantum(int nValue, int lValue, int mValue) { + for (size_t i = 0; i < elementMappings.size(); ++i) { + const ElementQuantum& element = elementMappings[i]; + if (element.n == nValue && element.l == lValue && element.m == mValue) { + return static_cast(i); + } + } + return -1; +} + +void syncElementSelectionFromQuantum() { + currentElementIndex = findElementIndexByQuantum(n, l, m); +} + +void setElementByIndex(int elementIndex) { + if (elementMappings.empty()) return; + const int total = static_cast(elementMappings.size()); + + currentElementIndex = elementIndex % total; + if (currentElementIndex < 0) currentElementIndex += total; + + const ElementQuantum& element = elementMappings[currentElementIndex]; + n = element.n; + l = element.l; + m = element.m; +} + +void stepElementSelection(int direction) { + if (elementMappings.empty()) return; + + if (currentElementIndex < 0) { + syncElementSelectionFromQuantum(); + if (currentElementIndex < 0) currentElementIndex = 0; + } + + setElementByIndex(currentElementIndex + direction); +} + +string currentElementLabel() { + if (currentElementIndex >= 0 && currentElementIndex < static_cast(elementMappings.size())) { + const ElementQuantum& element = elementMappings[currentElementIndex]; + return to_string(element.atomicNumber) + " " + element.name; + } + + return "Custom Orbital"; +} + // ================= Physics Sampling ================= // struct Particle { vec3 pos; @@ -51,8 +195,10 @@ double sampleR(int n, int l, mt19937& gen) { static vector cdf; static bool built = false; + static int cachedN = -1; + static int cachedL = -1; - if (!built) { + if (!built || cachedN != n || cachedL != l) { cdf.resize(N); double dr = rMax / (N - 1); double sum = 0.0; @@ -87,6 +233,8 @@ double sampleR(int n, int l, mt19937& gen) { for (double& v : cdf) v /= sum; built = true; + cachedN = n; + cachedL = l; } uniform_real_distribution dis(0.0, 1.0); @@ -100,8 +248,11 @@ double sampleTheta(int l, int m, mt19937& gen) { const int N = 2048; static vector cdf; static bool built = false; + static int cachedL = -1; + static int cachedAbsM = -1; + const int absM = abs(m); - if (!built) { + if (!built || cachedL != l || cachedAbsM != absM) { cdf.resize(N); double dtheta = M_PI / (N - 1); double sum = 0.0; @@ -112,27 +263,27 @@ double sampleTheta(int l, int m, mt19937& gen) { // Associated Legendre P_l^m(x) double Pmm = 1.0; - if (m > 0) { + if (absM > 0) { double somx2 = sqrt((1.0 - x) * (1.0 + x)); double fact = 1.0; - for (int j = 1; j <= m; ++j) { + for (int j = 1; j <= absM; ++j) { Pmm *= -fact * somx2; fact += 2.0; } } double Plm; - if (l == m) { + if (l == absM) { Plm = Pmm; } else { - double Pm1m = x * (2 * m + 1) * Pmm; - if (l == m + 1) { + double Pm1m = x * (2 * absM + 1) * Pmm; + if (l == absM + 1) { Plm = Pm1m; } else { double Pll; - for (int ll = m + 2; ll <= l; ++ll) { + for (int ll = absM + 2; ll <= l; ++ll) { Pll = ((2 * ll - 1) * x * Pm1m - - (ll + m - 1) * Pmm) / (ll - m); + (ll + absM - 1) * Pmm) / (ll - absM); Pmm = Pm1m; Pm1m = Pll; } @@ -147,6 +298,8 @@ double sampleTheta(int l, int m, mt19937& gen) { for (double& v : cdf) v /= sum; built = true; + cachedL = l; + cachedAbsM = absM; } uniform_real_distribution dis(0.0, 1.0); @@ -242,28 +395,29 @@ vec4 inferno(double r, double theta, double phi, int n, int l, int m) { // --- angular part |P_l^m(cosθ)|^2 --- double x = cos(theta); + const int absM = abs(m); double Pmm = 1.0; - if (m > 0) { + if (absM > 0) { double somx2 = sqrt((1.0 - x) * (1.0 + x)); double fact = 1.0; - for (int j = 1; j <= m; ++j) { + for (int j = 1; j <= absM; ++j) { Pmm *= -fact * somx2; fact += 2.0; } } double Plm; - if (l == m) { + if (l == absM) { Plm = Pmm; } else { - double Pm1m = x * (2*m + 1) * Pmm; - if (l == m + 1) { + double Pm1m = x * (2*absM + 1) * Pmm; + if (l == absM + 1) { Plm = Pm1m; } else { - for (int ll = m + 2; ll <= l; ++ll) { + for (int ll = absM + 2; ll <= l; ++ll) { double Pll = ((2*ll - 1) * x * Pm1m - - (ll + m - 1) * Pmm) / (ll - m); + (ll + absM - 1) * Pmm) / (ll - absM); Pmm = Pm1m; Pm1m = Pll; } @@ -295,7 +449,7 @@ struct Camera { double lastX = 0.0, lastY = 0.0; vec3 position() const { - float clampedElevation = clamp(elevation, 0.01f, float(M_PI) - 0.01f); + float clampedElevation = glm::clamp(elevation, 0.01f, float(M_PI) - 0.01f); return vec3( radius * sin(clampedElevation) * cos(azimuth), radius * cos(clampedElevation), @@ -359,6 +513,158 @@ void generateParticles(int N) { } } +void buildQuantumCombos(int maxN = selectorMaxN) { + quantumCombos.clear(); + for (int nValue = 1; nValue <= maxN; ++nValue) { + for (int lValue = 0; lValue < nValue; ++lValue) { + for (int mValue = -lValue; mValue <= lValue; ++mValue) { + quantumCombos.push_back({nValue, lValue, mValue}); + } + } + } +} + +int findComboIndex(int nValue, int lValue, int mValue) { + for (size_t i = 0; i < quantumCombos.size(); ++i) { + const QuantumCombo& combo = quantumCombos[i]; + if (combo.n == nValue && combo.l == lValue && combo.m == mValue) { + return static_cast(i); + } + } + return -1; +} + +void syncCurrentComboIndex() { + currentComboIndex = findComboIndex(n, l, m); +} + +void stepComboSelection(int direction) { + if (quantumCombos.empty()) return; + + if (currentComboIndex < 0) { + syncCurrentComboIndex(); + if (currentComboIndex < 0) currentComboIndex = 0; + } + + const int total = static_cast(quantumCombos.size()); + currentComboIndex = (currentComboIndex + direction) % total; + if (currentComboIndex < 0) currentComboIndex += total; + + const QuantumCombo& combo = quantumCombos[currentComboIndex]; + n = combo.n; + l = combo.l; + m = combo.m; +} + +void updateWindowTitle(GLFWwindow* window) { + if (!window) return; + + string title = currentElementLabel() + + " - n=" + to_string(n) + + " l=" + to_string(l) + + " m=" + to_string(m) + + " N=" + to_string(N); + + if (currentComboIndex >= 0 && !quantumCombos.empty()) { + title += " [" + to_string(currentComboIndex + 1) + + "/" + to_string(quantumCombos.size()) + "]"; + } + + glfwSetWindowTitle(window, title.c_str()); +} + +using Glyph5x7 = array; + +const Glyph5x7& glyphForChar(char c) { + static const unordered_map glyphs = { + {' ', {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {'-', {0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00}}, + {'0', {0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}}, + {'1', {0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E}}, + {'2', {0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F}}, + {'3', {0x1F, 0x01, 0x02, 0x06, 0x01, 0x11, 0x0E}}, + {'4', {0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02}}, + {'5', {0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E}}, + {'6', {0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E}}, + {'7', {0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08}}, + {'8', {0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E}}, + {'9', {0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C}}, + {'A', {0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}}, + {'B', {0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E}}, + {'C', {0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E}}, + {'D', {0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C}}, + {'E', {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F}}, + {'F', {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10}}, + {'G', {0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F}}, + {'H', {0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}}, + {'I', {0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1F}}, + {'J', {0x01, 0x01, 0x01, 0x01, 0x11, 0x11, 0x0E}}, + {'K', {0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11}}, + {'L', {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F}}, + {'M', {0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11}}, + {'N', {0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11}}, + {'O', {0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}}, + {'P', {0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10}}, + {'Q', {0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D}}, + {'R', {0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11}}, + {'S', {0x0F, 0x10, 0x10, 0x0E, 0x01, 0x01, 0x1E}}, + {'T', {0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}}, + {'U', {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}}, + {'V', {0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04}}, + {'W', {0x11, 0x11, 0x11, 0x15, 0x15, 0x15, 0x0A}}, + {'X', {0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11}}, + {'Y', {0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04}}, + {'Z', {0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F}} + }; + + static const Glyph5x7 fallback = {0x1F, 0x11, 0x02, 0x04, 0x04, 0x00, 0x04}; + const auto it = glyphs.find(c); + return (it == glyphs.end()) ? fallback : it->second; +} + +vector buildTextVertices(const string& text, float originX, float originY, float pixelScale) { + vector vertices; + vertices.reserve(text.size() * 7 * 5 * 12); + + float cursorX = originX; + float cursorY = originY; + const float charWidth = 5.0f * pixelScale; + const float charHeight = 7.0f * pixelScale; + const float spacing = pixelScale; + + for (char rawChar : text) { + if (rawChar == '\n') { + cursorX = originX; + cursorY += charHeight + spacing * 2.0f; + continue; + } + + const char c = static_cast(toupper(static_cast(rawChar))); + const Glyph5x7& glyph = glyphForChar(c); + + for (int row = 0; row < 7; ++row) { + for (int col = 0; col < 5; ++col) { + const bool bitOn = (glyph[row] >> (4 - col)) & 1; + if (!bitOn) continue; + + const float x = cursorX + col * pixelScale; + const float y = cursorY + row * pixelScale; + const float x2 = x + pixelScale; + const float y2 = y + pixelScale; + + vertices.insert(vertices.end(), { + x, y, x2, y, x, y2, + x2, y, x2, y2, x, y2 + }); + } + } + + cursorX += charWidth + spacing; + } + + return vertices; +} + struct Engine { GLFWwindow* window; int WIDTH = 800; @@ -370,6 +676,11 @@ struct Engine { GLuint shaderProgram; GLint modelLoc, viewLoc, projLoc, colorLoc; + // text overlay vars + GLuint textVAO = 0, textVBO = 0; + GLuint textShaderProgram = 0; + GLint textProjectionLoc = -1, textColorLoc = -1; + // --- shaders --- const char* vertexShaderSource = R"glsl( #version 330 core @@ -393,11 +704,127 @@ struct Engine { FragColor = vec4(objectColor.rgb , objectColor.a); } )glsl"; + static bool checkShaderCompile(GLuint shader, const char* stage) { + GLint ok = GL_FALSE; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + if (ok == GL_TRUE) return true; + + GLint logLen = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLen); + string log(std::max(1, logLen), '\0'); + glGetShaderInfoLog(shader, logLen, nullptr, log.data()); + cerr << "Shader compile error (" << stage << "): " << log << '\n'; + return false; + } + + static bool checkProgramLink(GLuint program) { + GLint ok = GL_FALSE; + glGetProgramiv(program, GL_LINK_STATUS, &ok); + if (ok == GL_TRUE) return true; + + GLint logLen = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLen); + string log(std::max(1, logLen), '\0'); + glGetProgramInfoLog(program, logLen, nullptr, log.data()); + cerr << "Program link error: " << log << '\n'; + return false; + } + + void initTextRenderer() { + const char* textVertexShader = R"glsl( + #version 330 core + layout(location = 0) in vec2 aPos; + uniform mat4 projection; + void main() { + gl_Position = projection * vec4(aPos, 0.0, 1.0); + } + )glsl"; + + const char* textFragmentShader = R"glsl( + #version 330 core + out vec4 FragColor; + uniform vec4 textColor; + void main() { + FragColor = textColor; + } + )glsl"; + + GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertexShader, 1, &textVertexShader, nullptr); + glCompileShader(vertexShader); + if (!checkShaderCompile(vertexShader, "text vertex")) { + glDeleteShader(vertexShader); + return; + } + + GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragmentShader, 1, &textFragmentShader, nullptr); + glCompileShader(fragmentShader); + if (!checkShaderCompile(fragmentShader, "text fragment")) { + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + return; + } + + textShaderProgram = glCreateProgram(); + glAttachShader(textShaderProgram, vertexShader); + glAttachShader(textShaderProgram, fragmentShader); + glLinkProgram(textShaderProgram); + if (!checkProgramLink(textShaderProgram)) { + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + glDeleteProgram(textShaderProgram); + textShaderProgram = 0; + return; + } + + textProjectionLoc = glGetUniformLocation(textShaderProgram, "projection"); + textColorLoc = glGetUniformLocation(textShaderProgram, "textColor"); + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + glGenVertexArrays(1, &textVAO); + glGenBuffers(1, &textVBO); + glBindVertexArray(textVAO); + glBindBuffer(GL_ARRAY_BUFFER, textVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 12, nullptr, GL_DYNAMIC_DRAW); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + glBindVertexArray(0); + } + Engine() { - if (!glfwInit()) exit(-1); + if (!glfwInit()) { + cerr << "GLFW init failed\n"; + exit(EXIT_FAILURE); + } + +#ifdef __APPLE__ + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); +#endif + window = glfwCreateWindow(800, 600, "Atom Prob-Flow", NULL, NULL); + if (!window) { + cerr << "Failed to create GLFW window\n"; + glfwTerminate(); + exit(EXIT_FAILURE); + } + glfwMakeContextCurrent(window); - glewInit(); + glewExperimental = GL_TRUE; + GLenum glewStatus = glewInit(); + if (glewStatus != GLEW_OK) { + cerr << "GLEW init failed: " << reinterpret_cast(glewGetErrorString(glewStatus)) << '\n'; + glfwDestroyWindow(window); + glfwTerminate(); + exit(EXIT_FAILURE); + } + // Clear benign GL_INVALID_ENUM that can occur on core profiles after glewInit. + glGetError(); glEnable(GL_DEPTH_TEST); // Generate Sphere Vertices manually (like I did in the gravity sim) @@ -424,21 +851,40 @@ struct Engine { GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertexShader, 1, &vertexShaderSource, NULL); glCompileShader(vertexShader); + if (!checkShaderCompile(vertexShader, "vertex")) { + glfwDestroyWindow(window); + glfwTerminate(); + exit(EXIT_FAILURE); + } GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL); glCompileShader(fragmentShader); + if (!checkShaderCompile(fragmentShader, "fragment")) { + glfwDestroyWindow(window); + glfwTerminate(); + exit(EXIT_FAILURE); + } shaderProgram = glCreateProgram(); glAttachShader(shaderProgram, vertexShader); glAttachShader(shaderProgram, fragmentShader); glLinkProgram(shaderProgram); + if (!checkProgramLink(shaderProgram)) { + glfwDestroyWindow(window); + glfwTerminate(); + exit(EXIT_FAILURE); + } // Get uniform locations modelLoc = glGetUniformLocation(shaderProgram, "model"); viewLoc = glGetUniformLocation(shaderProgram, "view"); projLoc = glGetUniformLocation(shaderProgram, "projection"); colorLoc = glGetUniformLocation(shaderProgram, "objectColor"); + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + initTextRenderer(); } vec3 sphericalToCartesian(float r, float theta, float phi){ float x = r * sin(theta) * cos(phi); @@ -471,7 +917,14 @@ struct Engine { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glUseProgram(shaderProgram); // Use our new shaded system - mat4 projection = perspective(radians(45.0f), 800.0f/600.0f, 0.1f, 2000.0f); + int framebufferWidth = 0; + int framebufferHeight = 0; + glfwGetFramebufferSize(window, &framebufferWidth, &framebufferHeight); + if (framebufferWidth <= 0 || framebufferHeight <= 0) return; + + glViewport(0, 0, framebufferWidth, framebufferHeight); + const float aspect = static_cast(framebufferWidth) / static_cast(framebufferHeight); + mat4 projection = perspective(radians(45.0f), aspect, 0.1f, 2000.0f); mat4 view = lookAt(camera.position(), camera.target, vec3(0, 1, 0)); // Send view and projection to the shader @@ -490,6 +943,59 @@ struct Engine { glDrawArrays(GL_TRIANGLES, 0, sphereVertexCount); } } + void drawTopLeftLabel(const string& text) { + if (text.empty()) return; + + int framebufferWidth = 0; + int framebufferHeight = 0; + glfwGetFramebufferSize(window, &framebufferWidth, &framebufferHeight); + if (framebufferWidth <= 0 || framebufferHeight <= 0) return; + + vector textVertices = buildTextVertices(text, 16.0f, 16.0f, 3.0f); + if (textVertices.empty()) return; + + vector textVerticesClip; + textVerticesClip.reserve((textVertices.size() / 2) * 3); + for (size_t i = 0; i + 1 < textVertices.size(); i += 2) { + const float px = textVertices[i]; + const float py = textVertices[i + 1]; + const float ndcX = (px / static_cast(framebufferWidth)) * 2.0f - 1.0f; + const float ndcY = 1.0f - (py / static_cast(framebufferHeight)) * 2.0f; + textVerticesClip.push_back(ndcX); + textVerticesClip.push_back(ndcY); + textVerticesClip.push_back(0.0f); + } + + const bool depthWasEnabled = glIsEnabled(GL_DEPTH_TEST) == GL_TRUE; + const bool blendWasEnabled = glIsEnabled(GL_BLEND) == GL_TRUE; + + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glUseProgram(shaderProgram); + const mat4 identity = mat4(1.0f); + glUniformMatrix4fv(modelLoc, 1, GL_FALSE, value_ptr(identity)); + glUniformMatrix4fv(viewLoc, 1, GL_FALSE, value_ptr(identity)); + glUniformMatrix4fv(projLoc, 1, GL_FALSE, value_ptr(identity)); + glUniform4f(colorLoc, 1.0f, 1.0f, 1.0f, 1.0f); + + if (textVAO == 0 || textVBO == 0) { + glGenVertexArrays(1, &textVAO); + glGenBuffers(1, &textVBO); + } + + glBindVertexArray(textVAO); + glBindBuffer(GL_ARRAY_BUFFER, textVBO); + glBufferData(GL_ARRAY_BUFFER, textVerticesClip.size() * sizeof(float), textVerticesClip.data(), GL_DYNAMIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + glDrawArrays(GL_TRIANGLES, 0, static_cast(textVerticesClip.size() / 3)); + glBindVertexArray(0); + + if (!blendWasEnabled) glDisable(GL_BLEND); + if (depthWasEnabled) glEnable(GL_DEPTH_TEST); + } void setupCameraCallbacks() { glfwSetWindowUserPointer(window, &camera); glfwSetMouseButtonCallback(window, [](GLFWwindow* win, int button, int action, int mods) { @@ -501,38 +1007,61 @@ struct Engine { glfwSetScrollCallback(window, [](GLFWwindow* win, double xoffset, double yoffset) { ((Camera*)glfwGetWindowUserPointer(win))->processScroll(xoffset, yoffset); }); - // Key callback: modify global quantum numbers + // Key callback: modify global quantum numbers / cycle valid combinations. glfwSetKeyCallback(window, [](GLFWwindow* win, int key, int scancode, int action, int mods) { if (!(action == GLFW_PRESS || action == GLFW_REPEAT)) return; - if (key == GLFW_KEY_W) { + bool shouldRegen = false; + bool usedSelector = false; + bool usedElementSelector = false; + + if (key == GLFW_KEY_UP) { + stepElementSelection(1); + shouldRegen = true; + usedElementSelector = true; + } else if (key == GLFW_KEY_DOWN) { + stepElementSelection(-1); + shouldRegen = true; + usedElementSelector = true; + } else if (key == GLFW_KEY_RIGHT) { + stepComboSelection(1); + shouldRegen = true; + usedSelector = true; + } else if (key == GLFW_KEY_LEFT) { + stepComboSelection(-1); + shouldRegen = true; + usedSelector = true; + } else if (key == GLFW_KEY_W) { n += 1; - generateParticles(N); + shouldRegen = true; } else if (key == GLFW_KEY_S) { n -= 1; if (n < 1) n = 1; - generateParticles(N); + shouldRegen = true; } else if (key == GLFW_KEY_E) { l += 1; - generateParticles(N); + shouldRegen = true; } else if (key == GLFW_KEY_D) { l -= 1; if (l < 0) l = 0; - generateParticles(N); + shouldRegen = true; } else if (key == GLFW_KEY_R) { m += 1; - generateParticles(N); + shouldRegen = true; } else if (key == GLFW_KEY_F) { m -= 1; - generateParticles(N); + shouldRegen = true; } else if (key == GLFW_KEY_T) { - N +=100000; - generateParticles(N); + N += 100000; + shouldRegen = true; } else if (key == GLFW_KEY_G) { - N -=100000; - generateParticles(N); + N -= 100000; + if (N < 1000) N = 1000; + shouldRegen = true; } + if (!shouldRegen) return; + // Clamp to valid ranges if (l > n - 1) l = n - 1; if (l < 0) l = 0; @@ -540,16 +1069,26 @@ struct Engine { if (m < -l) m = -l; electron_r = float(n) / 3.0f; + syncCurrentComboIndex(); + if (!usedElementSelector) syncElementSelectionFromQuantum(); cout << "Quantum numbers updated: n=" << n << " l=" << l << " m=" << m << " N=" << N << "\n"; + if (usedSelector && currentComboIndex >= 0) { + cout << "Selector: " << (currentComboIndex + 1) << "/" << quantumCombos.size() << "\n"; + } + if (usedElementSelector) { + cout << "Element: " << currentElementLabel() << "\n"; + } + generateParticles(N); + updateWindowTitle(win); }); } }; -Engine engine; struct Grid { + Engine& engine; GLuint gridVAO, gridVBO; vector vertices; - Grid() { + Grid(Engine& engineRef) : engine(engineRef) { vertices = CreateGridVertices(500.0f, 2); engine.CreateVBOVAO(gridVAO, gridVBO, vertices.data(), vertices.size()); } @@ -623,11 +1162,16 @@ struct Grid { } }; -Grid grid; // ================= Main Loop ================= // int main () { - GLint modelLoc = glGetUniformLocation(engine.shaderProgram, "model"); + Engine engine; + Grid grid(engine); + buildQuantumCombos(); + buildElementMappings(); + setElementByIndex(0); + syncCurrentComboIndex(); + GLint objectColorLoc = glGetUniformLocation(engine.shaderProgram, "objectColor"); glUseProgram(engine.shaderProgram); engine.setupCameraCallbacks(); @@ -636,7 +1180,18 @@ int main () { electron_r = float(n) / 3.0f; // --- Sample particles --- - generateParticles(250000); + N = 250000; + generateParticles(N); + updateWindowTitle(engine.window); + cout << "Mapped known elements to (n,l,m): " << elementMappings.size() << " entries.\n"; + for (const ElementQuantum& element : elementMappings) { + cout << setw(3) << element.atomicNumber << " " + << element.name + << " -> n=" << element.n + << " l=" << element.l + << " m=" << element.m << "\n"; + } + cout << "Controls: UP/DOWN cycle elements, LEFT/RIGHT cycle valid (n,l,m) combinations.\n"; float dt = 0.5f; cout << "Starting simulation..." << endl; @@ -656,6 +1211,7 @@ int main () { } // ------ Draw Particles ------ engine.drawSpheres(particles); + engine.drawTopLeftLabel(currentElementLabel()); glfwSwapBuffers(engine.window); glfwPollEvents(); @@ -665,4 +1221,4 @@ int main () { glfwDestroyWindow(engine.window); glfwTerminate(); return 0; -} \ No newline at end of file +}