(cl:defpackage :ocm.nvg (:use :cl :cffi-c-ref :alexandria) (:export run)) (cl:in-package :ocm.nvg) (defun aget (alist key) (cdr (assoc key alist :test #'equal))) (defun agets (alist &rest keys) (reduce #'aget keys :initial-value alist)) (defun agetter (&rest keys) (lambda (alist) (apply 'agets alist keys))) (defun tags (obj) (let ((tags (ocm.osm::obj-tags obj))) (when (functionp tags) (setf tags (funcall tags) (ocm.osm::obj-tags obj) tags)) tags)) (defun merge-tags (obj parent) (append obj parent)) (defvar *context* nil "NanoVG Context") (defvar *window-width* 640.0) (defvar *window-height* 480.0) (defvar *frame-width* 640.0) (defvar *frame-height* 480.0) (defvar *pixel-ratio* 1.0) (defvar *scale* 3f0) (defvar *pos-x* 0f0) (defvar *pos-y* 0f0) (defvar *objects* nil "List of OSM referenced objects to render") (defun fix-multipoly (rel) (labels ((id (obj) (ocm.osm::obj-id obj)) (try-attach (way ways) (loop with way-nodes = (ocm.osm::way-refs way) with way-first = (car way-nodes) with way-last = (car (last way-nodes)) for prev in ways for prev-nodes = (ocm.osm::way-refs prev) for prev-first = (car prev-nodes) for prev-last = (car (last prev-nodes)) when (and way-first way-last prev-first prev-last) do (when-let (nodes (cond ((= (id way-first) (id prev-last)) (append prev-nodes (cdr way-nodes))) ((= (id way-last) (id prev-first)) (append way-nodes (cdr prev-nodes))) ((= (id way-first) (id prev-first)) (append (reverse way-nodes) (cdr prev-nodes))) ((= (id way-last) (id prev-last)) (append prev-nodes (cdr (reverse way-nodes)))) )) (setf (ocm.osm::way-refs prev) nodes) (return prev))))) (loop with inner-ways and outer-ways for mem in (ocm.osm::rel-memids rel) for mem-role in (ocm.osm::rel-roles rel) for mem-type in (ocm.osm::rel-types rel) for parts = (if (equal "inner" mem-role) inner-ways outer-ways) when (ocm.osm::way-p mem) unless (try-attach mem parts) do (let ((new-way (ocm.osm::make-way :id (id mem) :refs (copy-list (ocm.osm::way-refs mem))))) (if (equal "inner" mem-role) (push new-way inner-ways) (push new-way outer-ways))) finally (return (ocm.osm::make-rel :id (id rel) :tags (ocm.osm::obj-tags rel) :memids (append outer-ways inner-ways) :roles (append (loop for w in outer-ways collect "outer") (loop for w in inner-ways collect "inner")) :types (append (loop for w in outer-ways collect :way) (loop for w in inner-ways collect :way))))))) (defun osm-fix (obj) (cond ((multipoly-p obj) (fix-multipoly obj)) (:otherwise obj))) (defun load-test-objects () (setf *objects* nil) (ocm.osm::with-related (obj "spb.osm.pbf" '( (nil "natural" "water") (nil "natural" "wood") (nil "natural" "scrub") (nil "highway" "primary") (nil "highway" "secondary"))) (push (osm-fix obj) *objects*))) (defparameter +granularity+ (expt 10 8)) (defun node-x (node) (float (/ (ocm.osm::node-lon node) +granularity+))) (defun node-y (node) (float (- (/ (ocm.osm::node-lat node) +granularity+)))) (defun render-path (nodes) (%nvg:begin-path *context*) (let ((start (car nodes))) (%nvg:move-to *context* (node-x start) (node-y start)) (loop for node in (cdr nodes) do (%nvg:line-to *context* (node-x node) (node-y node))))) (defparameter +highway-color+ '(150 100 0 255)) (defparameter +highway-primary-color+ '(150 30 0 255)) (defparameter +strokes+ `( (((:way "lanes" "5")) :color ,+highway-primary-color+ :width 0.005) (((:way "lanes" "3")) :color ,+highway-primary-color+ :width 0.004) (((:way "highway" "primary")) :color ,+highway-primary-color+ :width 0.003) (((:way "highway")) :color ,+highway-color+ :width 0.001))) (defun apply-stroke (tags) (when-let (options (find-match +strokes+ tags :way)) (destructuring-bind (&key color width) options (when color (c-with ((n-color %nvg:color)) (%nvg:stroke-color *context* (apply '%nvg:rgba (n-color &) color)))) (when width (%nvg:stroke-width *context* width))))) (defun render-line (nodes tags) (render-path nodes) (apply-stroke tags) (%nvg:stroke *context*)) (defparameter +water-color+ '(0 0 250 255)) (defparameter +wood-color+ '(0 250 0 255)) (defparameter +fills+ `( (((nil "natural" "water")) :color ,+water-color+) (((nil "natural" "scrub") (nil "natural" "wood")) :color ,+wood-color+) )) (defun find-match (query-value tags type) (loop for (query . value) in query-value when (ocm.osm::query-match2 query type tags) do (return value))) (defun apply-fill (tags) (when-let (fill (find-match +fills+ tags :way)) (destructuring-bind (&key color paint) fill (when color (c-with ((n-color %nvg:color)) (%nvg:fill-color *context* (apply '%nvg:rgba (n-color &) color)))) (when paint)))) (defun render-poly (nodes tags &optional hole) (render-path nodes) (%nvg:close-path *context*) (if hole (%nvg:path-winding *context* (cffi:foreign-bitfield-value '%nvg:solidity :hole)) (apply-fill tags)) (%nvg:fill *context*)) (defparameter +closed-lines+ '((:way "highway"))) (defun closed-p (nodes) (let ((first (first nodes)) (last (car (last nodes)))) (and first last (= (if (ocm.osm::obj-p first) (ocm.osm::obj-id first) first) (if (ocm.osm::obj-p last) (ocm.osm::obj-id last) last))))) (defun area-p (nodes tags) (and (closed-p nodes) (not (ocm.osm::query-match2 +closed-lines+ :way tags)))) (defun multipoly-p (rel) (and (ocm.osm::rel-p rel) (equal (aget (tags rel) "type") "multipolygon"))) (defun render-way (nodes tags) (if (area-p nodes tags) (render-poly nodes tags) (render-line nodes tags))) (defun render-multipoly (rel) (loop for mem in (ocm.osm::rel-memids rel) for mem-role in (ocm.osm::rel-roles rel) for mem-type in (ocm.osm::rel-types rel) when (ocm.osm::way-p mem) ;; skip non-ways do (render-poly (ocm.osm::way-refs mem) (tags rel) (equal "inner" mem-role)))) (defun render-objects (objects) (loop for obj in objects do (typecase obj (ocm.osm::way (render-way (ocm.osm::way-refs obj) (tags obj))) (ocm.osm::rel (when (multipoly-p obj) (render-multipoly obj)))))) (defun render () (%nvg:begin-frame *context* *window-width* *window-height* *pixel-ratio*) (%nvg:transform *context* *scale* 0f0 0f0 *scale* *pos-x* *pos-y*) (render-objects *objects*) (%nvg:end-frame *context*)) (defvar *pressed-keys* nil) (defun process-keys () (when (member %glfw:+key-left+ *pressed-keys*) (incf *pos-x* +translate-step+)) (when (member %glfw:+key-right+ *pressed-keys*) (decf *pos-x* +translate-step+)) (when (member %glfw:+key-up+ *pressed-keys*) (incf *pos-y* +translate-step+)) (when (member %glfw:+key-down+ *pressed-keys*) (decf *pos-y* +translate-step+)) (when (member %glfw:+key-minus+ *pressed-keys*) (decf *scale* +scale-step+)) (when (member %glfw:+key-equal+ *pressed-keys*) (incf *scale* +scale-step+))) (defparameter +translate-step+ 80f0) (defparameter +scale-step+ 0.3) (glfw:define-key-callback on-keys (window key scancode action mod-keys) (declare (ignorable window key scancode action mod-keys)) (format t "KEY: ~{~A ~}~%" (list key scancode action mod-keys)) (when (eql action %glfw:+press+) (pushnew key *pressed-keys*) (cond ((or (eql key %glfw:+key-escape+) (eql key %glfw:+key-q+)) (%glfw:set-window-should-close window %glfw:+true+)) ((eql key %glfw:+key-left+) (incf *pos-x* +translate-step+)) ((eql key %glfw:+key-right+) (decf *pos-x* +translate-step+)) ((eql key %glfw:+key-up+) (incf *pos-y* +translate-step+)) ((eql key %glfw:+key-down+) (decf *pos-y* +translate-step+)) ((eql key %glfw:+key-minus+) (decf *scale* +scale-step+)) ((eql key %glfw:+key-equal+) (incf *scale* +scale-step+)))) (when (eql action %glfw:+release+) (setf *pressed-keys* (delete key *pressed-keys*))) (print (list *scale* *pos-x* *pos-y*))) (glfw:define-framebuffer-size-callback on-fb-size (window width height) (declare (ignorable window)) (setf *frame-width* (float width) *frame-height* (float height) *pixel-ratio* (/ *frame-width* *window-width*)) (gl:viewport 0 0 *frame-width* *frame-height*)) (glfw:define-window-size-callback on-win-size (window width height) (declare (ignorable window)) (setf *window-width* (float width) *window-height* (float height) *pixel-ratio* (/ *frame-width* *window-width*))) (glfw::define-error-callback on-error (err desc) (format *error-output* "GLFW error ~A: ~A~%" err desc)) (defun create-window () (if (uiop:featurep :bodge-gl2) (glfw:with-window-hints ((%glfw:+context-version-major+ 2) (%glfw:+context-version-minor+ 1) (%glfw:+depth-bits+ 24) (%glfw:+stencil-bits+ 8)) (%glfw:create-window 640 480 "Hello NanoVG 2" nil nil)) (glfw:with-window-hints ((%glfw:+context-version-major+ 3) (%glfw:+context-version-minor+ 3) (%glfw:+opengl-profile+ %glfw:+opengl-core-profile+) (%glfw:+opengl-forward-compat+ %glfw:+true+) (%glfw:+depth-bits+ 24) (%glfw:+stencil-bits+ 8)) (%glfw:create-window 640 480 "Hello NanoVG 3" nil nil)))) (defun main () ;; Initializing window and OpenGL context (glfw:with-init () (%glfw:set-error-callback (cffi:callback on-error)) (c-let ((window %glfw:window :from (create-window)) (width :int :alloc t) (height :int :alloc t)) (when (cffi:null-pointer-p (window &)) (%glfw:terminate) (error "Failed to create GLFW window")) (%glfw:make-context-current (window &)) ;; Mangling GL function pointers (glad:init) ;; Set event callbacks (%glfw:set-key-callback (window &) (cffi:callback on-keys)) (%glfw:set-framebuffer-size-callback (window &) (cffi:callback on-fb-size)) (%glfw:set-window-size-callback (window &) (cffi:callback on-win-size)) ;; Get initial values (%glfw:get-framebuffer-size (window &) (width &) (height &)) (setf *frame-width* (float width) *frame-height* (float height)) (%glfw:get-window-size (window &) (width &) (height &)) (setf *window-width* (float width) *window-height* (float height) *pixel-ratio* (/ *frame-width* *window-width*)) (gl:viewport 0 0 *frame-width* *frame-height*) ;; reset (setf *scale* 540f0 *pos-x* -162910.0 *pos-y* 324540.0) ;; Creating NanoVG context (let ((*context* (nvg:make-context))) (unwind-protect (loop while (= (%glfw:window-should-close (window &)) 0) do (gl:clear-color 1f0 1f0 1f0 1f0) (gl:clear :color-buffer-bit :depth-buffer-bit :stencil-buffer-bit) ;; Step (process-keys) ;; Actually drawing into nanovg context and onto display (render) (%glfw:swap-buffers (window &)) (%glfw:poll-events)) ;; Cleaning up NanoVG context (nvg:destroy-context *context*)))))) (defun run () (main) ;; (trivial-main-thread:with-body-in-main-thread (:blocking t) ;; (float-features:with-float-traps-masked t ;; (main))) )