/* Spinning Kaleidoscope Star written by Jared Bruni 2025 */ #include"mx.hpp" #include"argz.hpp" #ifdef __EMSCRIPTEN__ #include <emscripten/emscripten.h> #include <GLES3/gl3.h> #endif #include"gl.hpp" #include"loadpng.hpp" #include"model.hpp" #define CHECK_GL_ERROR() \ { GLenum err = glGetError(); \ if (err != GL_NO_ERROR) \ printf("OpenGL Error: %d at %s:%d\n", err, __FILE__, __LINE__); } #ifndef M_PI #define M_PI 3.14159265358979323846 #endif const char *vSource = R"(#version 300 es precision highp float; layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aNormal; layout (location = 2) in vec2 aTexCoords; uniform mat4 model; uniform mat4 view; uniform mat4 projection; uniform vec3 lightPos; uniform vec3 viewPos; uniform vec3 lightColor; uniform vec3 objectColor; out vec3 vertexColor; out vec2 TexCoords; void main() { vec4 worldPos = model * vec4(aPos, 1.0); gl_Position = projection * view * worldPos; vec3 norm = normalize(mat3(transpose(inverse(model))) * aNormal); vec3 lightDir = normalize(lightPos - vec3(worldPos)); float ambientStrength = 0.8; vec3 ambient = ambientStrength * lightColor; float diff = max(dot(norm, lightDir), 0.0); vec3 diffuse = diff * lightColor; float specularStrength = 1.0; // Increase from 0.5 to 1.0 for more shine float shininess = 64.0; // Increase this value for a tighter, shinier highlight vec3 viewDir = normalize(viewPos - vec3(worldPos)); vec3 reflectDir = reflect(-lightDir, norm); float spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess); vec3 specular = specularStrength * spec * lightColor; vec3 finalColor = (ambient + diffuse + specular) * objectColor; vertexColor = finalColor; TexCoords = aTexCoords; } )"; const char *fSource = R"(#version 300 es precision highp float; in vec2 TexCoords; out vec4 color; uniform float time_f; uniform sampler2D texture1; uniform vec2 iResolution; float pi = 3.14159265358979323846264; float mirror1(float x) { return abs(fract(x * 0.5) * 2.0 - 1.0); } vec2 mirror2(vec2 uv) { return vec2(mirror1(uv.x), mirror1(uv.y)); } void main(void) { vec2 tc = TexCoords; vec2 p = tc * 2.0 - 1.0; p.x *= iResolution.x / iResolution.y; float n = 10.0 + 6.0 * sin(time_f * 0.25); float seg = 2.0 * pi / n; float a = atan(p.y, p.x) + time_f * 0.15; a = mod(a, seg); a = abs(a - seg * 0.5); float r = length(p); r += 0.08 * sin(8.0 * a + time_f) + 0.05 * sin(6.0 * r - time_f * 1.2); vec2 q = vec2(cos(a), sin(a)) * r; q.x /= iResolution.x / iResolution.y; vec2 uvM = q * 0.5 + 0.5; float s = 0.3 * sin(time_f * 0.6); vec2 c = vec2(0.5); vec2 d = uvM - c; float rr = length(d); float th = atan(d.y, d.x) + s * exp(-rr * 3.0); vec2 swirl = c + vec2(cos(th), sin(th)) * rr; vec2 uvMand = mirror2(swirl); vec2 uvOrig = mirror2(tc); vec4 mand = texture(texture1, uvMand); vec4 orig = texture(texture1, uvOrig); color = mix(orig, mand, 0.85); } )"; class Game : public gl::GLObject { public: Game() = default; virtual ~Game() override {} void load(gl::GLWindow *win) override { font.loadFont(win->util.getFilePath("data/font.ttf"), 36); if(!shader.loadProgramFromText(vSource, fSource)) { throw mx::Exception("Failed to load shader program"); } if(!model.openModel(win->util.getFilePath("data/star.mxmod"))) { throw mx::Exception("Failed to load model"); } model.setTextures(win, win->util.getFilePath("data/metal.tex"), win->util.getFilePath("data")); model.setShaderProgram(&shader, "texture1"); shader.useProgram(); } glm::mat4 modelMatrix{1.0f}; glm::mat4 viewMatrix{1.0f}; glm::mat4 projectionMatrix{1.0f}; glm::vec3 cameraPosition{0.0f, 2.0f, 5.0f}; glm::vec3 lightPos{0.0f, 5.0f, 0.0f}; float rot[3] = {1.0f, 0.0f, 0.f}; void draw(gl::GLWindow *win) override { glEnable(GL_DEPTH_TEST); Uint32 currentTime = SDL_GetTicks(); float deltaTime = (currentTime - lastUpdateTime) / 1000.0f; lastUpdateTime = currentTime; static float rotationAngle = 0.0f; rotationAngle += deltaTime * 800.0f; model.setShaderProgram(&shader, "texture1"); shader.useProgram(); cameraPosition = glm::vec3(0.0f, 2.0f, zoom); glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f); glm::vec3 upVector = glm::vec3(0.0f, 1.0f, 0.0f); modelMatrix = glm::mat4(1.0f); modelMatrix = glm::scale(modelMatrix, glm::vec3(2.0f, 2.0f, 2.0f)); modelMatrix = glm::rotate(modelMatrix, glm::radians(rotationAngle), glm::vec3(rot[0], rot[1], rot[2])); viewMatrix = glm::lookAt(cameraPosition, cameraTarget, upVector); float aspectRatio = 1.0f; if (win->h > 0) { aspectRatio = static_cast<float>(win->w) / static_cast<float>(win->h); } projectionMatrix = glm::perspective(glm::radians(45.0f), aspectRatio, 0.1f, 100.0f); glUniformMatrix4fv(glGetUniformLocation(shader.id(), "model"), 1, GL_FALSE, glm::value_ptr(modelMatrix)); glUniformMatrix4fv(glGetUniformLocation(shader.id(), "view"), 1, GL_FALSE, glm::value_ptr(viewMatrix)); glUniformMatrix4fv(glGetUniformLocation(shader.id(), "projection"), 1, GL_FALSE, glm::value_ptr(projectionMatrix)); glUniform3f(glGetUniformLocation(shader.id(), "lightPos"), lightPos.x, lightPos.y, lightPos.z); glUniform3f(glGetUniformLocation(shader.id(), "viewPos"), cameraPosition.x, cameraPosition.y, cameraPosition.z); glUniform3f(glGetUniformLocation(shader.id(), "lightColor"), 2.0f, 2.0f, 2.0f); glUniform3f(glGetUniformLocation(shader.id(), "objectColor"), 1.0f, 1.0f, 1.0f); glUniform2f(glGetUniformLocation(shader.id(), "iResolution"), static_cast<float>(win->w), static_cast<float>(win->h)); glUniform1f(glGetUniformLocation(shader.id(), "time_f"), static_cast<float>(currentTime) / 1000.0f); CHECK_GL_ERROR(); glDisable(GL_BLEND); model.drawArrays(); glDisable(GL_DEPTH_TEST); win->text.setColor ({255, 0,0, 255}); win->text.printText_Solid(font, 15, 15, "Example use arrow keys/swipe"); update(deltaTime); } float zoom = 5.0f; void event(gl::GLWindow *win, SDL_Event &e) override { if (e.type == SDL_KEYDOWN) { switch (e.key.keysym.sym) { case SDLK_LEFT: rot[0] = 0.0f; break; case SDLK_RIGHT: rot[0] = 1.0f; break; case SDLK_a: rot[1] = 0.0f; break; case SDLK_s: rot[1] = 1.0f; break; case SDLK_w: rot[2] = 0.0f; break; case SDLK_d: rot[2] = 1.0f; break; case SDLK_UP: zoom -= 0.5f; if (zoom < 0.5f) zoom = 0.5f; break; case SDLK_DOWN: zoom += 0.5f; if (zoom > 20.0f) zoom = 20.0f; break; default: break; } } if (e.type == SDL_FINGERDOWN || e.type == SDL_FINGERMOTION) { static float lastY = 0.0f; static float lastX = 0.0f; if (e.type == SDL_FINGERDOWN) { lastY = e.tfinger.y; lastX = e.tfinger.x; } else if (e.type == SDL_FINGERMOTION) { float dy = e.tfinger.y - lastY; zoom -= dy * 10.0f; if (zoom < 0.5f) zoom = 0.5f; if (zoom > 20.0f) zoom = 20.0f; float dx = e.tfinger.x - lastX; rot[0] += dx * 5.0f; if (rot[0] < 0.0f) rot[0] = 0.0f; if (rot[0] > 1.0f) rot[0] = 1.0f; lastY = e.tfinger.y; lastX = e.tfinger.x; } } } void update(float deltaTime) {} private: mx::Font font; Uint32 lastUpdateTime = SDL_GetTicks(); gl::ShaderProgram shader; mx::Model model; }; class MainWindow : public gl::GLWindow { public: MainWindow(std::string path, int tw, int th) : gl::GLWindow("Diamond", tw, th) { setPath(path); setObject(new Game()); object->load(this); } ~MainWindow() override {} virtual void event(SDL_Event &e) override {} const Uint32 FPS = 60; const Uint32 frameDelay = 1000 / FPS; virtual void draw() override { Uint32 frameStart = SDL_GetTicks(); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glViewport(0, 0, w, h); object->draw(this); swap(); Uint32 frameTime = SDL_GetTicks() - frameStart; if (frameTime < frameDelay) { SDL_Delay(frameDelay - frameTime); } } }; MainWindow *main_w = nullptr; void eventProc() { main_w->proc(); } int main(int argc, char **argv) { #ifdef __EMSCRIPTEN__ MainWindow main_window("/", 960, 720); main_w =&main_window; emscripten_set_main_loop(eventProc, 0, 1); #else Arguments args = proc_args(argc, argv); try { MainWindow main_window(args.path, args.width, args.height); main_window.loop(); } catch(const mx::Exception &e) { mx::system_err << "mx: Exception: " << e.text() << "\n"; mx::system_err.flush(); exit(EXIT_FAILURE); } #endif return 0; }