Particles Tunneling Through Solid Earth

No, I'm not going to write about neutrinos, or anything else that is 'real'. Pixels are the particles of today's post. Their universe is very different than ours, it is solid in all directions, it is quantized(at a different level than ours).

At each moment, there are a countable number of choices.

To be a pixel is to be blind to all but your nearest neighbors, to be trapped in a matrix far more insidious and probably less pleasing than in the movie. Movement is always a cooperation between at least two particles here, there's no vacuum after all. So let's see what actions a pixel particle can perform:

  1. Move to any of the surrounding coordinates, swapping places with each pixel it displaces.
  2. Change Color
  3. Stop being active

Luckily, those things aren't too much of a tall order.

(require :img-genner)


(defparameter *ffmpeg-writer* (uiop:launch-program "ffmpeg -f png_pipe -i - -y  particles.webp" :input :stream))
(defparameter *image* (make-image 640 480))
(defstruct particle (x 0) (y 0))
(defparameter *particles* 
  (loop for i from 0 below 900 collect (make-particle :x (random 640) :y (random 480))))
(defun copy-pixel(x1 y1 x2 y2)
  (set-pixel *image* x1 y1 (get-pixel *image* x2 y2)))
(defun wrap(value min max)
  (if (< value min)
      (1- max)
      (if (>= value max)
          min value)))
(defun move-particle(particle dx dy)
  (incf (particle-x particle) dx)
  (setf (particle-x particle) (wrap (particle-x particle) 0 640))
  (incf (particle-y particle) dy)
  (setf (particle-y particle) (wrap (particle-y particle) 0 480))
  (swap-pixel (particle-x particle) (particle-y particle)
              (wrap (- (particle-x particle) dx) 0 640)
              (wrap (- (particle-y particle) dy) 0 480)))
(defun change-color(particle)
  (set-pixel *image* (particle-x particle) (particle-y particle) (get-random-color)))
(defun die(particle)
  (setf *particles* (remove particle *particles* :test #'eq)))

;;;; This is how we're going to choose what each particle does:
(defun perform-action(particle)
  (case (random 25)
    ((1 2 0 3 4 5 9  18 20 19 21) (move-particle particle (- (random 3) 1) (- (random 3) 1)))
    ((10 12 13 14 15 11 17 6 16 22 23 24) (change-color particle))
    ((7 ) (die particle))))
(loop while *particles*
      for count from 0
      do(loop for i in *particles*
              do(perform-action i))
      do(img-genner:save-image *image* (uiop:process-info-input *ffmpeg-writer*))
      do(print count))
(uiop:close-streams *ffmpeg-writer*)

It's mostly the same structure as before, and with this, we get the following

Kinda sparse huh? Well, we could add more particles, or we could make the particles leave streaks, so we'll do the latter, because that's what I want to write 🙂

If we replace swap-pixel with copy-pixel, then we get this:

If the first animation is a bunch of pixels tunneling through darkness, this is them devouring it.

That was generated with the following code:

(require :img-genner)


(defparameter *ffmpeg-writer* (uiop:launch-program "ffmpeg -f png_pipe -i - -y -pix_fmt yuv420p -compression_level 6  -qscale 50  particles.webm" :input :stream))
(defparameter *image* (make-image 640 480))
(defstruct particle (x 0) (y 0))
(defparameter *particles* 
  (loop for i from 0 below 60 collect (make-particle :x (random 640) :y (random 480))))
(defun copy-pixel(x1 y1 x2 y2)
  (set-pixel *image* x1 y1 (get-pixel *image* x2 y2)))
(defun wrap(value min max)
  (if (< value min)
      (1- max)
      (if (>= value max)
          min value)))
(defun move-particle(particle dx dy)
  (incf (particle-x particle) dx)
  (setf (particle-x particle) (wrap (particle-x particle) 0 640))
  (incf (particle-y particle) dy)
  (setf (particle-y particle) (wrap (particle-y particle) 0 480))
  (copy-pixel (particle-x particle) (particle-y particle)
              (wrap (- (particle-x particle) dx) 0 640)
              (wrap (- (particle-y particle) dy) 0 480)))
(defun change-color(particle)
  (set-pixel *image* (particle-x particle) (particle-y particle) (get-random-color)))
(defun die(particle)
  (setf *particles* (remove particle *particles* :test #'eq)))

;;;; This is how we're going to choose what each particle does:
(defun perform-action(particle)
  (case (random 25)
    ((1 2 0 3 4 5 9  18 20 19 21) (move-particle particle (- (random 3) 1) (- (random 3) 1)))
    ((10 12 13 14 15 11 17 6 16 22 23 24) (change-color particle))
    ;((7 ) (die particle))
    ))
(loop while *particles*
      for count from 0
      repeat 1500
      do(loop for i in *particles*
              do(perform-action i))
      do(img-genner:save-image *image* (uiop:process-info-input *ffmpeg-writer*))
      do(print count))
(uiop:close-streams *ffmpeg-writer*)

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.