git.haldean.org plotter / master plot.lisp
master

Tree @master (Download .tar.gz)

plot.lisp @masterraw · history · blame

(ql:quickload :cl-svg)
(ql:quickload :cl-randist)

(setf w 300)
(setf h 300)
(setf c-x (/ w 2))
(setf c-y (/ h 2))

(setf iso-x-delta 20)
(setf iso-y-delta 6)

(setf bg "#124")
(setf fg "#FF0")

(setf n-x-dots (floor (/ w iso-x-delta)))
(setf n-y-dots (floor (/ h iso-y-delta)))
(setf xdlo (+ 1 (ceiling (- (/ n-x-dots 3)))))
(setf xdhi (floor (/ n-x-dots 3)))
(setf ydlo (+ 2 (ceiling (- (/ n-y-dots 4)))))
(setf ydhi (- (floor (/ n-y-dots 4)) 1))

(defun line (scene p1 p2 ox oy color)
  (let ((x1 (first p1)) (y1 (second p1))
        (x2 (first p2)) (y2 (second p2)))
    (cl-svg:draw
        scene
        (:line :x1 (+ x1 ox) :y1 (+ y1 oy) :x2 (+ x2 ox) :y2 (+ y2 oy))
        :stroke color :stroke-width 1 :stroke-linecap :round)))

(defun line-end (l)
  (let* ((i (caar l))
         (j (cadar l))
         (d (second l))
         (dx (cond
               ((= d 0) 0)
               ((= d 1) 1)
               ((= d 2) 1)
               ((= d 3) 0)
               ((= d 4) -1)
               ((= d 5) -1)))
         (dy (cond
               ((= d 0) -1)
               ((= d 1) 0)
               ((= d 2) 0)
               ((= d 3) 1)
               ((= d 4) 0)
               ((= d 5) 0)
               )))
    (list (+ i dx) (+ j dy))))

(defun gline (scene l &optional (ox 0) (oy 0) (c fg))
  (let* ((le (line-end l))
         (p1 (iso-grid (caar l) (cadar l)))
         (p2 (iso-grid (first le) (second le))))
    (line scene p1 p2 ox oy c)))

(defun dot (scene d)
  (let ((p (iso-grid (first d) (second d))))
    (let ((x (first p)) (y (second p)))
      (cl-svg:draw
          scene
          (:circle :cx x :cy y :r 2.5)
          :stroke fg :stroke-width 1 :fill bg))))

(defun iso-grid (x y)
  (let ((x_ (* iso-x-delta x))
        (y_ (* iso-y-delta (+ (* 2 y) (if (= (mod x 2) 0) 1 0)))))
    (list (+ (/ w 2) x_) (+ (/ h 2) y_))))

(defun dist (p1 p2)
  (sqrt (+ (expt (- (first p1) (first p2)) 2)
           (expt (- (second p1) (second p2)) 2))))

(defun nearest-on-grid (x y)
  (let* ((i (round (/ (- x (/ w 2)) iso-x-delta)))
         (tst (loop for j from ydlo to ydhi
                    collect (list (dist (iso-grid i j) (list x y)) (list i j)))))
    (second (reduce (lambda (a b) (if (< (first a) (first b)) a b)) tst))))

(defun random-point-normal ()
  (let* ((angle (random (* 2 pi)))
         (radius (randist:random-normal-ziggurat (/ (min w h) 3.0d0) 60.0d0)))
    (nearest-on-grid
     (+ c-x (* radius (cos angle)))
     (+ c-y (* radius (sin angle))))))
(defun random-point-uniform ()
  (list (+ xdlo (random (- xdhi xdlo)))
        (+ ydlo (random (- ydhi ydlo)))))
(defun random-point () (random-point-normal))

(defun dedupe (x &optional (accum '()))
  (cond
    ((null x) accum)
    ((member (car x) accum :test #'equal) (dedupe (cdr x) accum))
    (t (dedupe (cdr x) (cons (car x) accum)))
    ))

(defun gen-dots ()
  (dedupe (loop for i from 0 to 200 collect (random-point))))

(defun random-choice (lst) (nth (random (length lst)) lst))

(defun random-line-point (dots lines)
  (let ((line-ends (mapcar #'line-end lines)))
    (cond
      ((and (not (null line-ends)) (< (random 1.0) 0.4)) (random-choice line-ends))
      ((< (random 1.0) 0.7) (random-choice dots))
      (t (random-point))
      )))

(defun gen-lines (dots n attach)
  (dedupe
   (reduce
    (lambda (all ign) (cons (list (random-line-point dots all) (random 6)) all))
    (loop for i from 0 to n collect i)
    :initial-value nil)))

(defun gen (scene &key (use-dots t) (use-lines t))
  (let* ((dots (gen-dots))
         (root (cl-svg:make-group scene () nil)))
    (when use-lines (mapcar (lambda (l) (gline root l)) (gen-lines dots 200 nil)))
    (when use-dots (mapcar (lambda (d) (dot root d)) dots))
    root
    ))

(let ((scene (cl-svg:make-svg-toplevel 'cl-svg:svg-1.1-toplevel :height h :width w)))
  ; draw background
  (cl-svg:draw scene (:rect :x 0 :y 0 :width w :height h) :fill bg)
  (gen scene)
  (with-open-file (s #p"test.svg" :direction :output :if-exists :supersede)
    (cl-svg:stream-out s scene)))