• fullscreen
  • Block.pde
  • DynLightGrid.pde
  • Light.pde
  • Line.pde
  • Math.pde
  • Vector blocks = new Vector();
    boolean gridTouchedThisClick;
    
    void renderBlocks() {
    ////////////////////////////////////
      stroke(128);
      fill(96);
      for (int b = 0; b < blocks.size(); b++) {
        ((Block)blocks.get(b)).render();
      }
    }
    
    void touchGrid(float px, float py) {
    ////////////////////////////////////
      if (!gridTouchedThisClick) {
        
        boolean blockRemoved = false;
        
        // find the XY position on the grid
        int x = int(px / TILE_SIZE) * TILE_SIZE;
        int y = int(py / TILE_SIZE) * TILE_SIZE;
        
        // if there is a block here, remove it
        for (int i = 0; i < blocks.size(); i++) {
          PVector v = ((Block)blocks.get(i)).corner1;
          if ((int)v.x == x && (int)v.y == y) {
            blocks.remove(i);
            blockRemoved = true;
          }
        }
        if (!blockRemoved) {
          blocks.add(new Block(x / TILE_SIZE, y / TILE_SIZE));
        }
        gridTouchedThisClick = true;
      }
    }
    
    Vector getBlockLines() {
    ////////////////////////////////////
      Vector lines = new Vector();
      for (int b = 0; b < blocks.size(); b++) {
        lines.addAll(((Block)blocks.get(b)).lines);
      }
      return lines;
    }
    
    class Block {
    ////////////////////////////////////
      int x, y;
      Vector lines = new Vector();
      PVector corner1, corner2, corner3, corner4;
      
      Block(int px, int py) {
        x = px * TILE_SIZE;
        y = py * TILE_SIZE;
        corner1 = new PVector(x, y); // upper left
        corner2 = new PVector(x + TILE_SIZE, y); // upper right
        corner3 = new PVector(x + TILE_SIZE, y + TILE_SIZE); // bottom right
        corner4 = new PVector(x, y + TILE_SIZE); // bottom left
        lines.add(new Line(corner1, corner2));
        lines.add(new Line(corner2, corner3));
        lines.add(new Line(corner3, corner4));
        lines.add(new Line(corner4, corner1));
      }
      
      void render() {
        rect(corner1.x, corner1.y, TILE_SIZE, TILE_SIZE);
      }
      
    }
    
    
    // Dynamic Light Grid
    // Application to test real-world interaction of light against blocks on a grid.
    // Light works as intended, but accuracy is limited by number of rays used.
    // Ted Brown # January 6, 2011
    
    int TILE_SIZE = 32;
    int MAP_SIZE = 19;
    PFont font;
    Light light;
    boolean MOUSE_CONTROL = false;
    boolean SHOW_RAYS = false;
    
    void setup() {
    ////////////////////////////////////
      font = createFont("System", 12);
      textFont(font);
      size(TILE_SIZE * MAP_SIZE, TILE_SIZE * MAP_SIZE);
      blocks.add(new Block(1, 1));
      blocks.add(new Block(1, 2));
      blocks.add(new Block(10, 12));
      blocks.add(new Block(17, 17));
      blocks.add(new Block(13, 8));
      blocks.add(new Block(4, 18));
      blocks.add(new Block(5, 5));
      light = new Light(9, 9);
    }
    
    void draw() {
    ////////////////////////////////////
      if (MOUSE_CONTROL) {
        light.pos.set(mouseX - TILE_SIZE/2, mouseY - TILE_SIZE/2, 0);
        light.source.set(mouseX, mouseY, 0);
      }
      
      // Draw Grid
      background(0, 0, 64);
      stroke(64, 64, 128);
      for (int x = 0; x < MAP_SIZE; x++) {
        line(x * TILE_SIZE, 0, x * TILE_SIZE, height);
        line(0, x * TILE_SIZE, width, x * TILE_SIZE);
      }
      
      light.render();
      renderBlocks();
      fill(255);
      text("Hold LMB to move light\nRight Click to add/remove blocks\nSpacebar to toggle rays\nFPS " + frameRate, 6, 12);
    }
    
    void keyReleased() {
    ////////////////////////////////////
      switch (key) {
        case ' ':
          if (SHOW_RAYS) SHOW_RAYS = false;
          else SHOW_RAYS = true;
          break;
      }
    }
    
    void mousePressed() {
    ////////////////////////////////////
      if (mouseButton == LEFT) MOUSE_CONTROL = true;
      if (mouseButton == RIGHT) touchGrid(mouseX, mouseY);
    }
    
    void mouseReleased() {
    ////////////////////////////////////
      if (mouseButton == LEFT) MOUSE_CONTROL = false;
      if (mouseButton == RIGHT) gridTouchedThisClick = false;
    }
    
    
    class Light {
    
      PVector pos; // this is the position on the grid
      PVector source; // this is the screen position for the light
      float radius;
      float angle;
      float angleSpread;
      
      Light(int x, int y) {
        pos = new PVector(x * TILE_SIZE, y * TILE_SIZE);
        source = new PVector(pos.x + TILE_SIZE/2, pos.y + TILE_SIZE/2);
        radius = 400;
        angle = 0;
        angleSpread = 360; // 30
      }
      
      void render() {
        ////////////////////////////////////
        stroke(255);
        fill(255, 255, 0);
        ellipse(pos.x + TILE_SIZE/2, pos.y + TILE_SIZE/2, TILE_SIZE, TILE_SIZE);
        shine();
      }
    
      void shine() {
        ////////////////////////////////////
        Vector lightPoints = new Vector();
    
        // For each ray...
        for (float current_angle = angle - angleSpread/2; current_angle < angle + angleSpread / 2; current_angle += 1) {
    
          float x1 = cos(radians(current_angle)) * radius + source.x;
          float y1 = sin(radians(current_angle)) * radius + source.y;
          PVector rayEndUnblocked = new PVector(x1, y1);
          PVector rayEndBlocked = null;
          PVector rayLineIntersection = null;
    
          // For each block...
          for (int b = 0; b < blocks.size(); b++) {
    
            // Get its lines...
            Vector lines = ((Block)blocks.get(b)).lines;
            
            // ...and test collision
            for (int i = 0; i < lines.size(); i++) {
              
              Line ln = (Line)lines.get(i);
              
              // ...if the line's normal is facing the ray.
              if (rayCanStrikeFacing(radians(current_angle), ln.facing)) {
                
                // If the ray strikes the line, a PVector is returned.  Otherwise, it is null.
                rayLineIntersection = segIntersection(source.x, source.y, rayEndUnblocked.x, rayEndUnblocked.y, ln.v1.x, ln.v1.y, ln.v2.x, ln.v2.y);
                
                // If it is not null...
                if (rayLineIntersection != null) {
                  // And it hasn't yet been assigned a closer vector...
                  if (rayEndBlocked == null) {
                    // Assign the intersecting vector.
                    rayEndBlocked = new PVector(rayLineIntersection.x, rayLineIntersection.y, rayLineIntersection.z);
                  }
                  // But if it has already been assigned a closer vector...
                  else {
                    // See if this new vector is closer before assigning it
                    if (pos.dist(rayLineIntersection) < pos.dist(rayEndBlocked)) {
                      rayEndBlocked.set(rayLineIntersection);
                    }
                  }
                }
              }
              
            }
    
          }
    
          // Add a point of light      
          if (rayEndBlocked != null) {
            lightPoints.add(rayEndBlocked);
          } else {
            lightPoints.add(rayEndUnblocked);
          }
          
        }
    
        // Render the light according to preference
        if (SHOW_RAYS) {
          for (int i = 0; i < lightPoints.size(); i++) {
            PVector v1 = (PVector)lightPoints.get(i);
            line(source.x, source.y, v1.x, v1.y);
          }
        }
        else {
          // Draw a polygon using the points of light
          fill(255, 128);
          noStroke();
          beginShape();
      
          // Put a point at the source if it's not an omni light, which by definition is any light without an angle of 360
          if (angleSpread < 360) vertex(source.x, source.y);
          
          // Build a polygon using the points which were created
          for (int i = 0; i < lightPoints.size(); i++) {
            PVector v1 = (PVector)lightPoints.get(i);
            vertex(v1.x, v1.y);
          }
          endShape();
        }
        
      }
        
    }
    
    class Line {
    ////////////////////////////////////
    // A Line is something that blocks Light
    
      PVector v1, v2;
      float facing; // this is the normal in radians
      
      Line (PVector n1, PVector n2) {
        v1 = new PVector(n1.x, n1.y);
        v2 = new PVector(n2.x, n2.y);
        // Find the normal using a clockwise coordinate system
        facing = getTheta(v1, v2) + radians(90);
      }
      
      void render() {
        line(v1.x, v1.y, v2.x, v2.y);
      }
    
    }
    
    
    PVector segIntersection(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
    ////////////////////////////////////
    // http://wiki.processing.org/w/Line-Line_intersection
    // @author Ryan Alexander 
    
      float bx = x2 - x1; 
      float by = y2 - y1; 
      float dx = x4 - x3; 
      float dy = y4 - y3;
      float b_dot_d_perp = bx * dy - by * dx;
      if(b_dot_d_perp == 0) {
        return null;
      }
      float cx = x3 - x1;
      float cy = y3 - y1;
      float t = (cx * dy - cy * dx) / b_dot_d_perp;
      if(t < 0 || t > 1) {
        return null;
      }
      float u = (cx * by - cy * bx) / b_dot_d_perp;
      if(u < 0 || u > 1) { 
        return null;
      }
      return new PVector(x1+t*bx, y1+t*by);
    }
    
    boolean rayCanStrikeFacing (float rayTheta, float facingTheta) {
    ////////////////////////////////////
      float reflection = abs(rayTheta - facingTheta);
      if (reflection > HALF_PI && reflection < TWO_PI - HALF_PI) return true;
      else return false;
    }
    
    float getTheta (float x1, float y1, float x2, float y2) {
    ////////////////////////////////////
      return atan2(y1 - y2, x1 - x2);
    }
    
    float getTheta (PVector v1, PVector v2) {
    ////////////////////////////////////
      return atan2(v1.y - v2.y, v1.x - v2.x);
    }
    
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    Ted Brown

    Dynamic Light 2D

    Add to Faves Me Likey@! 10
    You must login/register to add this sketch to your favorites.

    Scatters rays that collide with arbitrary lines (represented here as blocks), then builds a light volume from the updated rays.

    It already has backface culling, but I am definitely open to any and all optimization suggestions! I have another version, a bit more complex, where the boxes know if they are hit by light.

    Uses Line-Line Intersection by Ryan Alexander. Inspired by a forum post by TheAustech on The Chaos Rift.

    this can be used for visibility analysis in urban planning, similar to http://vimeo.com/16907168. thx for sharing!
    You need to login/register to comment.