Motion Illusion

A Motion-based Optical Ilussion

Vertical blocks appear to move diagonally when your eye follows horizontal blocks.

What on Earth is This?

About five years ago I was trying to fall asleep at 6am, with my head against a very reflective pillow, with the sun beaming through the windows. At first I noticed some kind of weird noise, kind of like the static you see when you can't get the channel on an antenna TV. After a few minutes concentrating I could make out large circular, cell-like shapes darting around, appearing never to collide, but to move and fade out.

I have tried to reproduce this visual effect a few times, but found it really hard to figure out the math to simulate the motion. I have not seen this effect since, but have seen something similar (with smaller particles) when looking out of an plane window, at a very bright blue sky.

What you are looking at is one of my failed simulations which I discovered to have an interesting optical illusion. You can see that there are only four possible directions, Up, Down, Left & Right.

But when you track the horizonal motion of a particle, the vertical particles appear to move diagonally!

I originally made this is QBasic about 2 years ago on a 386 using characters rather than graphics. Then I converted it to Visual-Basic, exported the frames to After-Effects and uploaded the results to YouTube. You can see that video here: YouTube - Neron FX.

Apparently this phenomenon is called Blue field entoptic phenomenon and is caused by the white blood cells moving through the capillaries in front of the retina. You can read more about this phenomenon on WikiPedia: Blue field entoptic phenomenon.

color bgCol=color(0, 200, 255);
color fgCol=color(255);
color trackingCol=color(255, 255, 0);

int kScale=10, blocks=100, tracking=1;
int kWidth=64, kHeight=48;
int[][] matrix=new int[kWidth][kHeight];
int[][] block=new int[blocks][4]; 
  
void setup(){
  size(kWidth*kScale, kHeight*kScale);
  noStroke();
  for(int j=0; j < blocks; j++){
    block[j][0]=int(random(kWidth));
    block[j][1]=int(random(kHeight));
    block[j][2]=int(random(4))+1;
    matrix[block[j][0]][block[j][1]]=1;
  }    
}

void draw(){
  background(bgCol);
    
  for( int j=0; j < blocks; j++){
    int x = int(block[j][0]);
    int y = int(block[j][1]);
    int d = int(block[j][2]);
    matrix[x][y]=0;
    
    int nX,nY;
    switch(d){
    case 1:nY=int(y-1);
        if(inRange(x,nY)==true){
        switch(matrix[x][nY]){
        case 1:d=getOpps(d);break;
        default:y-=1;break;}
        }else {d=getOpps(d);};break;
    case 2:nX=int(x+1);
        if(inRange(nX,y)==true){
        switch(matrix[nX][y]){
        case 1:d=getOpps(d);break;
        default:x+=1;break;}
        }else{d=getOpps(d);};break;
    case 3: nY=int(y+1);
        if(inRange(x,nY)==true){
        switch(matrix[x][nY]){
        case 1:d=getOpps(d);break;
        default:y+=1;break;}
        }else{d=getOpps(d);};break;
    case 4: nX=int(x-1);
        if(inRange(nX,y)==true){                  
        switch(matrix[nX][y]){
        case 1:d=getOpps(d);break;
        default:x-=1;break;}
        }else{d=getOpps(d);};break;
    }

    if(tracking==j){fill(trackingCol);
    rect(x*kScale,y*kScale,1*kScale,1*kScale);fill(fgCol);}
    else{rect(x*kScale,y*kScale,1*kScale,1*kScale);}
    matrix[x][y]=1;
    block[j][0]=x;
    block[j][1]=y;
    block[j][2]=d;
  }  
}

public boolean inRange(int x, int y){
   if(x > -1 && x < kWidth && y > -1 && y < kHeight){return true;}
    else{return false;}
}

int range=4;
public int getOpps(int val){
    int opposite=val+(range/2);
    if (opposite>range){opposite=opposite-range;}
    return int(opposite);
}

void keyPressed(){
    tracking=int(random(blocks));
}

No awards for code-readability here!