从点开始的视线

rmp*_*tmp 3 javascript canvas html5-canvas

需要从点创建简单的视线.这条线的长度将适应画布的大小.如果线指向任何对象(圆形,矩形等),则必须在此之后中断.我不知道如何描述这个,但行为应该是这样的.这就像激光瞄准视频游戏.

演示jsfiddle.目标线有红色.我认为该行必须具有动态长度,具体取决于我将指向它的位置.

var canvas = document.querySelector("canvas");
canvas.width = 500;
canvas.height = 300;
var ctx = canvas.getContext("2d"),

line = {
	x1: 190, y1: 170,
    x2: 0, y2: 0,
    x3: 0, y3: 0
};
var length = 100;

var circle = {
	x: 400,
    y: 70
};

window.onmousemove = function(e) {
  //get correct mouse pos
  var rect = ctx.canvas.getBoundingClientRect(),
      x = e.clientX - rect.left,
      y = e.clientY - rect.top;

  // calc line angle
  var dx = x - line.x1,
      dy = y - line.y1,
      angle = Math.atan2(dy, dx);

  //Then render the line using 100 pixel radius:
  line.x2 = line.x1 - length * Math.cos(angle);
  line.y2 = line.y1 - length * Math.sin(angle);
  
  line.x3 = line.x1 + canvas.width * Math.cos(angle);
  line.y3 = line.y1 + canvas.width * Math.sin(angle);
 
  // render
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  ctx.beginPath();
  ctx.moveTo(line.x1, line.y1);
  ctx.lineTo(line.x2, line.y2);
  ctx.strokeStyle = '#333';
  ctx.stroke();
  
  ctx.beginPath();
  ctx.moveTo(line.x1, line.y1);
  ctx.lineTo(line.x3, line.y3);
  ctx.strokeStyle = 'red';
  ctx.stroke();
  
  ctx.beginPath();
  ctx.arc(circle.x, circle.y, 20, 0, Math.PI * 2, true);
  ctx.fillStyle = '#333';
  ctx.fill();
  
}
Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>
Run Code Online (Sandbox Code Playgroud)

Bli*_*n67 5

射线铸造

给出的答案是一个很好的答案,但这个问题更适合光线投射像解决方案,我们只对截距的距离而不是实际的拦截点感兴趣.我们每个投射光线只需要一个点,因此不计算点会减少数学运算,从而减少CPU负荷,从而每秒产生更多光线和物体.

射线是定义起点的点和表示射线方向的归一化矢量.因为光线使用的是一个单位长度的归一化向量,所以很多计算都被简化了,因为1*任何东西都没有变化.

问题还在于寻找最接近的截距,因此截距函数返回距离光线原点的距离.如果没有找到拦截,则Infinity返回以允许进行有效的距离比较.每个数字都小于Infinity.

JavaScript的一个很好的特性是它允许除以零并且如果发生这种情况则返回无穷大,这进一步降低了解决方案的复杂性.此外,如果截距发现负截距,则意味着该对象位于该光线投射原点之后,因此也将返回无穷大.

首先让我们通过创建函数来定义我们的对象.它们都是临时对象.

// Ad Hoc method for ray to set the direction vector
var updateRayDir = function(dir){
    this.nx = Math.cos(dir);
    this.ny = Math.sin(dir);
    return this;
}
// Creates a ray objects from 
// x,y start location
// dir the direction in radians
// len the rays length
var createRay = function(x,y,dir,len){
    return ({
       x : x,
       y : y,
       len : len,
       setDir : updateRayDir, // add function to set direction
    }).setDir(dir);
}
Run Code Online (Sandbox Code Playgroud)

一个圆圈

// returns a circle object 
// x,y is the center
// radius is the you know what..
// Note r2 is radius squared if you change the radius remember to set r2 as well
var createCircle = function(x , y, radius){
     return {
         x : x,
         y : y,
         rayDist : rayDist2Circle, // add ray cast method
         radius : radius,
         r2 : radius * radius,   // ray caster needs square of radius may as well do it here
     };
}
Run Code Online (Sandbox Code Playgroud)

