WASD + Free Camera look, fix double free, callbacks with exception handling

This commit is contained in:
Joseph Ferano 2026-03-21 12:20:44 +07:00
parent e9a19baa84
commit 734100d52d

View File

@ -10,12 +10,16 @@
[org.lwjgl.glfw GLFW GLFWKeyCallbackI] [org.lwjgl.glfw GLFW GLFWKeyCallbackI]
[org.lwjgl.opengl GL GL11 GL20 GL30 GL45] [org.lwjgl.opengl GL GL11 GL20 GL30 GL45]
[org.lwjgl.stb STBImage] [org.lwjgl.stb STBImage]
[org.joml Matrix4f] [org.joml Matrix4f Vector3f]
[org.lwjgl.system MemoryStack MemoryUtil])) [org.lwjgl.system MemoryStack MemoryUtil]))
(def ^LinkedBlockingQueue render-queue (LinkedBlockingQueue.)) (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)) (def gl-thread (atom nil))
@ -64,16 +68,91 @@
(.mTransformation) (.mTransformation)
(ai-matrix->matrix4f))) (ai-matrix->matrix4f)))
(defn node-matrix [^AINode c]
(-> c
(.mTransformation)
(ai-matrix->matrix4f)
(matrix4f->vectors)))
(def loaded-scene (atom nil)) (def loaded-scene (atom nil))
(def uploaded-scene (atom nil)) (def uploaded-scene (atom nil))
(def shaders (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] (defn render-scene [^MemoryStack stack scene node ^Matrix4f mtx]
(let [buf (.mallocFloat stack 16) (let [buf (.mallocFloat stack 16)
model-mtx (Matrix4f.)] model-mtx (Matrix4f.)]
@ -114,19 +193,26 @@
;; (swap! state assoc :last-time t) ;; (swap! state assoc :last-time t)
(try (try
(when (and @uploaded-scene @shaders) (when (and @uploaded-scene @shaders)
(handle-input! window)
(let [scene @uploaded-scene (let [scene @uploaded-scene
{:keys [program uniforms]} @shaders] {:keys [program uniforms]} @shaders]
(GL20/glUseProgram program) (GL20/glUseProgram program)
(let [vbuf (.mallocFloat stack 16) (let [vbuf (.mallocFloat stack 16)
pbuf (.mallocFloat stack 16) pbuf (.mallocFloat stack 16)
radius 10.0 radius 10.0
cam-x (* radius (Math/sin t)) ;; cam-x (* radius (Math/sin t))
cam-z (* radius (Math/cos 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.) vm (-> (Matrix4f.)
(.identity) (.identity)
(.lookAt (float cam-x) (float 0.0) (float cam-z) (.lookAt (.x camera-pos) (.y camera-pos) (.z camera-pos) ;; eye (camera position)
(float 0.0) (float 0.0) (float 0.0) (.x target) (.y target) (.z target) ;; center (where to look)
(float 0.0) (float 1.0) (float 0.0))) 0.0 1.0 0.0)) ;; up
pm (-> (Matrix4f.) pm (-> (Matrix4f.)
(.identity) (.identity)
(.perspective (float (Math/toRadians 45)) (.perspective (float (Math/toRadians 45))
@ -147,20 +233,16 @@
(GLFW/glfwSwapBuffers window))) (GLFW/glfwSwapBuffers window)))
(defn stop! [] (defn stop! []
(with-gl (when @uploaded-scene
(when @uploaded-scene (doseq [{:keys [^int vbo ^int vao ^int ebo]} (:gl-meshes @uploaded-scene)]
(doseq [{:keys [^int vbo ^int vao ^int ebo]} (:gl-meshes @uploaded-scene)] (GL45/glDeleteBuffers vbo)
(GL45/glDeleteBuffers vbo) (GL45/glDeleteBuffers ebo)
(GL45/glDeleteBuffers ebo) (GL30/glDeleteVertexArrays vao))
(GL30/glDeleteVertexArrays vao)) (doseq [{:keys [^int gl-id]} (:gl-textures @uploaded-scene)]
(doseq [{:keys [^int gl-id]} (:gl-textures @uploaded-scene)] (GL11/glDeleteTextures gl-id)))
(GL11/glDeleteTextures gl-id)))
(when @shaders
(GL20/glDeleteProgram (:program @shaders)))
(GLFW/glfwSetWindowShouldClose
(:window @state) true))
(reset! uploaded-scene nil) (reset! uploaded-scene nil)
(reset! shaders nil)) (reset! shaders nil)
(swap! state assoc :should-close true))
(defn -main [] (defn -main []
(when-not (GLFW/glfwInit) (when-not (GLFW/glfwInit)
@ -170,7 +252,7 @@
(GLFW/glfwWindowHint GLFW/GLFW_CONTEXT_VERSION_MINOR 5) (GLFW/glfwWindowHint GLFW/GLFW_CONTEXT_VERSION_MINOR 5)
(GLFW/glfwWindowHint GLFW/GLFW_OPENGL_PROFILE GLFW/GLFW_OPENGL_CORE_PROFILE) (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) (when (= window MemoryUtil/NULL)
(GLFW/glfwTerminate) (GLFW/glfwTerminate)
(throw (RuntimeException. "Failed to create window"))) (throw (RuntimeException. "Failed to create window")))
@ -188,8 +270,28 @@
(GLFW/glfwSetKeyCallback window (GLFW/glfwSetKeyCallback window
(reify GLFWKeyCallbackI (reify GLFWKeyCallbackI
(invoke [_ win key scancode action mods] (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)) (when (and (= key GLFW/GLFW_KEY_ESCAPE) (= action GLFW/GLFW_PRESS))
(swap! state assoc :should-close true))))) (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 []
(loop [] (loop []
@ -206,6 +308,7 @@
(MemoryStack/stackPop)))) (MemoryStack/stackPop))))
(recur))) (recur)))
(stop!)
(GLFW/glfwDestroyWindow window) (GLFW/glfwDestroyWindow window)
(GLFW/glfwTerminate))) (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/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_MIN_FILTER GL11/GL_LINEAR)
(GL45/glTextureParameteri tex-id GL11/GL_TEXTURE_MAG_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)) (assoc % :gl-id tex-id))
(:textures scene))] (:textures scene))]
(assoc scene :gl-textures gl-textures :gl-meshes gl-meshes))) (assoc scene :gl-textures gl-textures :gl-meshes gl-meshes)))
@ -342,17 +444,20 @@
:mv (GL20/glGetUniformLocation ^int program "uView") :mv (GL20/glGetUniformLocation ^int program "uView")
:mp (GL20/glGetUniformLocation ^int program "uProjection")}})) :mp (GL20/glGetUniformLocation ^int program "uProjection")}}))
(reset! loaded-scene (load-scene "assets/model.glb"))
(comment (comment
(start!) (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! uploaded-scene (with-gl (upload-scene! @loaded-scene)))
(reset! shaders (with-gl (load-shaders! "shaders/base.vert" "shaders/base.frag"))) (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 (with-gl
(GL11/glClearColor 0.392 0.584 0.929 1.0)) (GL11/glClearColor 0.392 0.584 0.929 1.0))
(with-gl (with-gl