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

3
我正在尝试测试一个点是否在旋转角度(x,y)周围的矩形区域内,如下图所示。这是一种与语言无关的问题,但我现在正在使用HTML5画布。
假设我们需要测试的点是(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)
我阅读了一些资源(在此处)并编写了一个测试项目,但它没有测试正确的区域。在我的测试项目中,我想要这个效果:
如果鼠标位于测试矩形区域范围内的点上,则会在鼠标周围显示一个点。如果在矩形之外,则不会显示任何内容。
然而,我的测试项目看起来像这样:(请注意,尽管我使用了基于向量的技术来测试旋转矩形区域中的点,但测试区域仍然是旋转之前的矩形)

// 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();
<canvas id='canvas'></canvas>

更新的解决方案

我仍然使用向量为基础的方法,如下所述:

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;

// 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();
<canvas id='canvas'></canvas>


4
在计算之前将旋转点和矩形向后移动? - wutzebaer
@wutzebaer。旋转鼠标指针或矩形,但不要同时旋转两者。;-) - markE
3个回答

3

2
浏览器始终以未转换(==未旋转)的方式报告鼠标位置。
因此,要测试鼠标是否在旋转的矩形内,您可以:
  • 从鼠标事件中获取相对于画布的未旋转鼠标位置。
  • 将鼠标x、y与旋转点相对应地旋转相同的旋转角度。
  • 测试鼠标是否在矩形内。现在,既然矩形和鼠标位置都已类似地旋转了,您只需像测试未旋转的鼠标和矩形一样进行测试即可。
注释代码和演示:

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);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<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>


这个解决方案是可行的,但我不确定 mouseAngle-rect.rotation 是什么意思。我已经发布了我的代码修订版,仍然按照我使用的方法,这样更容易理解它的工作原理。 - newguy
解释 mouseAngle-rect.rotation:当你点击时,鼠标相对于旋转点的角度为 mouseAngle。因此,从零度角到旋转点开始,我们首先将其旋转到 mouseAngle,然后再通过矩形的旋转角度进行反向旋转。这样可以使鼠标与矩形在旋转方面保持一致。祝你的项目好运! :-) - markE

2

如你在此Codepen中看到的(用于检测2个旋转矩形的碰撞)。

你需要检查点(在我的例子中是矩形的4个顶点)的两个投影并查看投影是否在另一个矩形上。

你需要处理同样的问题,但只针对一个点和一个矩形而不是2个矩形。

所有的投影都没有碰撞 enter image description here

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

required code for codepen link

1
这是非常好的技术。我认为它与我在测试项目解决方案中使用的那个类似。 - newguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接