一堵墙

注意我在演示中更改了墙上的代码

// Ad Hoc function to change the wall position
// x1,y1 are the start coords
// x2,y2 are the end coords
changeWallPosition = function(x1, y1, x2, y2){
    this.x = x1;
    this.y = y1;
    this.vx = x2 - x1;
    this.vy = y2 - y1;
    this.len = Math.hypot(this.vx,this.vy);
    this.nx = this.vx / this.len;
    this.ny = this.vy / this.len;
    return this;
}

// returns a wall object
// x1,y1 are the star coords
// x2,y2 are the end coords
var createWall = function(x1, y1, x2, y2){
    return({
       x : x1, y : y1,
       vx : x2 - x1,
       vy : y2 - y1,
       rayDist : rayDist2Wall, // add ray cast method

       setPos : changeWallPosition,
    }).setPos(x1, y1, x2, y2);
}
Run Code Online (Sandbox Code Playgroud)

所以那些是对象,它们可以是静态的,或者通过圆圈移动应该有一个setRadius函数,因为我添加了一个保存半径的正方形的属性,但是如果你使用那个代码,我会把它留给你.

现在拦截功能.

雷拦截

重要的事情.在演示中,这些函数绑定到对象,以便光线投射代码不必知道它正在检查的对象类型.

到圆的距离.

// Self evident 
// returns a distance or infinity if no valid solution
var rayDist2Circle = function(ray){
    var vcx, vcy, v;
    vcx = ray.x - this.x; // vector from ray to circle
    vcy = ray.y - this.y;
    v = -2 * (vcx * ray.nx + vcy * ray.ny);
    v -= Math.sqrt(v * v - 4 * (vcx * vcx + vcy * vcy - this.r2)); // this.r2 is the radius squared
    // If there is no solution then Math.sqrt returns NaN we should return Infinity
    // Not interested in intercepts in the negative direction so return infinity
    return isNaN(v) || v < 0 ? Infinity : v / 2;
}
Run Code Online (Sandbox Code Playgroud)

到墙的距离

// returns the distance to the wall
// if no valid solution then return Infinity
var rayDist2Wall = function(ray){
    var x,y,u;
    rWCross = ray.nx * this.ny - ray.ny * this.nx;
    if(!rWCross) { return Infinity; } // Not really needed.
    x = ray.x - this.x; // vector from ray to wall start
    y = ray.y - this.y;
    u = (ray.nx * y - ray.ny * x) / rWCross; // unit distance along normalised wall
    // does the ray hit the wall segment
    if(u < 0 || u > this.len){ return Infinity;}  /// no
    // as we use the wall normal and ray normal the unit distance is the same as the
    u = (this.nx * y - this.ny * x) / rWCross;
    return u < 0 ? Infinity : u;  // if behind ray return Infinity else the dist
}
Run Code Online (Sandbox Code Playgroud)

这涵盖了对象.如果你需要一个内向外的圆(你想要内表面然后改变圆光线函数的倒数第二行v +=而不是v -=

射线投射

现在只需要针对光线迭代所有对象并将距离保持在最近的对象上即可.将光线设置为该距离,您就完成了.

// Does a ray cast.
// ray the ray to cast
// objects an array of objects 
var castRay = function(ray,objects)
    var i,minDist;

    minDist = ray.len; // set the min dist to the rays length
    i = objects.length; // number of objects to check
    while(i > 0){
        i -= 1;
        minDist = Math.min(objects[i].rayDist(ray),minDist);
    }
    ray.len = minDist;
}
Run Code Online (Sandbox Code Playgroud)

一个演示

