From f95da0e935bca35efcc6f59c341c5a453bee33fb Mon Sep 17 00:00:00 2001 From: Riccardo Rolando Date: Sat, 21 Feb 2026 01:15:10 +0100 Subject: [PATCH 1/2] MacOS build + left & right keys to change (n,l,m) --- Makefile | 61 ++++++++++++ bin/atom | Bin 0 -> 56808 bytes bin/atom_raytracer | Bin 0 -> 78944 bytes bin/atom_realtime | Bin 0 -> 80800 bytes bin/wave_atom_2d | Bin 0 -> 56176 bytes src/atom_raytracer.cpp | 6 +- src/atom_realtime.cpp | 210 ++++++++++++++++++++++++++++++++++++----- 7 files changed, 253 insertions(+), 24 deletions(-) create mode 100644 Makefile create mode 100755 bin/atom create mode 100755 bin/atom_raytracer create mode 100755 bin/atom_realtime create mode 100755 bin/wave_atom_2d 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 0000000000000000000000000000000000000000..afdc2e000e90cbcf5d1255ec5b878a8448f4912f GIT binary patch literal 56808 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 Y`TDryznQovw6-;WO-hulNi*jE0T7@Hu>b%7 literal 0 HcmV?d00001 diff --git a/bin/atom_raytracer b/bin/atom_raytracer new file mode 100755 index 0000000000000000000000000000000000000000..908754655a6ea231c8ee433adf29cc7516ce184a GIT binary patch literal 78944 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 IW5C$|0Tez*;{X5v literal 0 HcmV?d00001 diff --git a/bin/atom_realtime b/bin/atom_realtime new file mode 100755 index 0000000000000000000000000000000000000000..1c21d54cac7d6e036396f0d62c07b4031eff6fd3 GIT binary patch literal 80800 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 PapdAF=iYN%3+n#@GrU`^ literal 0 HcmV?d00001 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..268ec48 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,7 @@ #include #include #include +#include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif @@ -31,6 +34,16 @@ 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 = 6; + // ================= Physics Sampling ================= // struct Particle { vec3 pos; @@ -295,7 +308,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 +372,65 @@ 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 = "Atom Prob-Flow - 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()); +} + struct Engine { GLFWwindow* window; int WIDTH = 800; @@ -393,11 +465,63 @@ 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; + } + 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 +548,39 @@ 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); } vec3 sphericalToCartesian(float r, float theta, float phi){ float x = r * sin(theta) * cos(phi); @@ -501,38 +643,52 @@ 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; + + 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 +696,22 @@ struct Engine { if (m < -l) m = -l; electron_r = float(n) / 3.0f; + syncCurrentComboIndex(); cout << "Quantum numbers updated: n=" << n << " l=" << l << " m=" << m << " N=" << N << "\n"; + if (usedSelector && currentComboIndex >= 0) { + cout << "Selector: " << (currentComboIndex + 1) << "/" << quantumCombos.size() << "\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 +785,14 @@ struct Grid { } }; -Grid grid; // ================= Main Loop ================= // int main () { - GLint modelLoc = glGetUniformLocation(engine.shaderProgram, "model"); + Engine engine; + Grid grid(engine); + buildQuantumCombos(); + syncCurrentComboIndex(); + GLint objectColorLoc = glGetUniformLocation(engine.shaderProgram, "objectColor"); glUseProgram(engine.shaderProgram); engine.setupCameraCallbacks(); @@ -636,7 +801,10 @@ int main () { electron_r = float(n) / 3.0f; // --- Sample particles --- - generateParticles(250000); + N = 250000; + generateParticles(N); + updateWindowTitle(engine.window); + cout << "Controls: LEFT/RIGHT cycle valid (n,l,m) combinations.\n"; float dt = 0.5f; cout << "Starting simulation..." << endl; @@ -665,4 +833,4 @@ int main () { glfwDestroyWindow(engine.window); glfwTerminate(); return 0; -} \ No newline at end of file +} From 1a56dbd0e24a5c88eedf3a8626902f3dfd7846b3 Mon Sep 17 00:00:00 2001 From: Riccardo Rolando Date: Sat, 21 Feb 2026 08:25:03 +0100 Subject: [PATCH 2/2] Added the names of the periodic table of elements. --- bin/atom_realtime | Bin 80800 -> 102368 bytes src/atom_realtime.cpp | 430 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 409 insertions(+), 21 deletions(-) diff --git a/bin/atom_realtime b/bin/atom_realtime index 1c21d54cac7d6e036396f0d62c07b4031eff6fd3..bd329f14e14012ea7364d141573081cda184ca5f 100755 GIT binary patch literal 102368 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 diff --git a/src/atom_realtime.cpp b/src/atom_realtime.cpp index 268ec48..a29377d 100644 --- a/src/atom_realtime.cpp +++ b/src/atom_realtime.cpp @@ -18,6 +18,10 @@ #include #include #include +#include +#include +#include +#include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif @@ -42,7 +46,134 @@ struct QuantumCombo { vector quantumCombos; int currentComboIndex = -1; -constexpr int selectorMaxN = 6; +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 { @@ -64,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; @@ -100,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); @@ -113,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; @@ -125,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; } @@ -160,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); @@ -255,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; } @@ -418,7 +559,8 @@ void stepComboSelection(int direction) { void updateWindowTitle(GLFWwindow* window) { if (!window) return; - string title = "Atom Prob-Flow - n=" + to_string(n) + string title = currentElementLabel() + + " - n=" + to_string(n) + " l=" + to_string(l) + " m=" + to_string(m) + " N=" + to_string(N); @@ -431,6 +573,98 @@ void updateWindowTitle(GLFWwindow* window) { 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; @@ -442,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 @@ -491,6 +730,70 @@ struct Engine { 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()) { cerr << "GLFW init failed\n"; @@ -581,6 +884,7 @@ struct Engine { glDeleteShader(vertexShader); glDeleteShader(fragmentShader); + initTextRenderer(); } vec3 sphericalToCartesian(float r, float theta, float phi){ float x = r * sin(theta) * cos(phi); @@ -613,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 @@ -632,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) { @@ -649,8 +1013,17 @@ struct Engine { bool shouldRegen = false; bool usedSelector = false; + bool usedElementSelector = false; - if (key == GLFW_KEY_RIGHT) { + 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; @@ -697,10 +1070,14 @@ struct Engine { 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); }); @@ -791,6 +1168,8 @@ int main () { Engine engine; Grid grid(engine); buildQuantumCombos(); + buildElementMappings(); + setElementByIndex(0); syncCurrentComboIndex(); GLint objectColorLoc = glGetUniformLocation(engine.shaderProgram, "objectColor"); @@ -804,7 +1183,15 @@ int main () { N = 250000; generateParticles(N); updateWindowTitle(engine.window); - cout << "Controls: LEFT/RIGHT cycle valid (n,l,m) combinations.\n"; + 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; @@ -824,6 +1211,7 @@ int main () { } // ------ Draw Particles ------ engine.drawSpheres(particles); + engine.drawTopLeftLabel(currentElementLabel()); glfwSwapBuffers(engine.window); glfwPollEvents();