├── .gitignore ├── README.md ├── deps.edn └── src └── cgl ├── camera.clj ├── shader.clj ├── mesh.clj ├── window.clj ├── geometry.clj └── core.clj /.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache/ 2 | .idea/ 3 | .nrepl-port 4 | chill-gl.iml -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | runs on apple silicon 2 | 3 | ```sh 4 | clojure -M:nrepl 5 | ``` -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {org.lwjgl/lwjgl {:mvn/version "3.3.3"} 2 | org.lwjgl/lwjgl-glfw {:mvn/version "3.3.3"} 3 | org.lwjgl/lwjgl-opengl {:mvn/version "3.3.3"} 4 | org.lwjgl/lwjgl$natives-macos-arm64 {:mvn/version "3.3.3"} 5 | org.lwjgl/lwjgl-glfw$natives-macos-arm64 {:mvn/version "3.3.3"} 6 | org.lwjgl/lwjgl-opengl$natives-macos-arm64 {:mvn/version "3.3.3"} 7 | org.joml/joml {:mvn/version "1.10.8"}} 8 | 9 | :aliases {:nrepl {:extra-deps {nrepl/nrepl {:mvn/version "1.3.1"} 10 | ch.qos.logback/logback-classic {:mvn/version "1.4.14"}} 11 | :jvm-opts ["-Djdk.attach.allowAttachSelf" "-XstartOnFirstThread"] 12 | :main-opts ["-m" "cgl.core" "true"]}}} 13 | 14 | -------------------------------------------------------------------------------- /src/cgl/camera.clj: -------------------------------------------------------------------------------- 1 | (ns cgl.camera 2 | (:import [org.joml Matrix4f Vector3f])) 3 | 4 | (defrecord Camera [position target up]) 5 | 6 | (defn create-camera 7 | "Creates a new camera with the given position, target, and up vector" 8 | ([] 9 | (create-camera (Vector3f. 0.0 0.0 3.0) 10 | (Vector3f. 0.0 0.0 0.0) 11 | (Vector3f. 0.0 1.0 0.0))) 12 | ([position target up] 13 | (->Camera position target up))) 14 | 15 | (defn get-view-matrix 16 | "Returns the view matrix for this camera" 17 | [camera] 18 | (let [{:keys [position target up]} camera] 19 | (-> (Matrix4f.) 20 | (.identity) 21 | (.lookAt position target up)))) 22 | 23 | (defn move-camera 24 | "Move the camera by the given delta values" 25 | [camera dx dy dz] 26 | (let [position (:position camera)] 27 | (.add position dx dy dz) 28 | camera)) 29 | 30 | (defn move-forward 31 | "Move the camera forward by the given amount" 32 | [camera amount] 33 | (let [direction (-> (Vector3f.) 34 | (.set ^Vector3f (:target camera)) 35 | (.sub (:position camera)) 36 | (.normalize) 37 | (.mul amount))] 38 | (move-camera camera (.x direction) (.y direction) (.z direction)))) 39 | 40 | (defn move-right 41 | "Move the camera right by the given amount" 42 | [camera amount] 43 | (let [direction (-> (Vector3f.) 44 | (.set ^Vector3f (:target camera)) 45 | (.sub (:position camera)) 46 | (.cross (:up camera)) 47 | (.normalize) 48 | (.mul amount))] 49 | (move-camera camera (.x direction) (.y direction) (.z direction)))) 50 | 51 | (defn rotate-camera 52 | "Rotate the camera around the y axis by the given angle in radians" 53 | [camera angle] 54 | (let [position (:position camera) 55 | target (:target camera) 56 | direction (-> (Vector3f.) 57 | (.set ^Vector3f position) 58 | (.sub target)) 59 | distance (.length direction) 60 | 61 | ; Calculate new x and z positions 62 | new-x (* distance (Math/sin angle)) 63 | new-z (* distance (Math/cos angle)) 64 | 65 | ; Create new position 66 | new-position (Vector3f. new-x (.y position) new-z)] 67 | 68 | (assoc camera :position new-position))) 69 | -------------------------------------------------------------------------------- /src/cgl/shader.clj: -------------------------------------------------------------------------------- 1 | (ns cgl.shader 2 | (:require [cgl.camera :as camera] 3 | [cgl.window :as win]) 4 | (:import (java.nio FloatBuffer) 5 | (org.joml Matrix4f) 6 | (org.lwjgl BufferUtils) 7 | (org.lwjgl.opengl GL33))) 8 | 9 | (defn create-shader [type ^String source] 10 | (let [shader (GL33/glCreateShader ^int type)] 11 | (GL33/glShaderSource shader source) 12 | (GL33/glCompileShader shader) 13 | 14 | (when (zero? (GL33/glGetShaderi shader GL33/GL_COMPILE_STATUS)) 15 | (let [log (GL33/glGetShaderInfoLog shader)] 16 | (GL33/glDeleteShader shader) 17 | (throw (RuntimeException. (str "Shader compilation failed: " log))))) 18 | 19 | shader)) 20 | 21 | (defn set-uniform [type program uniform value] 22 | (let [{:keys [location buffer]} (-> program :uniforms uniform)] 23 | (case type 24 | :m4f (do 25 | (.get ^Matrix4f value ^FloatBuffer buffer) 26 | (GL33/glUniformMatrix4fv ^int location false ^FloatBuffer buffer))))) 27 | 28 | (defn update-view-matrix [camera shader-program] 29 | (let [view (camera/get-view-matrix camera)] 30 | (set-uniform :m4f shader-program :view view))) 31 | 32 | (defn update-projection-matrix [shader-program] 33 | (let [aspect-ratio (/ (float @win/window-width*) (float @win/window-height*)) 34 | projection (-> (Matrix4f.) 35 | (.identity) 36 | (.perspective (Math/toRadians 45.0) aspect-ratio 0.1 100.0))] 37 | (set-uniform :m4f shader-program :projection projection))) 38 | 39 | (defprotocol IShaderProgram 40 | (set-uniforms [this inputs])) 41 | 42 | (defrecord ShaderProgram [program uniforms] 43 | IShaderProgram 44 | (set-uniforms [this {:keys [camera]}] 45 | (update-view-matrix camera this) 46 | (update-projection-matrix this))) 47 | 48 | (defn create-shader-program [vertex-shader-source fragment-shader-source] 49 | (let [vertex-shader (create-shader GL33/GL_VERTEX_SHADER vertex-shader-source) 50 | fragment-shader (create-shader GL33/GL_FRAGMENT_SHADER fragment-shader-source) 51 | program (GL33/glCreateProgram)] 52 | 53 | (GL33/glAttachShader program vertex-shader) 54 | (GL33/glAttachShader program fragment-shader) 55 | (GL33/glLinkProgram program) 56 | 57 | (when (zero? (GL33/glGetProgrami program GL33/GL_LINK_STATUS)) 58 | (let [log (GL33/glGetProgramInfoLog program)] 59 | (throw (RuntimeException. (str "Program linking failed: " log))))) 60 | 61 | (GL33/glDeleteShader vertex-shader) 62 | (GL33/glDeleteShader fragment-shader) 63 | 64 | (GL33/glUseProgram program) 65 | 66 | (ShaderProgram. 67 | program 68 | {:model {:location (GL33/glGetUniformLocation program "model") 69 | :buffer (BufferUtils/createFloatBuffer 16)} 70 | :view {:location (GL33/glGetUniformLocation program "view") 71 | :buffer (BufferUtils/createFloatBuffer 16)} 72 | :projection {:location (GL33/glGetUniformLocation program "projection") 73 | :buffer (BufferUtils/createFloatBuffer 16)}}))) 74 | -------------------------------------------------------------------------------- /src/cgl/mesh.clj: -------------------------------------------------------------------------------- 1 | (ns cgl.mesh 2 | (:require [cgl.shader :as shader]) 3 | (:import (java.nio FloatBuffer IntBuffer) 4 | (org.joml Matrix4f) 5 | (org.lwjgl BufferUtils) 6 | (org.lwjgl.opengl GL33))) 7 | 8 | (defn- rotation* [model {:keys [x y z]}] 9 | (cond-> model 10 | x (.rotationX x) 11 | y (.rotationY y) 12 | z (.rotationZ z))) 13 | 14 | (defn- rotate* [model {:keys [x y z]}] 15 | (cond-> model 16 | x (.rotateX x) 17 | y (.rotateY y) 18 | z (.rotateZ z))) 19 | 20 | (defn ^FloatBuffer create-vertices-buffer [vertices] 21 | (-> (BufferUtils/createFloatBuffer (count vertices)) 22 | (.put vertices) 23 | (.flip))) 24 | 25 | (defn ^IntBuffer create-indices-buffer [indices] 26 | (-> (BufferUtils/createIntBuffer (count indices)) 27 | (.put indices) 28 | (.flip))) 29 | 30 | (defprotocol IRenderable 31 | (render [this])) 32 | 33 | (defprotocol ITransformable 34 | (rotation [this {:keys [x y z] :as rotation}]) 35 | (rotate [this {:keys [x y z] :as rotation-dt}])) 36 | 37 | (defrecord Mesh [vao vbo ebo indices model shader] 38 | IRenderable 39 | (render [this] 40 | (shader/set-uniform :m4f shader :model model) 41 | (GL33/glBindVertexArray vao) 42 | (GL33/glDrawElements GL33/GL_TRIANGLES (count indices) GL33/GL_UNSIGNED_INT 0)) 43 | 44 | ITransformable 45 | (rotation [this {:keys [x y z] :as rotation}] 46 | (rotation* model rotation)) 47 | (rotate [this {:keys [x y z] :as rotation-dt}] 48 | (rotate* model rotation-dt))) 49 | 50 | (defn create-mesh [{:keys [vertices indices shader]}] 51 | (let [vbo (GL33/glGenBuffers) 52 | vao (GL33/glGenVertexArrays) 53 | ebo (GL33/glGenBuffers) 54 | 55 | vertices-buffer (create-vertices-buffer vertices) 56 | indices-buffer (create-indices-buffer indices)] 57 | ; Bind VAO first 58 | (GL33/glBindVertexArray vao) 59 | 60 | ; Bind and fill VBO 61 | (GL33/glBindBuffer GL33/GL_ARRAY_BUFFER vbo) 62 | (GL33/glBufferData GL33/GL_ARRAY_BUFFER vertices-buffer GL33/GL_STATIC_DRAW) 63 | 64 | ; Bind and fill EBO 65 | (GL33/glBindBuffer GL33/GL_ELEMENT_ARRAY_BUFFER ebo) 66 | (GL33/glBufferData GL33/GL_ELEMENT_ARRAY_BUFFER indices-buffer GL33/GL_STATIC_DRAW) 67 | 68 | ; Position attribute (3 floats per vertex) 69 | (GL33/glVertexAttribPointer 0 3 GL33/GL_FLOAT false (int (* 6 Float/BYTES)) 0) 70 | (GL33/glEnableVertexAttribArray 0) 71 | 72 | ; Color attribute (3 floats per vertex) 73 | (GL33/glVertexAttribPointer 1 3 GL33/GL_FLOAT false (int (* 6 Float/BYTES)) (int (* 3 Float/BYTES))) 74 | (GL33/glEnableVertexAttribArray 1) 75 | 76 | (map->Mesh 77 | {:vao vao 78 | :vbo vbo 79 | :abo ebo 80 | :indices indices 81 | :model (.identity (Matrix4f.)) 82 | :shader shader}))) 83 | 84 | (defrecord Line [vao vbo vertices model shader] 85 | IRenderable 86 | (render [this] 87 | (shader/set-uniform :m4f shader :model model) 88 | (GL33/glBindVertexArray vao) 89 | (GL33/glDrawArrays GL33/GL_LINE_STRIP 0 (count vertices))) 90 | 91 | ITransformable 92 | (rotation [this {:keys [x y z] :as rotation}] 93 | (rotation* model rotation)) 94 | (rotate [this {:keys [x y z] :as rotation-dt}] 95 | (rotate* model rotation-dt))) 96 | -------------------------------------------------------------------------------- /src/cgl/window.clj: -------------------------------------------------------------------------------- 1 | (ns cgl.window 2 | (:import [org.lwjgl.glfw Callbacks GLFW GLFWErrorCallback] 3 | [org.lwjgl.opengl GL GL33] 4 | [org.lwjgl.system MemoryUtil] 5 | [org.lwjgl BufferUtils])) 6 | 7 | ;; ================= TIME ==================== 8 | 9 | (def current-time* (volatile! (GLFW/glfwGetTime))) 10 | 11 | (defn step [] 12 | (let [current-time (GLFW/glfwGetTime) 13 | dt (- current-time @current-time*) 14 | _ (vreset! current-time* current-time)] 15 | dt)) 16 | 17 | ;; ================= /TIME ==================== 18 | 19 | (def window* (atom nil)) 20 | 21 | (def keys-pressed* (atom {})) 22 | (def window-width* (atom 640)) 23 | (def window-height* (atom 480)) 24 | 25 | (defn update-viewport-and-projection [] 26 | ; On macOS with Retina displays, the framebuffer size may differ from the window size 27 | ; We need to get the actual framebuffer size for the viewport 28 | (let [width-buffer (BufferUtils/createIntBuffer 1) 29 | height-buffer (BufferUtils/createIntBuffer 1)] 30 | (GLFW/glfwGetFramebufferSize (long @window*) width-buffer height-buffer) 31 | (let [framebuffer-width (.get width-buffer 0) 32 | framebuffer-height (.get height-buffer 0)] 33 | ; Reset the atoms to the actual framebuffer size 34 | (reset! window-width* framebuffer-width) 35 | (reset! window-height* framebuffer-height) 36 | ; Update the viewport with the framebuffer size 37 | (GL33/glViewport 0 0 framebuffer-width framebuffer-height)))) 38 | 39 | (defn init-window [] 40 | (.set (GLFWErrorCallback/createPrint System/err)) 41 | 42 | (when-not (GLFW/glfwInit) 43 | (throw (IllegalStateException. "Unable to initialize GLFW"))) 44 | 45 | (GLFW/glfwDefaultWindowHints) 46 | (GLFW/glfwWindowHint GLFW/GLFW_CONTEXT_VERSION_MAJOR 3) 47 | (GLFW/glfwWindowHint GLFW/GLFW_CONTEXT_VERSION_MINOR 3) 48 | (GLFW/glfwWindowHint GLFW/GLFW_OPENGL_PROFILE GLFW/GLFW_OPENGL_CORE_PROFILE) 49 | (GLFW/glfwWindowHint GLFW/GLFW_OPENGL_FORWARD_COMPAT GL33/GL_TRUE) 50 | (GLFW/glfwWindowHint GLFW/GLFW_FLOATING 1) 51 | 52 | (let [window (GLFW/glfwCreateWindow (int @window-width*) (int @window-height*) "OpenGL 3.3 Window" MemoryUtil/NULL MemoryUtil/NULL)] 53 | (when (= window MemoryUtil/NULL) 54 | (GLFW/glfwTerminate) 55 | (throw (RuntimeException. "Failed to create the GLFW window"))) 56 | 57 | ; Set framebuffer size callback 58 | (GLFW/glfwSetFramebufferSizeCallback window 59 | (fn [window width height] 60 | (reset! window-width* width) 61 | (reset! window-height* height) 62 | (update-viewport-and-projection))) 63 | 64 | (reset! window* window))) 65 | 66 | (defn init-inputs [] 67 | ; Key callback - for single press events (like escape to exit) 68 | (GLFW/glfwSetKeyCallback @window* 69 | (fn [window key scancode action mods] 70 | (cond 71 | ; Exit on escape key release 72 | (and (= key GLFW/GLFW_KEY_ESCAPE) 73 | (= action GLFW/GLFW_RELEASE)) 74 | (GLFW/glfwSetWindowShouldClose window true) 75 | 76 | ; Track key press state for continuous movement 77 | (= action GLFW/GLFW_PRESS) 78 | (swap! keys-pressed* assoc key true) 79 | 80 | (= action GLFW/GLFW_RELEASE) 81 | (swap! keys-pressed* dissoc key))))) 82 | 83 | (defn init-context [] 84 | (GLFW/glfwMakeContextCurrent @window*) 85 | (GLFW/glfwSwapInterval 1)) 86 | 87 | (defn init [] 88 | (init-window) 89 | (init-inputs) 90 | (init-context)) 91 | 92 | (defn teardown [] 93 | (Callbacks/glfwFreeCallbacks @window*) 94 | (GLFW/glfwDestroyWindow @window*) 95 | (GLFW/glfwTerminate) 96 | (.free (GLFW/glfwSetErrorCallback nil))) 97 | -------------------------------------------------------------------------------- /src/cgl/geometry.clj: -------------------------------------------------------------------------------- 1 | (ns cgl.geometry 2 | (:import [org.joml Vector3f Vector2f Matrix4f])) 3 | 4 | ;; Path generation 5 | 6 | (defn create-line-path 7 | "Creates a straight line path between two points with a given number of segments. 8 | Returns a sequence of Vector3f points along the path." 9 | [start end num-segments] 10 | (let [start-vec (Vector3f. (.x start) (.y start) (.z start)) 11 | end-vec (Vector3f. (.x end) (.y end) (.z end)) 12 | direction (-> (Vector3f.) 13 | (.set end-vec) 14 | (.sub start-vec)) 15 | segment-length (/ (.length direction) num-segments) 16 | unit-direction (-> direction (.normalize))] 17 | 18 | (for [i (range (inc num-segments))] 19 | (-> (Vector3f.) 20 | (.set unit-direction) 21 | (.mul (float (* i segment-length))) 22 | (.add start-vec))))) 23 | 24 | (defn create-bezier-path 25 | "Creates a cubic Bezier curve path with the given control points and number of segments. 26 | Returns a sequence of Vector3f points along the path." 27 | [p0 p1 p2 p3 num-segments] 28 | (for [i (range (inc num-segments))] 29 | (let [t (/ i num-segments) 30 | t2 (* t t) 31 | t3 (* t2 t) 32 | mt (- 1 t) 33 | mt2 (* mt mt) 34 | mt3 (* mt2 mt) 35 | b0 (* mt3) ; (1-t)^3 36 | b1 (* 3 mt2 t) ; 3(1-t)^2 * t 37 | b2 (* 3 mt t2) ; 3(1-t) * t^2 38 | b3 (* t3)] ; t^3 39 | (-> (Vector3f.) 40 | (.set (* b0 (.x p0)) (* b0 (.y p0)) (* b0 (.z p0))) 41 | (.add (* b1 (.x p1)) (* b1 (.y p1)) (* b1 (.z p1))) 42 | (.add (* b2 (.x p2)) (* b2 (.y p2)) (* b2 (.z p2))) 43 | (.add (* b3 (.x p3)) (* b3 (.y p3)) (* b3 (.z p3))))))) 44 | 45 | (defn create-infinity-path 46 | "Creates a path in the shape of an infinity symbol (figure eight)" 47 | [num-segments radius-x radius-y] 48 | (let [t-step (/ (* 2 Math/PI) num-segments)] 49 | (vec 50 | (for [i (range num-segments)] 51 | (let [t (* i t-step) 52 | ; Parametric equation for a lemniscate (figure eight) 53 | ; x = radius-x * sin(t) / (1 + cos(t)^2) 54 | ; y = radius-y * sin(t) * cos(t) / (1 + cos(t)^2) 55 | ; z = 0 56 | denominator (+ 1.0 (Math/pow (Math/cos t) 2)) 57 | x (* radius-x (Math/sin t) (/ 1.0 denominator)) 58 | y (* radius-y (Math/sin t) (Math/cos t) (/ 1.0 denominator)) 59 | z 0.0] 60 | (Vector3f. x y z)))))) 61 | 62 | ;; Circle generation 63 | 64 | (defn create-circle 65 | "Creates a circle in 3D space with the given parameters: 66 | - center: Vector3f center point of the circle 67 | - radius: radius of the circle 68 | - normal: Vector3f normal vector to the circle plane 69 | - num-segments: number of segments to divide the circle into 70 | 71 | Returns a sequence of Vector3f points along the circle." 72 | [center radius normal num-segments] 73 | (let [normal-vec (-> (Vector3f.) 74 | (.set normal) 75 | (.normalize)) 76 | 77 | ; Create a coordinate system with normal as z-axis 78 | ; First, find an arbitrary vector perpendicular to normal 79 | perpendicular (if (> (Math/abs (.x normal-vec)) 0.1) 80 | (Vector3f. 0 1 0) ; Use up vector if normal is not close to up 81 | (Vector3f. 1 0 0)) ; Use right vector if normal is close to up 82 | 83 | ; Create two perpendicular vectors in the circle plane 84 | right-vec (-> (Vector3f.) 85 | (.set perpendicular) 86 | (.cross normal-vec) 87 | (.normalize) 88 | (.mul (float radius))) 89 | 90 | up-vec (-> (Vector3f.) 91 | (.set normal-vec) 92 | (.cross right-vec) 93 | (.normalize) 94 | (.mul (float radius)))] 95 | 96 | ; Generate points around the circle 97 | (for [i (range num-segments)] 98 | (let [angle (* i (/ (* 2 Math/PI) num-segments)) 99 | x-component (-> (Vector3f.) 100 | (.set right-vec) 101 | (.mul (float (Math/cos angle)))) 102 | y-component (-> (Vector3f.) 103 | (.set up-vec) 104 | (.mul (float (Math/sin angle))))] 105 | (-> (Vector3f.) 106 | (.set center) 107 | (.add x-component) 108 | (.add y-component)))))) 109 | 110 | ;; Tube generation 111 | 112 | (defn build-frame [^Vector3f tangent ^Vector3f prev-n] 113 | ;; Remove the component of prev-n that is parallel to the new tangent 114 | (let [n' (-> (Vector3f. prev-n) 115 | (.sub (-> (Vector3f.) 116 | (.set tangent) 117 | (.mul (.dot tangent prev-n)))) 118 | (.normalize)) 119 | b' (-> (Vector3f.) (.set tangent) (.cross n') (.normalize))] 120 | [n' b'])) ;; n' = normal, b' = binormal 121 | 122 | 123 | (defn create-tube 124 | [path radius segments] 125 | (let [v (Vector3f.) 126 | n (atom (Vector3f. 0 0 1)) ;; start-up vector = global Z 127 | ;; accumulate vertices, normals and indices 128 | {:keys [verts norms inds]} 129 | (reduce 130 | (fn [{:keys [verts norms inds] :as acc} [pNext pCurr idx]] 131 | ;; tangent 132 | (-> v (.set pNext) (.sub pCurr) (.normalize)) 133 | ;; transport the old normal so it stays ⟂ to new tangent 134 | (let [[^Vector3f n' b'] (build-frame v @n)] 135 | (reset! n n') ;; keep it for the next step 136 | ;; generate one ring 137 | (let [ring (for [j (range segments)] 138 | (let [f (* (/ (* 2 Math/PI) segments) j)] 139 | (-> (Vector3f.) 140 | (.set n') 141 | (.mul (Math/cos f)) 142 | (.add (-> (Vector3f.) (.set ^Vector3f b') (.mul (Math/sin f)))) 143 | (.mul ^float radius) 144 | (.add pCurr))))] 145 | {:verts (into verts ring) 146 | :norms (into norms 147 | (map (fn [vtx] (-> (Vector3f. ^Vector3f vtx) (.sub pCurr) (.normalize))) ring)) 148 | :inds (into inds 149 | (for [j (range segments)] 150 | (let [a (+ idx j) 151 | b (+ idx (mod (inc j) segments)) 152 | c (+ idx segments j) 153 | d (+ idx segments (mod (inc j) segments))] 154 | [a c b b c d])))}))) 155 | {:verts [] :norms [] :inds []} 156 | (map vector (rest path) path (iterate #(+ % segments) 0)))] 157 | {:vertices verts :normals norms :indices (apply concat inds)})) 158 | 159 | (defn tube-to-vertex-data 160 | "Convert tube data to a flat array suitable for OpenGL buffers. 161 | Each vertex has position (x,y,z) and normal (nx,ny,nz). 162 | 163 | Returns a map with: 164 | - :vertex-array - a float array of [x,y,z,nx,ny,nz, ...] 165 | - :index-array - an int array of triangle indices" 166 | [tube-data] 167 | (let [{:keys [vertices indices normals]} tube-data 168 | 169 | ; Create flattened vertex array with positions and normals 170 | vertex-count (count vertices) 171 | vertex-array (float-array (* vertex-count 6)) ; 3 for position, 3 for normal 172 | 173 | ; Fill vertex array 174 | _ (doseq [i (range vertex-count)] 175 | (let [^Vector3f vertex (nth vertices i) 176 | ^Vector3f normal (nth normals i) 177 | base-idx (* i 6)] 178 | (aset vertex-array (+ base-idx 0) (.x vertex)) 179 | (aset vertex-array (+ base-idx 1) (.y vertex)) 180 | (aset vertex-array (+ base-idx 2) (.z vertex)) 181 | (aset vertex-array (+ base-idx 3) (.x normal)) 182 | (aset vertex-array (+ base-idx 4) (.y normal)) 183 | (aset vertex-array (+ base-idx 5) (.z normal)))) 184 | 185 | ; Convert indices to primitive array 186 | index-array (int-array indices)] 187 | 188 | {:vertex-array vertex-array 189 | :index-array index-array})) 190 | 191 | ;; Helper functions for geometry transformation 192 | 193 | (defn transform-vertices 194 | "Apply a transformation matrix to a sequence of vertices" 195 | [vertices transform-matrix] 196 | (mapv 197 | (fn [vertex] 198 | (-> (Vector3f. (.x vertex) (.y vertex) (.z vertex)) 199 | (.mulPosition transform-matrix))) 200 | vertices)) 201 | -------------------------------------------------------------------------------- /src/cgl/core.clj: -------------------------------------------------------------------------------- 1 | (ns cgl.core 2 | (:require [nrepl.server] 3 | [cgl.camera :as camera] 4 | [cgl.geometry :as geo] 5 | [cgl.window :as win] 6 | [cgl.shader :as shader] 7 | [cgl.mesh :as mesh]) 8 | (:import [java.nio FloatBuffer IntBuffer] 9 | [org.lwjgl.glfw GLFW] 10 | [org.lwjgl.opengl GL GL33] 11 | [org.lwjgl BufferUtils] 12 | [org.joml Matrix4f])) 13 | 14 | (def vertex-shader-source 15 | "#version 330 core 16 | layout (location = 0) in vec3 aPos; 17 | layout (location = 1) in vec3 aColor; 18 | out vec3 ourColor; 19 | uniform mat4 model; 20 | uniform mat4 view; 21 | uniform mat4 projection; 22 | void main() { 23 | gl_Position = projection * view * model * vec4(aPos, 1.0); 24 | ourColor = aColor; 25 | }") 26 | 27 | (def fragment-shader-source 28 | "#version 330 core 29 | in vec3 ourColor; 30 | out vec4 FragColor; 31 | void main() { 32 | FragColor = vec4(ourColor, 1.0); 33 | }") 34 | 35 | (def camera* (atom nil)) 36 | (def shader-program* (atom nil)) 37 | 38 | (def objects* (atom {})) 39 | 40 | 41 | (defn create-cube [] 42 | (let [vertices (float-array [; positions ; colors 43 | -0.5 -0.5 -0.5 1.0 0.0 0.0 ; front bottom left 44 | 0.5 -0.5 -0.5 0.0 1.0 0.0 ; front bottom right 45 | 0.5 0.5 -0.5 0.0 0.0 1.0 ; front top right 46 | -0.5 0.5 -0.5 1.0 1.0 0.0 ; front top left 47 | -0.5 -0.5 0.5 1.0 0.0 1.0 ; back bottom left 48 | 0.5 -0.5 0.5 0.0 1.0 1.0 ; back bottom right 49 | 0.5 0.5 0.5 0.7 0.7 0.7 ; back top right 50 | -0.5 0.5 0.5 1.0 1.0 1.0]) ; back top left 51 | 52 | indices (int-array [; front face (z = -0.5) 53 | 0 1 2, 54 | 2 3 0, 55 | ; back face (z = 0.5) 56 | 4 5 6, 57 | 6 7 4, 58 | ; left face (x = -0.5) 59 | 0 3 7, 60 | 7 4 0, 61 | ; right face (x = 0.5) 62 | 1 5 6, 63 | 6 2 1, 64 | ; bottom face (y = -0.5) 65 | 0 4 5, 66 | 5 1 0, 67 | ; top face (y = 0.5) 68 | 3 2 6, 69 | 6 7 3])] 70 | 71 | (mesh/create-mesh 72 | {:vertices vertices 73 | :indices indices 74 | :shader @shader-program*}))) 75 | 76 | (defn create-curve-path [num-segments radius-x radius-y] 77 | ; Create an infinity-shaped path 78 | (let [path (geo/create-infinity-path num-segments radius-x radius-y)] 79 | 80 | ; Create a line visualization for the path 81 | (let [vertices (float-array (apply concat 82 | (for [point path] 83 | [(.x point) (.y point) (.z point) 84 | 1.0 1.0 1.0]))) ; White color for path 85 | vao (GL33/glGenVertexArrays) 86 | vbo (GL33/glGenBuffers)] 87 | 88 | (GL33/glBindVertexArray vao) 89 | (GL33/glBindBuffer GL33/GL_ARRAY_BUFFER vbo) 90 | 91 | (let [buffer (-> (BufferUtils/createFloatBuffer (count vertices)) 92 | (.put vertices) 93 | (.flip))] 94 | (GL33/glBufferData GL33/GL_ARRAY_BUFFER buffer GL33/GL_STATIC_DRAW)) 95 | 96 | ; Position attribute (3 floats) 97 | (GL33/glVertexAttribPointer 0 3 GL33/GL_FLOAT false (int (* 6 Float/BYTES)) 0) 98 | (GL33/glEnableVertexAttribArray 0) 99 | 100 | ; Color attribute (3 floats) 101 | (GL33/glVertexAttribPointer 1 3 GL33/GL_FLOAT false (int (* 6 Float/BYTES)) (int (* 3 Float/BYTES))) 102 | (GL33/glEnableVertexAttribArray 1) 103 | 104 | (mesh/map->Line 105 | {:vao vao 106 | :vbo vbo 107 | :vertices path 108 | :model (.identity (Matrix4f.)) 109 | :shader @shader-program*})))) 110 | 111 | (defn create-tube [path] 112 | (let [; Create tube along the path 113 | tube-radius 0.15 114 | circle-segments 16 115 | 116 | ; Make the path complete a full loop by adding the first point again 117 | ; This ensures we don't have gaps at the beginning/end 118 | closed-path (conj (vec path) (first path)) 119 | 120 | tube-data (geo/create-tube closed-path tube-radius circle-segments) 121 | 122 | ; Convert tube data to vertex arrays for OpenGL 123 | vertex-data (geo/tube-to-vertex-data tube-data) 124 | vertices (:vertex-array vertex-data) 125 | indices (:index-array vertex-data) 126 | 127 | ; Create a color array - we'll make a rainbow effect along the tube 128 | color-count (/ (count vertices) 6) ; 6 floats per vertex (position + normal) 129 | colors (float-array 130 | (apply concat 131 | (for [i (range color-count)] 132 | (let [t (/ i (float color-count)) 133 | ; Rainbow colors 134 | r (-> (Math/sin (* t Math/PI 2.0)) (+ 1.0) (/ 2.0)) 135 | g (-> (Math/sin (+ (* t Math/PI 2.0) (/ Math/PI 3.0))) (+ 1.0) (/ 2.0)) 136 | b (-> (Math/sin (+ (* t Math/PI 2.0) (/ (* 2.0 Math/PI) 3.0))) (+ 1.0) (/ 2.0))] 137 | [r g b])))) 138 | 139 | ; Create buffers 140 | vao (GL33/glGenVertexArrays) 141 | vbo-positions (GL33/glGenBuffers) 142 | vbo-colors (GL33/glGenBuffers) 143 | ebo (GL33/glGenBuffers) 144 | 145 | ; Create and fill vertex buffer 146 | vertices-buffer (-> (BufferUtils/createFloatBuffer (count vertices)) 147 | (.put vertices) 148 | (.flip)) 149 | 150 | ; Create and fill colors buffer 151 | colors-buffer (-> (BufferUtils/createFloatBuffer (count colors)) 152 | (.put colors) 153 | (.flip)) 154 | 155 | ; Create and fill element buffer 156 | indices-buffer ^IntBuffer (-> (BufferUtils/createIntBuffer (count indices)) 157 | (.put indices) 158 | (.flip))] 159 | 160 | ; Bind VAO 161 | (GL33/glBindVertexArray vao) 162 | 163 | ; Bind positions VBO 164 | (GL33/glBindBuffer GL33/GL_ARRAY_BUFFER vbo-positions) 165 | (GL33/glBufferData GL33/GL_ARRAY_BUFFER ^FloatBuffer vertices-buffer GL33/GL_STATIC_DRAW) 166 | 167 | ; Position attribute (3 floats) 168 | (GL33/glVertexAttribPointer 0 3 GL33/GL_FLOAT false (int (* 6 Float/BYTES)) 0) 169 | (GL33/glEnableVertexAttribArray 0) 170 | 171 | ; Bind colors VBO 172 | (GL33/glBindBuffer GL33/GL_ARRAY_BUFFER vbo-colors) 173 | (GL33/glBufferData GL33/GL_ARRAY_BUFFER colors-buffer GL33/GL_STATIC_DRAW) 174 | 175 | ; Color attribute (3 floats) 176 | (GL33/glVertexAttribPointer 1 3 GL33/GL_FLOAT false (int (* 3 Float/BYTES)) 0) 177 | (GL33/glEnableVertexAttribArray 1) 178 | 179 | ; Bind EBO 180 | (GL33/glBindBuffer GL33/GL_ELEMENT_ARRAY_BUFFER ebo) 181 | (GL33/glBufferData GL33/GL_ELEMENT_ARRAY_BUFFER indices-buffer GL33/GL_STATIC_DRAW) 182 | 183 | (mesh/map->Mesh 184 | {:vao vao 185 | :vbo vbo-positions 186 | :ebo ebo 187 | :indices indices 188 | :model (.identity (Matrix4f.)) 189 | :shader @shader-program*}))) 190 | 191 | (defn init-opengl [] 192 | (GL/createCapabilities) 193 | 194 | (println "OpenGL Vendor: " (GL33/glGetString GL33/GL_VENDOR)) 195 | (println "OpenGL Renderer: " (GL33/glGetString GL33/GL_RENDERER)) 196 | (println "OpenGL Version: " (GL33/glGetString GL33/GL_VERSION)) 197 | (println "GLSL Version: " (GL33/glGetString GL33/GL_SHADING_LANGUAGE_VERSION)) 198 | 199 | (GL33/glEnable GL33/GL_DEPTH_TEST) 200 | 201 | (win/update-viewport-and-projection) 202 | 203 | (reset! shader-program* (shader/create-shader-program 204 | vertex-shader-source 205 | fragment-shader-source)) 206 | 207 | (reset! camera* (camera/create-camera)) 208 | 209 | (swap! objects* assoc :path (create-curve-path 100 1.5 1.5)) 210 | (swap! objects* assoc :tube (create-tube (:vertices (:path @objects*)))) 211 | (swap! objects* assoc :cube1 (create-cube))) 212 | 213 | (defn process-input []) 214 | 215 | (defn render-loop [] 216 | (let [dt (win/step)] 217 | (GL33/glClearColor 0.1 0.1 0.2 1.0) 218 | (GL33/glClear (bit-or GL33/GL_COLOR_BUFFER_BIT GL33/GL_DEPTH_BUFFER_BIT)) 219 | 220 | (GL33/glUseProgram (:program @shader-program*)) 221 | (shader/set-uniforms @shader-program* {:camera @camera*}) 222 | 223 | (mesh/rotation (:tube @objects*) {:x (Math/cos @win/current-time*)}) 224 | (mesh/rotate (:cube1 @objects*) {:x dt}) 225 | 226 | (doseq [[_ obj] @objects*] 227 | (mesh/render obj)))) 228 | 229 | (defn run [] 230 | (win/init) 231 | (init-opengl) 232 | 233 | (while (not (GLFW/glfwWindowShouldClose @win/window*)) 234 | (process-input) 235 | (render-loop) 236 | (GLFW/glfwSwapBuffers @win/window*) 237 | (GLFW/glfwPollEvents)) 238 | 239 | (win/teardown)) 240 | 241 | (defn -main [& [repl?]] 242 | (when repl? 243 | (nrepl.server/start-server :port 7888) 244 | (println "nrepl server started on port 7888")) 245 | (run)) 246 | --------------------------------------------------------------------------------