如何测试一个点是否在一个旋转角度的矩形区域?

时间:2016-07-08 14:10:26

标签: javascript html5

我正在尝试测试一个点是否位于围绕(x,y)旋转角度的矩形区域内,如下图所示。这是语言无关的问题,但我现在正在使用HTML5 canvas。

假设我们需要测试的点是(x1,y1),矩形的宽度是100,高度是60.在正常的笛卡尔坐标系中,矩形ABCD左上角的点A是(canvas.width / 2, canvas.height / 2 -rect.height/2)。我假设(canvas.width / 2, canvas.height / 2)位于AB线的中间,其中B是(canvas.width / 2, canvas.height / 2 + rect.height /2)

我已经阅读了一些资源here并编写了一个测试项目,但它没有测试正确的区域。在我的测试项目中,我想要这个效果:

如果鼠标位于测试矩形区域范围内的点上,则鼠标周围将显示一个点。如果它在矩形之外,则不会显示任何内容。

然而,我的测试项目看起来像这样:(请注意,虽然我使用基于矢量的技术来测试旋转矩形区域中的点,但测试区域在旋转前仍然是矩形)



// Detecting a point is in a rotated rectangle area
// using vector based method
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');

class Rectangle {
	constructor(x, y, width, height) {
  	this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.searchPoint = { x: 0, y: 0};
    this.binding();
  }
  
  binding() {
  	let self = this;
    window.addEventListener('mousemove', e => {
      if (!e) return;
      let rect = canvas.getBoundingClientRect();
      let mx = e.clientX - rect.left - canvas.clientLeft;
      let my = e.clientY - rect.top - canvas.clientTop;
      self.searchPoint = { x: mx, y: my };
    });
	}
}

let rect = new Rectangle(canvas.width /2, canvas.height /2 - 30, 100, 60);

function vector(p1, p2) {
    return {
            x: (p2.x - p1.x),
            y: (p2.y - p1.y)
    };
}

function point(x, y) {
	return { x, y };
}

// Vector dot operation
function dot(a, b) {
  return a.x * b.x + a.y * b.y;
}

function pointInRect(p, rect, angle) {
	let a = newPointTurningAngle(0, -rect.height / 2, angle);
	let b = newPointTurningAngle(0, rect.height / 2, angle);
  let c = newPointTurningAngle(rect.width, rect.height / 2, angle);
	let AB = vector(a, b);
  let AM = vector(a, p);
  let BC = vector(b, c);
  let BM = vector(b, p);
  let dotABAM = dot(AB, AM);
  let dotABAB = dot(AB, AB);
  let dotBCBM = dot(BC, BM);
  let dotBCBC = dot(BC, BC);
  
  return 0 <= dotABAM && dotABAM <= dotABAB && 0 <= dotBCBM && dotBCBM <= dotBCBC;
}

function drawLine(x, y) {
	ctx.strokeStyle = 'black';
	ctx.lineTo(x, y);
  ctx.stroke();
}

function text(text, x, y) {
	ctx.font = "18px serif";
  ctx.fillText(text, x, y);
}

function newPointTurningAngle(nx, ny, angle) {
	return {
  	x: nx * Math.cos(angle) - ny * Math.sin(angle),
    y: nx * Math.sin(angle) + ny * Math.cos(angle)
  };
}

function animate() {
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	ctx.setTransform(1, 0, 0, 1, 0, 0);
	ctx.moveTo(canvas.width / 2, 0);
  drawLine(canvas.width /2, canvas.height / 2);
  
  ctx.moveTo(0, canvas.height / 2);
  drawLine(canvas.width / 2, canvas.height /2);
  
	let angle = -Math.PI / 4;
	ctx.setTransform(Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), canvas.width / 2, canvas.height / 2);
  //ctx.setTransform(1, 0, 0, 1, canvas.width/2, canvas.height / 2);
	ctx.strokeStyle = 'red';
  ctx.strokeRect(0, -rect.height / 2, rect.width, rect.height);
  
	let p = newPointTurningAngle(rect.searchPoint.x - canvas.width / 2, rect.searchPoint.y - canvas.height / 2, angle);

	let testResult = pointInRect(p, rect, angle);
	if (testResult) {
  	ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.beginPath();
  	ctx.fillStyle = 'black';
  	ctx.arc(rect.searchPoint.x, rect.searchPoint.y, 5, 0, Math.PI * 2);
    ctx.fill();
  }
  
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  text('searchPoint x: ' + rect.searchPoint.x + ', y: ' + rect.searchPoint.y, 60, 430);
  text('x: ' + canvas.width / 2 + ', y: ' + canvas.height / 2, 60, 480);
  
  requestAnimationFrame(animate);
}

