From 359755aad1badd8cd60d68d6da9887ae19796d51 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Tue, 7 Apr 2026 08:58:44 +0800 Subject: [PATCH] Fully functioning orbit camera --- src/nol/core.clj | 127 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 92 insertions(+), 35 deletions(-) diff --git a/src/nol/core.clj b/src/nol/core.clj index 141db3a..980c9cb 100644 --- a/src/nol/core.clj +++ b/src/nol/core.clj @@ -10,18 +10,25 @@ [org.lwjgl.glfw GLFW GLFWKeyCallbackI] [org.lwjgl.opengl GL GL11 GL20 GL30 GL45] [org.lwjgl.stb STBImage] - [org.joml Matrix4f Vector3f] + [org.joml Matrix4f Vector2f Vector3f] [org.lwjgl.system MemoryStack MemoryUtil])) (def ^LinkedBlockingQueue render-queue (LinkedBlockingQueue.)) (def state (atom {:last-time 0.0 + :drag-start-pos nil :camera-pos (Vector3f. 0.0 0.0 10.0) :camera-yaw 0.0 :camera-pitch 0.0 - :camera-target (Vector3f.)})) + :orbit-yaw 0.0 + :orbit-pitch 0.0 + :last-orbit-x 0.0 + :last-orbit-y 0.0 + :focus-point (Vector3f.) + :camera-target (Vector3f.) + :projection-matrix})) -(def gl-thread (atom nil)) +(defonce gl-thread (atom nil)) (defmacro with-gl [& body] `(let [result# (promise)] @@ -68,11 +75,11 @@ (.mTransformation) (ai-matrix->matrix4f))) -(def loaded-scene (atom nil)) -(def uploaded-scene (atom nil)) -(def shaders (atom nil)) +(defonce loaded-scene (atom nil)) +(defonce uploaded-scene (atom nil)) +(defonce shaders (atom nil)) -(def cam-speed 0.03) +(def cam-speed 0.04) (defn camera-forward [^double yaw ^double pitch] (Vector3f. (* (Math/cos pitch) (Math/sin yaw)) @@ -89,8 +96,27 @@ (= (GLFW/glfwGetKey window key) GLFW/GLFW_PRESS)) (def ^Vector3f move-dir (Vector3f.)) +(def ^Vector3f focus-distance 5.0) -(defn- handle-input! [window] +(defn- handle-key-actions! [_win key _scancode action _mods] + (when (= action GLFW/GLFW_PRESS) + (when (= key GLFW/GLFW_KEY_ESCAPE) + (swap! state assoc :should-close true)) + (when (= key GLFW/GLFW_KEY_R) + (swap! state assoc + :camera-pos (Vector3f. 0.0 0.0 10.0) + :camera-yaw 0.0 + :camera-pitch 0.0 + :focus-point (Vector3f. 0.0 0.0 0.0) + :camera-target (Vector3f.))))) + +(defn- drag-detected? [^double x1 ^double y1 ^double x2 ^double y2] + (let [threshold 10.0 + dx (- x2 x1) + dy (- y2 y1)] + (< threshold (+ (* dx dx) (* dy dy))))) + +(defn- handle-keyboard-state! [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)) @@ -117,7 +143,24 @@ (.add camera-pos velocity))) (.set move-dir 0.0 0.0 0.0))) -(defn- handle-mouse-input! [window ^double xpos ^double ypos] +(defn- handle-mouse-move! [_window ^double xpos ^double ypos] + (when (:orbit-mode @state) + (let [{:keys [^double orbit-yaw ^double orbit-pitch]} @state + last-orbit-x (double (or (:last-orbit-x @state) xpos)) + last-orbit-y (double (or (:last-orbit-y @state) ypos)) + sensitivity 0.005 + dx (- last-orbit-x xpos) + dy (- last-orbit-y ypos)] + (swap! state assoc + :last-orbit-x xpos + :last-orbit-y ypos + :orbit-yaw (- orbit-yaw (* dx sensitivity)) + :orbit-pitch (-> (- orbit-pitch (* dy sensitivity)) + (max (- (/ Math/PI 2))) + (min (/ Math/PI 2)))))) + #_(when-let [cursor-pos ^Vector2f (:drag-start-pos @state)] + (when (and (drag-detected? xpos ypos (.x cursor-pos) (.y cursor-pos))) + (swap! state assoc :orbit-mode true))) (when (:fps-mode @state) (let [{:keys [^double camera-yaw ^double camera-pitch]} @state last-cursor-x (double (or (:last-cursor-x @state) xpos)) @@ -131,6 +174,12 @@ :camera-yaw (- camera-yaw (* dx sensitivity)) :camera-pitch (+ camera-pitch (* dy sensitivity)))))) +(defn- get-current-cursor-pos! ^Vector2f [^MemoryStack stack ^long window] + (let [xbuf (.mallocDouble stack 1) + ybuf (.mallocDouble stack 1)] + (GLFW/glfwGetCursorPos window xbuf ybuf) + (Vector2f. (.get xbuf 0) (.get ybuf 0)))) + (defn- handle-mouse-buttons! [^long win button action mods] (cond (and (= button GLFW/GLFW_MOUSE_BUTTON_RIGHT) (= action GLFW/GLFW_PRESS)) @@ -144,13 +193,14 @@ (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)) + (do + (when (mods :ctrl) + (swap! state assoc :orbit-mode true)) + (swap! state assoc :drag-start-pos (get-current-cursor-pos! (MemoryStack/stackGet) win))) (and (= button GLFW/GLFW_MOUSE_BUTTON_LEFT) (= action GLFW/GLFW_RELEASE)) (let [] - (swap! state assoc :orbit-mode false) + (swap! state assoc :orbit-mode false :drag-start-pos nil :last-orbit-x nil :last-orbit-y nil) (GLFW/glfwSetCursor win MemoryUtil/NULL)))) (defn render-scene [^MemoryStack stack scene node ^Matrix4f mtx] @@ -160,7 +210,7 @@ (.get model-mtx buf) (when-let [mesh-indices (seq (:meshes node))] (let [{:keys [gl-meshes gl-textures]} scene - {:keys [program uniforms]} @shaders] + {:keys [_program uniforms]} @shaders] (doseq [idx mesh-indices :let [gl-mesh (nth gl-meshes idx) mesh (:mesh gl-mesh) @@ -190,37 +240,42 @@ (GL11/glClear GL11/GL_COLOR_BUFFER_BIT) (GL11/glClear (bit-or GL11/GL_COLOR_BUFFER_BIT GL11/GL_DEPTH_BUFFER_BIT)) - ;; (swap! state assoc :last-time t) (try (when (and @uploaded-scene @shaders) - (handle-input! window) + (handle-keyboard-state! 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)) {: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) + fwd ^Vector3f (camera-forward camera-yaw camera-pitch) + camera-pos (if (:orbit-mode @state) + (let [fwd ^Vector3f (camera-forward (:orbit-yaw @state) (:orbit-pitch @state)) + orbit-pos (Vector3f. ^Vector3f (:focus-point @state))] + (.add orbit-pos (.mul (Vector3f. fwd) 8.0))) + camera-pos) + target (:camera-target @state) _ (doto ^Vector3f target (.set camera-pos) (.add fwd)) - vm (-> (Matrix4f.) + target (if (:orbit-mode @state) + (:focus-point @state) + target) + view (-> (Matrix4f.) (.identity) (.lookAt (.x camera-pos) (.y camera-pos) (.z camera-pos) ;; eye (camera position) - (.x target) (.y target) (.z target) ;; center (where to look) + (.x ^Vector3f target) (.y ^Vector3f target) (.z ^Vector3f target) ;; center (where to look) 0.0 1.0 0.0)) ;; up - pm (-> (Matrix4f.) + proj (-> (Matrix4f.) (.identity) (.perspective (float (Math/toRadians 45)) (float (/ (float w) (float h))) (float 0.1) (float 100.0)))] - (.get vm vbuf) - (.get pm pbuf) + (.get view vbuf) + (.get proj pbuf) (GL20/glUniformMatrix4fv ^int (:mv uniforms) false vbuf) (GL20/glUniformMatrix4fv ^int (:mp uniforms) false pbuf) (render-scene stack scene (:hiearchy scene) (Matrix4f.))))) @@ -252,7 +307,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 400 #_#_1900 1100 "NOL" MemoryUtil/NULL MemoryUtil/NULL)] + (let [window (GLFW/glfwCreateWindow 960 500 #_#_1900 1100 "NOL" MemoryUtil/NULL MemoryUtil/NULL)] (when (= window MemoryUtil/NULL) (GLFW/glfwTerminate) (throw (RuntimeException. "Failed to create window"))) @@ -270,29 +325,31 @@ (GLFW/glfwSetKeyCallback window (reify GLFWKeyCallbackI (invoke [_ win key scancode action mods] - #_(try - (handle-input! win key scancode action mods) + (try + (handle-key-actions! 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))))) + (println "Key Callback Exception:" e)))))) (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) + (handle-mouse-move! 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) + (let [mods (cond-> #{} + (pos? (bit-and mods GLFW/GLFW_MOD_SHIFT)) (conj :shift) + (pos? (bit-and mods GLFW/GLFW_MOD_CONTROL)) (conj :ctrl) + (pos? (bit-and mods GLFW/GLFW_MOD_ALT)) (conj :alt) + (pos? (bit-and mods GLFW/GLFW_MOD_SUPER)) (conj :super))] + (handle-mouse-buttons! win button action mods)) (catch Exception e (println "Mouse Button Callback Exception:" e)))))) - (loop [] (loop [] (when-let [f (.poll render-queue)] @@ -461,6 +518,6 @@ (with-gl (GL11/glClearColor 0.392 0.584 0.929 1.0)) (with-gl - (GL11/glClearColor 0.0 0.0 0.0 1.0)) + (GL11/glClearColor 0.2 0.25 0.3 1.0)) :-)