Search code examples
javascriptsvggeometrygame-enginelight

SVG & JS - Light stopped by obstacle


I'm actually working on a little game and i'm trying to block map reveal with obstacle. Currently i have this : enter image description here

And i of course want this :

enter image description here

I tried SAT.js library which let me know when there is a collision but i don't really know what yo do with this. My first idea was to create a black (or white just for hide) polygon behind the obstacle but i'm sure the is a better solution.

For this "light" effect i use trick like this in SVG :

   <clippath id="clips" >
        <path class="view" tokenname="" d="M 1 1000 L 250 1 L 500 1000 z"/>
    </clippath>
 <image xlink:href="https://i.pinimg.com/originals/46/3a/12/463a1244e2e2627c53ff9806e2012c84.jpg" width="1920" height="1080" x="0" y="0" class="one"/>
    <image xlink:href="https://i.pinimg.com/originals/46/3a/12/463a1244e2e2627c53ff9806e2012c84.jpg" width="1920" height="1080" x="0" y="0" clip-path="url(#clips)"/>

Thank you for your help.


Solution

  • The next demo is inspired by this tutorial: 2D Raycasting;

    The main idea is using the white rays as a mask for your image. Please change the stroke-width in css for a dimmer / clearer image.

    Please move the mouse over the svg canvas to see it changing.

    Also read the comments in the code and do not forget to see Daniel Shiffman's tutorial.

    let SVG_NS = "http://www.w3.org/2000/svg";
    let SVG_XLINK = "http://www.w3.org/1999/xlink";
    let svg = document.querySelector("svg");
    
    let m = { x: 0, y: 0 };// the initial mouse position
    let record = 600;//the maxim length of a ray
    
    let walls = [];//the array of the walls 
    
    //list of points for the boundary (walls)
    let p1 = {
      x: 300,
      y: 100
    };
    let p2 = {
      x: 200,
      y: 300
    };
    
    let p3 = {
      x: 10,
      y: 300
    };
    let p4 = {
      x: 300,
      y: 200
    };
    
    class Particle {
      constructor(pos) {
        this.pos = pos;
        this.rays = [];
    
        for (let a = 0; a < 2 * Math.PI; a += Math.PI / 360) {
          this.rays.push(new Ray(this.pos, a));
        }
        // Uncomment to visualize the particle
        //let o = { cx: this.pos.x, cy: this.pos.y, r: 2, fill: "red" };
        //this.element = drawSVGelmt(o, "circle", svg);
      }
    
      show(m) {
        //update the position of the mouse
        this.update(m.x, m.y);
        //empty the group of rays
        rys.innerHTML = "";
        // Uncomment to visualize the particle
        //let o = { cx: this.pos.x, cy: this.pos.y };
        //this.element = updateSVGElmt(o, this.element);
    
        //first cast the rays
        for (let i = 0; i < this.rays.length; i++) {
          for (let w = 0; w < walls.length; w++) {
            this.rays[i].cast(walls[w]);
          }
        }
        
        
        //next draw the rays and append them to the rys group
        for (let i = 0; i < this.rays.length; i++) {
          //if the ray is intersecting one of the walls
          if (this.rays[i].intersection) {
            //set the attributes of the ray line
            var l = {};
            l.x1 = this.pos.x;
            l.y1 = this.pos.y;
            l.x2 = this.rays[i].intersection.x;
            l.y2 = this.rays[i].intersection.y;
            //draw ray and append it to the rys group
            this.line = drawSVGelmt(l, "line", rys);
          }
        }
      }
      update(x, y) {
        //update all the rays inside the rys
        this.rays.map((r) => {
          r.update();
        });
        //reset the position of the particle
        this.pos.x = x;
        this.pos.y = y;
      }
    }
    
    class Ray {
      constructor(pos, a) {
        this.pos = pos;//the starting point
        this.angle = a;//the angle of the ray
        this.maxLength = record;
        this.dir = {//the direction of a ray with an initial length of 1 unit
          x: Math.cos(this.angle),
          y: Math.sin(this.angle)
        };
      }
    
      cast(wall) {//cast the ray against the wall
        let p4 = {};
        p4.x = this.pos.x + this.dir.x;
        p4.y = this.pos.y + this.dir.y;
        
        // see if the ray is intersecting the wall
        let Intersection = Intersect(wall.a, wall.b, this.pos, p4);
    
        if (Intersection) {
          let length = dist(this.pos, Intersection);
          if (length < this.maxLength) {
            this.maxLength = length;
            this.intersection = Intersection;
          }
        }
      }
    
    
      // update the ray when the mouse (m) is moving
      update() {
        this.pos = { x: m.x, y: m.y };
        this.intersection = false;
        this.maxLength = record;
      }
    }
    
    
    //the walls
    class Boundary {
      constructor(a, b) {
        this.a = a;
        this.b = b;
      }
    
      show() {
        // the attributes for the line
        let o = {};
        o.x1 = this.a.x;
        o.y1 = this.a.y;
        o.x2 = this.b.x;
        o.y2 = this.b.y;
        o.class = "boundary";// a class to style the walls 
        //draw and append the wall line
        this.line = drawSVGelmt(o, "line", wls);
      }
    }
    
    walls.push(new Boundary(p1, p2));
    walls.push(new Boundary(p3, p4));
    
    walls.forEach((w) => {
      w.show();
    });
    
    let p = new Particle(m);
    p.show(m);
    
    //HELPERS
    
    
    // a function to get the intersection point of 2 lines, Returns the point of intersection or false if there is no intersection point
    function Intersect(p1, p2, p3, p4) {
      var denominator =
        (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
      var ua =
        ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) /
        denominator;
      var ub =
        ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) /
        denominator;
      var x = p1.x + ua * (p2.x - p1.x);
      var y = p1.y + ua * (p2.y - p1.y);
      if (ua > 0 && ua < 1 && ub > 0 /*&& ub < 1*/) {
        return { x: x, y: y };
      } else {
        return false;
      }
    }
    
    
    // a function to draw an svg element
    function drawSVGelmt(o, tag, parent) {
      let elmt = document.createElementNS(SVG_NS, tag);
      for (let name in o) {
        if (o.hasOwnProperty(name)) {
          elmt.setAttributeNS(null, name, o[name]);
        }
      }
      parent.appendChild(elmt);
      return elmt;
    }
    // a function to update an svg element
    function updateSVGElmt(o, element) {
      for (var name in o) {
        if (o.hasOwnProperty(name)) {
          element.setAttribute(name, o[name]);
        }
      }
      return element;
    }
    
    
    //a function to get the angle of a line from p1 to p2
    function getAngle(p1, p2) {
      let dx = p2.x - p1.x;
      let dy = p2.y - p1.y;
      let angle = Math.atan2(dy, dx);
      return angle < 0 ? 2 * Math.PI + angle : angle;
    }
    
    //a function to get the distance between 2 points: p1 & p2
    function dist(p1, p2) {
      let dx = p2.x - p1.x;
      let dy = p2.y - p1.y;
      return Math.sqrt(dx * dx + dy * dy);
    }
    
    
    //a function to get the mouse position inside an svg element
    function oMousePosSVG(e) {
      let p = svg.createSVGPoint();
      p.x = e.clientX;
      p.y = e.clientY;
      let ctm = svg.getScreenCTM().inverse();
      p = p.matrixTransform(ctm);
      return p;
    }
    
    svg.addEventListener("mousemove", function (e) {
      m = oMousePosSVG(e);
      p.show(m);
    });
    *{margin:0;padding:0;}
    body{background:black;}
    svg{border:1px solid silver;width:min(100vw,100vh)}
    
    line{stroke:white;stroke-width:1px}
    .boundary{stroke:white;stroke-width:2px}
    <svg viewBox="0 0 400 400">
      <g id="wls"></g>
      <mask id="m">
      <g id="rys"></g>
      </mask>
      
      <image xlink:href="https://assets.codepen.io/222579/darwin300.jpg" height="400" width="400" mask="url(#m)"></image>
    </svg>