animate();
&#13;
<canvas id='canvas'></canvas>
&#13;
&#13;
&#13;

更新了解决方案

我仍在使用基于矢量的方法:

0 <= dot(AB,AM) <= dot(AB,AB) &&
0 <= dot(BC,BM) <= dot(BC,BC)

现在我已经改变了点的旋转角度和角点坐标,因此可以在矩形中检测到点。角点已经在旋转的坐标系中,因此它们不需要被平移,但是在矩形区域中测试之前需要翻译鼠标位置的点。

setTransform方法中,顺时针旋转时旋转的角度为正,形式为:

ctx.setTransform(angle_cosine, angle_sine, -angle_sine, angle_cosine, x, y);

因此,在旋转角度后计算点的新坐标时,公式需要更改为此值,以便顺时针旋转时角度也为正:

 new_x = x * angle_cosine + y * angle_sine;
 new_y = -x * angle_sine + y * angle_cos;

&#13;
&#13;
// Detecting a point is in a rotated rectangle area
// using vector based method
const canvas = document.getElementById('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext('2d');

class Rectangle {
	constructor(x, y, width, height) {
  	this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.searchPoint = { x: 0, y: 0};
    this.binding();
  }
  
  binding() {
  	let self = this;
    window.addEventListener('mousemove', e => {
      if (!e) return;
      let rect = canvas.getBoundingClientRect();
      let mx = e.clientX - rect.left - canvas.clientLeft;
      let my = e.clientY - rect.top - canvas.clientTop;
      self.searchPoint = { x: mx, y: my };
    });
	}
}

let rect = new Rectangle(canvas.width /2, canvas.height /2 - 30, 100, 60);

function vector(p1, p2) {
    return {
            x: (p2.x - p1.x),
            y: (p2.y - p1.y)
    };
}

function point(x, y) {
	return { x, y };
}

// Vector dot operation
function dot(a, b) {
  return a.x * b.x + a.y * b.y;
}

function pointInRect(p, rect) {
  let a = { x: 0, y: -rect.height / 2};
  let b = { x: 0, y: rect.height / 2};
  let c = { x: rect.width, y: rect.height / 2};
  text('P x: ' + p.x.toFixed() + ', y: ' + p.y.toFixed(), 60, 430);
  text('A x: ' + a.x.toFixed() + ', y: ' + a.y.toFixed(), 60, 455);
  text('B x: ' + b.x.toFixed() + ', y: ' + b.y.toFixed(), 60, 480);
	let AB = vector(a, b);
  let AM = vector(a, p);
  let BC = vector(b, c);
  let BM = vector(b, p);
  let dotABAM = dot(AB, AM);
  let dotABAB = dot(AB, AB);
  let dotBCBM = dot(BC, BM);
  let dotBCBC = dot(BC, BC);
  
  return 0 <= dotABAM && dotABAM <= dotABAB && 0 <= dotBCBM && dotBCBM <= dotBCBC;
}

function drawLine(x, y) {
	ctx.strokeStyle = 'black';
	ctx.lineTo(x, y);
  ctx.stroke();
}

function text(text, x, y) {
	ctx.font = "18px serif";
  ctx.fillText(text, x, y);
}

function newPointTurningAngle(nx, ny, angle) {
	let cos = Math.cos(angle);
  let sin = Math.sin(angle);
	return {
  	x: nx * cos + ny * sin,
    y: -nx * sin + ny * cos
  };
}

function animate() {
	ctx.clearRect(0, 0, canvas.width, canvas.height);
	ctx.setTransform(1, 0, 0, 1, 0, 0);
	ctx.moveTo(canvas.width / 2, 0);
  drawLine(canvas.width /2, canvas.height / 2);
  
  ctx.moveTo(0, canvas.height / 2);
  drawLine(canvas.width / 2, canvas.height /2);
  
    let angle = - Math.PI / 4;
	ctx.setTransform(Math.cos(angle), Math.sin(angle), -Math.sin(angle), Math.cos(angle), canvas.width / 2, canvas.height / 2);
	ctx.strokeStyle = 'red';
  ctx.strokeRect(0, -rect.height / 2, rect.width, rect.height);
  
  	let p = newPointTurningAngle(rect.searchPoint.x - canvas.width / 2, rect.searchPoint.y - canvas.height / 2, angle);
    
  ctx.setTransform(1, 0, 0, 1, 0, 0);
	let testResult = pointInRect(p, rect);
  
	if (testResult) {
    ctx.beginPath();
  	ctx.fillStyle = 'black';
  	ctx.arc(rect.searchPoint.x, rect.searchPoint.y, 5, 0, Math.PI * 2);
    ctx.fill();
  }
  
  ctx.setTransform(1, 0, 0, 1, 0, 0);
  text('searchPoint x: ' + rect.searchPoint.x + ', y: ' + rect.searchPoint.y, 60, 412);
  text('x: ' + canvas.width / 2 + ', y: ' + canvas.height / 2, 60, 510);
  
  requestAnimationFrame(animate);
}

animate();
&#13;
<canvas id='canvas'></canvas>
&#13;
&#13;
&#13;

3 个答案:

答案 0 :(得分:3)

假设您知道如何检查点是否在矩形中,解决方案的方法是旋转并将所有内容(点和矩形)转换为“标准化”协调系统(我们熟悉的笛卡尔坐标系)然后琐碎地检查它。

有关更多信息,请检查仿射变换。你可以开始的好链接是

http://www.mathworks.com/discovery/affine-transformation.html?requestedDomain=www.mathworks.com

答案 1 :(得分:2)

浏览器始终报告未转换的鼠标位置(==未旋转)。

因此,要测试鼠标是否在旋转的矩形内,您可以:

  • 从鼠标事件(相对于画布)获取未旋转的鼠标位置。
  • 使用与矩形相同的旋转,将鼠标x,y与旋转点旋转。
  • 测试鼠标是否在矩形内。既然rect和鼠标位置都已经被类似地旋转了,你可以只测试鼠标和rect是不旋转的。

带注释的代码和演示:

&#13;
&#13;
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
    var BB=canvas.getBoundingClientRect();
    offsetX=BB.left;
    offsetY=BB.top;        
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }

