From 734100d52d5657510cf1d1da3e6556d16f4eb405 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Sat, 21 Mar 2026 12:20:44 +0700 Subject: [PATCH] WASD + Free Camera look, fix double free, callbacks with exception handling --- src/nol/core.clj | 171 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 138 insertions(+), 33 deletions(-) diff --git a/src/nol/core.clj b/src/nol/core.clj index 7add083..141db3a 100644 --- a/src/nol/core.clj +++ b/src/nol/core.clj @@ -10,12 +10,16 @@ [org.lwjgl.glfw GLFW GLFWKeyCallbackI] [org.lwjgl.opengl GL GL11 GL20 GL30 GL45] [org.lwjgl.stb STBImage] - [org.joml Matrix4f] + [org.joml Matrix4f Vector3f] [org.lwjgl.system MemoryStack MemoryUtil])) (def ^LinkedBlockingQueue render-queue (LinkedBlockingQueue.)) -(def state (atom {:last-time 0.0 :window nil})) +(def state (atom {:last-time 0.0 + :camera-pos (Vector3f. 0.0 0.0 10.0) + :camera-yaw 0.0 + :camera-pitch 0.0 + :camera-target (Vector3f.)})) (def gl-thread (atom nil)) @@ -64,16 +68,91 @@ (.mTransformation) (ai-matrix->matrix4f))) -(defn node-matrix [^AINode c] - (-> c - (.mTransformation) - (ai-matrix->matrix4f) - (matrix4f->vectors))) - (def loaded-scene (atom nil)) (def uploaded-scene (atom nil)) (def shaders (atom nil)) +(def cam-speed 0.03) + +(defn camera-forward [^double yaw ^double pitch] + (Vector3f. (* (Math/cos pitch) (Math/sin yaw)) + (Math/sin pitch) + (- (* (Math/cos pitch) (Math/cos yaw))))) + +(defn camera-right [^double yaw] + (Vector3f. (Math/cos yaw) 0.0 (Math/sin yaw))) + +(defn camera-up [^Vector3f forward ^Vector3f right] + (-> (Vector3f. forward) (.cross right))) + +(defn- key-down? [window key] + (= (GLFW/glfwGetKey window key) GLFW/GLFW_PRESS)) + +(def ^Vector3f move-dir (Vector3f.)) + +(defn- handle-input! [window] + (let [{:keys [camera-yaw camera-pitch ^Vector3f camera-pos]} @state] + (when (key-down? window GLFW/GLFW_KEY_W) + (.add move-dir 0.0 0.0 1.0)) + (when (key-down? window GLFW/GLFW_KEY_S) + (.add move-dir 0.0 0.0 -1.0)) + (when (key-down? window GLFW/GLFW_KEY_A) + (.add move-dir -1.0 0.0 0.0)) + (when (key-down? window GLFW/GLFW_KEY_D) + (.add move-dir 1.0 0.0 0.0)) + (when (key-down? window GLFW/GLFW_KEY_Q) + (.add move-dir 0.0 1.0 0.0)) + (when (key-down? window GLFW/GLFW_KEY_E) + (.add move-dir 0.0 -1.0 0.0)) + (when (> (.length move-dir) 0.0) + (.normalize move-dir) + (let [forward ^Vector3f (camera-forward camera-yaw camera-pitch) + right ^Vector3f (camera-right camera-yaw) + up ^Vector3f (camera-up forward right) + velocity ^Vector3f (-> (Vector3f. right) + (.mul (.x move-dir)) + (.fma (.z move-dir) forward) + (.fma (.y move-dir) up) + (.mul ^double cam-speed))] + (.add camera-pos velocity))) + (.set move-dir 0.0 0.0 0.0))) + +(defn- handle-mouse-input! [window ^double xpos ^double ypos] + (when (:fps-mode @state) + (let [{:keys [^double camera-yaw ^double camera-pitch]} @state + last-cursor-x (double (or (:last-cursor-x @state) xpos)) + last-cursor-y (double (or (:last-cursor-y @state) ypos)) + sensitivity 0.001 + dx (- last-cursor-x xpos) + dy (- last-cursor-y ypos)] + (swap! state assoc + :last-cursor-x xpos + :last-cursor-y ypos + :camera-yaw (- camera-yaw (* dx sensitivity)) + :camera-pitch (+ camera-pitch (* dy sensitivity)))))) + +(defn- handle-mouse-buttons! [^long win button action mods] + (cond + (and (= button GLFW/GLFW_MOUSE_BUTTON_RIGHT) (= action GLFW/GLFW_PRESS)) + (let [] + (swap! state assoc :fps-mode true) + (GLFW/glfwSetInputMode win GLFW/GLFW_CURSOR GLFW/GLFW_CURSOR_DISABLED)) + + (and (= button GLFW/GLFW_MOUSE_BUTTON_RIGHT) (= action GLFW/GLFW_RELEASE)) + (let [] + (swap! state assoc :fps-mode false :last-cursor-x nil :last-cursor-y nil) + (GLFW/glfwSetInputMode win GLFW/GLFW_CURSOR GLFW/GLFW_CURSOR_NORMAL)) + + (and (= button GLFW/GLFW_MOUSE_BUTTON_LEFT) (= action GLFW/GLFW_PRESS)) + (let [cursor (GLFW/glfwCreateStandardCursor GLFW/GLFW_RESIZE_ALL_CURSOR)] + (swap! state assoc :orbit-mode true) + (GLFW/glfwSetCursor win cursor)) + + (and (= button GLFW/GLFW_MOUSE_BUTTON_LEFT) (= action GLFW/GLFW_RELEASE)) + (let [] + (swap! state assoc :orbit-mode false) + (GLFW/glfwSetCursor win MemoryUtil/NULL)))) + (defn render-scene [^MemoryStack stack scene node ^Matrix4f mtx] (let [buf (.mallocFloat stack 16) model-mtx (Matrix4f.)] @@ -114,19 +193,26 @@ ;; (swap! state assoc :last-time t) (try (when (and @uploaded-scene @shaders) + (handle-input! window) (let [scene @uploaded-scene {:keys [program uniforms]} @shaders] (GL20/glUseProgram program) (let [vbuf (.mallocFloat stack 16) pbuf (.mallocFloat stack 16) radius 10.0 - cam-x (* radius (Math/sin t)) - cam-z (* radius (Math/cos t)) + ;; cam-x (* radius (Math/sin t)) + ;; cam-z (* radius (Math/cos t)) + {:keys [^double camera-yaw ^double camera-pitch ^Vector3f camera-pos camera-target]} @state + fwd (camera-forward camera-yaw camera-pitch) + target ^Vector3f (:camera-target @state) + _ (doto ^Vector3f target + (.set camera-pos) + (.add fwd)) vm (-> (Matrix4f.) (.identity) - (.lookAt (float cam-x) (float 0.0) (float cam-z) - (float 0.0) (float 0.0) (float 0.0) - (float 0.0) (float 1.0) (float 0.0))) + (.lookAt (.x camera-pos) (.y camera-pos) (.z camera-pos) ;; eye (camera position) + (.x target) (.y target) (.z target) ;; center (where to look) + 0.0 1.0 0.0)) ;; up pm (-> (Matrix4f.) (.identity) (.perspective (float (Math/toRadians 45)) @@ -147,20 +233,16 @@ (GLFW/glfwSwapBuffers window))) (defn stop! [] - (with-gl - (when @uploaded-scene - (doseq [{:keys [^int vbo ^int vao ^int ebo]} (:gl-meshes @uploaded-scene)] - (GL45/glDeleteBuffers vbo) - (GL45/glDeleteBuffers ebo) - (GL30/glDeleteVertexArrays vao)) - (doseq [{:keys [^int gl-id]} (:gl-textures @uploaded-scene)] - (GL11/glDeleteTextures gl-id))) - (when @shaders - (GL20/glDeleteProgram (:program @shaders))) - (GLFW/glfwSetWindowShouldClose - (:window @state) true)) + (when @uploaded-scene + (doseq [{:keys [^int vbo ^int vao ^int ebo]} (:gl-meshes @uploaded-scene)] + (GL45/glDeleteBuffers vbo) + (GL45/glDeleteBuffers ebo) + (GL30/glDeleteVertexArrays vao)) + (doseq [{:keys [^int gl-id]} (:gl-textures @uploaded-scene)] + (GL11/glDeleteTextures gl-id))) (reset! uploaded-scene nil) - (reset! shaders nil)) + (reset! shaders nil) + (swap! state assoc :should-close true)) (defn -main [] (when-not (GLFW/glfwInit) @@ -170,7 +252,7 @@ (GLFW/glfwWindowHint GLFW/GLFW_CONTEXT_VERSION_MINOR 5) (GLFW/glfwWindowHint GLFW/GLFW_OPENGL_PROFILE GLFW/GLFW_OPENGL_CORE_PROFILE) - (let [window (GLFW/glfwCreateWindow 960 300 #_#_1900 1100 "NOL" MemoryUtil/NULL MemoryUtil/NULL)] + (let [window (GLFW/glfwCreateWindow 960 400 #_#_1900 1100 "NOL" MemoryUtil/NULL MemoryUtil/NULL)] (when (= window MemoryUtil/NULL) (GLFW/glfwTerminate) (throw (RuntimeException. "Failed to create window"))) @@ -188,8 +270,28 @@ (GLFW/glfwSetKeyCallback window (reify GLFWKeyCallbackI (invoke [_ win key scancode action mods] + #_(try + (handle-input! win key scancode action mods) + (catch Exception e + (println "GLFW Input error:" e))) (when (and (= key GLFW/GLFW_KEY_ESCAPE) (= action GLFW/GLFW_PRESS)) (swap! state assoc :should-close true))))) + (when (GLFW/glfwRawMouseMotionSupported) + (GLFW/glfwSetInputMode window GLFW/GLFW_RAW_MOUSE_MOTION GLFW/GLFW_TRUE)) + (GLFW/glfwSetCursorPosCallback window + (reify org.lwjgl.glfw.GLFWCursorPosCallbackI + (invoke [_ win xpos ypos] + (try + (handle-mouse-input! win xpos ypos) + (catch Exception e + (println "Cursor Pos Callback Exception:" e)))))) + (GLFW/glfwSetMouseButtonCallback window + (reify org.lwjgl.glfw.GLFWMouseButtonCallbackI + (invoke [_ win button action mods] + (try + (handle-mouse-buttons! win button action mods) + (catch Exception e + (println "Mouse Button Callback Exception:" e)))))) (loop [] (loop [] @@ -206,6 +308,7 @@ (MemoryStack/stackPop)))) (recur))) + (stop!) (GLFW/glfwDestroyWindow window) (GLFW/glfwTerminate))) @@ -326,7 +429,6 @@ (GL45/glTextureSubImage2D tex-id 0 0 0 ^int w ^int h GL11/GL_RGBA GL11/GL_UNSIGNED_BYTE ^ByteBuffer (:data %)) (GL45/glTextureParameteri tex-id GL11/GL_TEXTURE_MIN_FILTER GL11/GL_LINEAR) (GL45/glTextureParameteri tex-id GL11/GL_TEXTURE_MAG_FILTER GL11/GL_LINEAR) - (STBImage/stbi_image_free ^ByteBuffer (:data %)) (assoc % :gl-id tex-id)) (:textures scene))] (assoc scene :gl-textures gl-textures :gl-meshes gl-meshes))) @@ -342,17 +444,20 @@ :mv (GL20/glGetUniformLocation ^int program "uView") :mp (GL20/glGetUniformLocation ^int program "uProjection")}})) -(reset! loaded-scene (load-scene "assets/model.glb")) - (comment (start!) - (stop!) + (with-gl (stop!)) + (reset! loaded-scene + (do + (when @loaded-scene + (doseq [tex (:textures @loaded-scene)] + (when-let [data (:data tex)] + (STBImage/stbi_image_free ^ByteBuffer data)))) + (load-scene "assets/model.glb"))) (reset! uploaded-scene (with-gl (upload-scene! @loaded-scene))) (reset! shaders (with-gl (load-shaders! "shaders/base.vert" "shaders/base.frag"))) - (with-gl - (GL11/glClear (bit-or GL11/GL_COLOR_BUFFER_BIT GL11/GL_DEPTH_BUFFER_BIT))) (with-gl (GL11/glClearColor 0.392 0.584 0.929 1.0)) (with-gl