并演示了以上所有内容.这是一些小的改变(绘图).重要的是两个拦截功能.每次调整大小时,演示都会创建一个随机场景,并从鼠标位置投射16条光线.我可以在你的代码中看到你知道如何获得一条线的方向所以我让演示展示了如何投射你最有可能最终会做的多条光线

    const COLOUR = "BLACK";
    const RAY_COLOUR = "RED";
    const LINE_WIDTH = 4;   
    const RAY_LINE_WIDTH = 2;   
    const OBJ_COUNT = 20; // number of object in the scene;
    const NUMBER_RAYS = 16; // number of rays 
    const RAY_DIR_SPACING = Math.PI / (NUMBER_RAYS / 2);
    const RAY_ROTATE_SPEED = Math.PI * 2 / 31000;    
    if(typeof Math.hypot === "undefined"){ // poly fill for Math.hypot
        Math.hypot = function(x, y){
            return Math.sqrt(x * x + y * y);
        }
    }

    var ctx, canvas, objects, ray, w, h, mouse, rand, ray, rayMaxLen, screenDiagonal;
    // create a canvas and add to the dom
    var canvas = document.createElement("canvas"); 
    canvas.width          = w = window.innerWidth;
    canvas.height         = h = window.innerHeight;
    canvas.style.position = "absolute";
    canvas.style.left     = "0px";
    canvas.style.top      = "0px";
    document.body.appendChild(canvas);
    // objects to ray cast 
    objects = [];
    // mouse object
    mouse = {x :0, y: 0};
    //========================================================================
    // random helper
    rand = function(min, max){
        return Math.random() * (max - min) + min;
    }
    //========================================================================
    // Ad Hoc draw line method
    // col is the stroke style
    // width is the storke width
    var drawLine = function(col,width){
        ctx.strokeStyle = col;
        ctx.lineWidth = width;
        ctx.beginPath();
        ctx.moveTo(this.x,this.y);
        ctx.lineTo(this.x + this.nx * this.len, this.y + this.ny * this.len);
        ctx.stroke();
    }
    //========================================================================
    // Ad Hoc draw circle method
    // col is the stroke style
    // width is the storke width
    var drawCircle = function(col,width){
        ctx.strokeStyle = col;
        ctx.lineWidth = width;
        ctx.beginPath();
        ctx.arc(this.x , this.y, this.radius, 0 , Math.PI * 2);
        ctx.stroke();
    }
    //========================================================================
    // Ad Hoc method for ray to set the direction vector
    var updateRayDir = function(dir){
        this.nx = Math.cos(dir);
        this.ny = Math.sin(dir);
        return this;
    }
    //========================================================================
    // Creates a ray objects from 
    // x,y start location
    // dir the direction in radians
    // len the rays length
    var createRay = function(x,y,dir,len){
        return ({
           x : x,
           y : y,
           len : len,
           draw : drawLine,
           setDir : updateRayDir, // add function to set direction
        }).setDir(dir);
    }
    //========================================================================
    // returns a circle object 
    // x,y is the center
    // radius is the you know what..
    // Note r2 is radius squared if you change the radius remember to set r2 as well
    var createCircle = function(x , y, radius){
         return {
             x : x,
             y : y,
             draw : drawCircle,  // draw function
             rayDist : rayDist2Circle, // add ray cast method
             radius : radius,
             r2 : radius * radius,   // ray caster needs square of radius may as well do it here
         };
    }
    //========================================================================
    // Ad Hoc function to change the wall position
    // x1,y1 are the start coords
    // x2,y2 are the end coords
    changeWallPosition = function(x1, y1, len, dir){
        this.x = x1;
        this.y = y1;
        this.len = len;
        this.nx = Math.cos(dir);
        this.ny = Math.sin(dir);
        return this;
    }
    //========================================================================
    // returns a wall object
    // x1,y1 are the star coords
    // len is the length 
    // dir is the direction
    var createWall = function(x1, y1, len, dir){
        return({
           x : x1, y : y1,
           rayDist : rayDist2Wall, // add ray cast method
           draw : drawLine,
           setPos : changeWallPosition,
        }).setPos(x1, y1, len, dir);
    }
    //========================================================================
    // Self evident 
    // returns a distance or infinity if no valid solution
    var rayDist2Circle = function(ray){
        var vcx, vcy, v;
        vcx = ray.x - this.x; // vector from ray to circle
        vcy = ray.y - this.y;
        v = -2 * (vcx * ray.nx + vcy * ray.ny);
        v -= Math.sqrt(v * v - 4 * (vcx * vcx + vcy * vcy - this.r2)); // this.r2 is the radius squared
        // If there is no solution then Math.sqrt returns NaN we should return Infinity
        // Not interested in intercepts in the negative direction so return infinity
        return isNaN(v) || v < 0 ? Infinity : v / 2;
    }
    //========================================================================
    // returns the distance to the wall
    // if no valid solution then return Infinity
    var rayDist2Wall = function(ray){
        var x,y,u;
        rWCross = ray.nx * this.ny - ray.ny * this.nx;
        if(!rWCross) { return Infinity; } // Not really needed.
        x = ray.x - this.x; // vector from ray to wall start
        y = ray.y - this.y;
        u = (ray.nx * y - ray.ny * x) / rWCross; // unit distance along normal of wall
        // does the ray hit the wall segment
        if(u < 0 || u > this.len){ return Infinity;}  /// no
        // as we use the wall normal and ray normal the unit distance is the same as the
        u = (this.nx * y - this.ny * x) / rWCross;
        return u < 0 ? Infinity : u;  // if behind ray return Infinity else the dist
    }
    //========================================================================
    // does a ray cast
    // ray the ray to cast
    // objects an array of objects 
    var castRay = function(ray,objects){
        var i,minDist;
        minDist = ray.len; // set the min dist to the rays length
        i = objects.length; // number of objects to check
        while(i > 0){
            i -= 1;
            minDist = Math.min(objects[i].rayDist(ray), minDist);
        }
        ray.len = minDist;
    }
    //========================================================================
    // Draws all objects
    // objects an array of objects 
    var drawObjects = function(objects){
        var i = objects.length; // number of objects to check
        while(i > 0){
            objects[--i].draw(COLOUR, LINE_WIDTH);
        }
    }
    //========================================================================
    // called on start and resize
    // creats a new scene each time
    // fits the canvas to the avalible realestate
    function reMakeAll(){
        w = canvas.width  = window.innerWidth;
        h = canvas.height = window.innerHeight;
        ctx = canvas.getContext("2d"); 
        screenDiagonal = Math.hypot(window.innerWidth,window.innerHeight);
        if(ray === undefined){
            ray = createRay(0,0,0,screenDiagonal);
        }

        objects.length = 0;
        var i = OBJ_COUNT;
        while( i > 0 ){
            if(Math.random() < 0.5){ // half circles half walls
                objects.push(createWall(rand(0, w), rand(0, h), rand(screenDiagonal * 0.1, screenDiagonal * 0.2), rand(0, Math.PI * 2)));
            }else{
                objects.push(createCircle(rand(0, w), rand(0, h), rand(screenDiagonal * 0.02, screenDiagonal * 0.05)));
            }
            i -= 1;
        }
    }
    //========================================================================
    function mouseMoveEvent(event){
        mouse.x = event.clientX;
        mouse.y = event.clientY;
    }
    //========================================================================
    // updates all that is needed when needed
    function updateAll(time){
        var i;
        ctx.clearRect(0,0,w,h);
        ray.x = mouse.x;
        ray.y = mouse.y;
        drawObjects(objects);
        i = 0;
        while(i < NUMBER_RAYS){
            ray.setDir(i * RAY_DIR_SPACING + time * RAY_ROTATE_SPEED);
            ray.len = screenDiagonal;
            castRay(ray,objects);
            ray.draw(RAY_COLOUR, RAY_LINE_WIDTH);
            i ++;
        }
        requestAnimationFrame(updateAll);
    }
    // add listeners
    window.addEventListener("resize",reMakeAll);   
    canvas.addEventListener("mousemove",mouseMoveEvent);
    // set it all up
    reMakeAll();
    // start the ball rolling
    requestAnimationFrame(updateAll);
Run Code Online (Sandbox Code Playgroud)