var isDown=false;
var startX,startY;

var rect=makeRect(50,20,35,20,Math.PI/4,60,30);

function makeRect(x,y,w,h,angle,rotationPointX,rotationPointY){
    return({
        x:x,y:y,width:w,height:h,
        rotation:angle,rotationPoint:{x:rotationPointX,y:rotationPointY},
    });
}

drawRect(rect);

$("#canvas").mousedown(function(e){handleMouseDown(e);});

function drawRect(r){
    var rx=r.rotationPoint.x;
    var ry=r.rotationPoint.y;
    // demo only, draw the rotation point
    dot(rx,ry,'blue');
    // draw the rotated rect
    ctx.translate(rx,ry);
    ctx.rotate(r.rotation);
    ctx.strokeRect(rect.x-rx,rect.y-ry,r.width,r.height);
    // always clean up, undo the transformations (in reverse order)
    ctx.rotate(-r.rotation);
    ctx.translate(-rx,-ry);
}

function dot(x,y,fill){
    ctx.fillStyle=fill;
    ctx.beginPath();
    ctx.arc(x,y,3,0,Math.PI*2);
    ctx.fill();
}

function handleMouseDown(e){
    // tell the browser we're handling this event
    e.preventDefault();
    e.stopPropagation();
    // get mouse position relative to canvas
    mouseX=parseInt(e.clientX-offsetX);
    mouseY=parseInt(e.clientY-offsetY);
    // rotate the mouse position versus the rotationPoint
    var dx=mouseX-rect.rotationPoint.x;
    var dy=mouseY-rect.rotationPoint.y;
    var mouseAngle=Math.atan2(dy,dx);
    var mouseDistance=Math.sqrt(dx*dx+dy*dy);
    var rotatedMouseX=rect.rotationPoint.x+mouseDistance*Math.cos(mouseAngle-rect.rotation);
    var rotatedMouseY=rect.rotationPoint.y+mouseDistance*Math.sin(mouseAngle-rect.rotation);
    // test if rotated mouse is inside rotated rect
    var mouseIsInside=rotatedMouseX>rect.x &&
        rotatedMouseX<rect.x+rect.width &&
        rotatedMouseY>rect.y &&
        rotatedMouseY<rect.y+rect.height;
    // draw a dot at the unrotated mouse position
    // green if inside rect, otherwise red
    var hitColor=mouseIsInside?'green':'red';
    dot(mouseX,mouseY,hitColor);
}
&#13;
body{ background-color: ivory; }
#canvas{border:1px solid red; }
&#13;
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Clicks inside rect are green, otherwise red.</h4>
<canvas id="canvas" width=512 height=512></canvas>
&#13;
&#13;
&#13;

答案 2 :(得分:2)

正如你在Codepen我所看到的那样(检测2个旋转矩形碰撞)。

你必须检查你的点的2个投影(在我的情况下,矩形的4个点)并查看投影是否在另一个矩形上

你必须处理相同的事情,但只针对一个点和一个矩形而不是两个行

所有投影均未发生碰撞 enter image description here

所有投影都在碰撞 enter image description here

required code for codepen link