如何解决确定选择框是否在旋转矩形上方 测试点测试边缘完成测试已添加示例
我有一个用于绘制到 HTML Canvas 的 Rectangle 类。它有一个应用于其绘制方法的旋转属性。如果用户在画布内拖动,则会绘制一个选择选取框。当矩形位于选择选取框内使用数学时,如何将矩形的 active
属性设置为 true
?这是我在另一种语言和上下文中遇到的一个问题,所以我没有所有可用的 Canvas 方法(例如 isPointInPath
)。
我找到了一篇关于查找 Mouse position within rotated rectangle in HTML5 Canvas 的 StackOverflow 帖子,我正在 Rectangle 方法 checkHit
中实现它。但是,它不考虑选择选取框。它只是看着鼠标 X 和 Y,它仍然关闭。浅蓝色点是矩形旋转的原点。如果有任何不清楚的地方,请告诉我。谢谢。
class Rectangle
{
constructor(x,y,width,height,rotation) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.xOffset = this.x + this.width/2;
this.yOffset = this.y + ((this.y+this.height)/2);
this.rotation = rotation;
this.active = false;
}
checkHit()
{
// translate mouse point values to origin
let originX = this.xOffset;
let originY = this.yOffset;
let dx = marquee[2] - originX;
let dy = marquee[3] - originY;
// distance between the point and the center of the rectangle
let h1 = Math.sqrt(dx*dx + dy*dy);
let currA = Math.atan2(dy,dx);
// Angle of point rotated around origin of rectangle in opposition
let newA = currA - this.rotation;
// New position of mouse point when rotated
let x2 = Math.cos(newA) * h1;
let y2 = Math.sin(newA) * h1;
// Check relative to center of rectangle
if (x2 > -0.5 * this.width && x2 < 0.5 * this.width && y2 > -0.5 * this.height && y2 < 0.5 * this.height){
this.active = true;
} else {
this.active = false;
}
}
draw()
{
ctx.save();
ctx.translate(this.xOffset,this.yOffset);
ctx.fillStyle = 'rgba(255,255,1)';
ctx.beginPath();
ctx.arc(0,3,2 * Math.PI,true);
ctx.fill();
ctx.rotate(this.rotation * Math.PI / 180);
ctx.translate(-this.xOffset,-this.yOffset);
if (this.active)
{
ctx.fillStyle = 'rgba(255,0.5)';
} else {
ctx.fillStyle = 'rgba(0,0.5)';
}
ctx.beginPath();
ctx.fillRect(this.x,this.y,this.width,this.y+this.height);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;
var rect = new Rectangle(50,50,90,30,45);
var marquee = [-3,-3,-3];
var BB=canvas.getBoundingClientRect();
var offsetX=BB.left;
var offsetY=BB.top;
var start_x,start_y;
let draw = () => {
ctx.clearRect(0,canvas.width,canvas.height);
//rect.rotation+=1;
rect.draw();
ctx.fillStyle = "rgba(200,200,0.5)";
ctx.fillRect(parseInt(marquee[0]),parseInt(marquee[1]),parseInt(marquee[2]),parseInt(marquee[3]))
ctx.strokeStyle = "white"
ctx.lineWidth = 1;
ctx.rect(parseInt(marquee[0]),parseInt(marquee[3]))
ctx.stroke()
raf = window.requestAnimationFrame(draw);
}
let dragStart = (e) =>
{
start_x = parseInt(e.clientX-offsetX);
start_y = parseInt(e.clientY-offsetY);
marquee = [start_x,start_y,0];
canvas.addEventListener("mousemove",drag);
}
let drag = (e) =>
{
let mouseX = parseInt(e.clientX-offsetX);
let mouseY = parseInt(e.clientY-offsetY);
marquee[2] = mouseX - start_x;
marquee[3] = mouseY - start_y;
rect.checkHit();
}
let dragEnd = (e) =>
{
marquee = [-10,-10,-10];
canvas.removeEventListener("mousemove",drag);
}
canvas.addEventListener('mousedown',dragStart);
canvas.addEventListener('mouseup',dragEnd);
raf = window.requestAnimationFrame(draw);
body
{
margin:0;
}
#canvas
{
width: 360px;
height: 180px;
border: 1px solid grey;
background-color: grey;
}
<canvas id="canvas" width="360" height="180"></canvas>
解决方法
凸多边形是否重叠
矩形是凸多边形。
Rectangle
和 marquee
各有 4 个点(角),定义了连接这些点的 4 条边(线段)。
此解决方案适用于所有具有 3 个或更多边的凸不规则多边形。
点和边必须是连续的或顺时针 CW 或 Count Clockwise CCW
测试点
如果一个多边形的任何一点在另一个多边形内,那么它们必须重叠。查看示例函数 isInside
要检查点是否在多边形内,求叉积,边起点为向量,边为向量。
如果所有叉积 >= 0(左侧),则存在重叠(对于 CW 多边形)。如果多边形是逆时针,那么如果所有叉积都
可以在另一个多边形内没有任何点的情况下重叠。
测试边缘
如果一个多边形的任何边与另一个多边形的任何边交叉,则必须有重叠。如果两条线段截取,则函数 doLinesIntercept
返回 true。
完成测试
如果两个多边形重叠,函数 isPolyOver(poly1,poly2)
将返回 true
。
多边形由一组连接点的 Point
和 Lines
定义。
多边形可以是不规则的,也就是说每条边可以是任意长度 > 0
不要传递边长度为 === 0 的多边形,否则将不起作用。
已添加
我添加了函数 Rectangle.toPoints
来转换矩形并返回一组 4 个点(角)。
示例
示例是使用上述方法工作的代码副本。
canvas.addEventListener('mousedown',dragStart);
canvas.addEventListener('mouseup',dragEnd);
requestAnimationFrame(draw);
const Point = (x = 0,y = 0) => ({x,y,set(x,y){ this.x = x; this.y = y }});
const Line = (p1,p2) => ({p1,p2});
const selector = { points: [Point(),Point(),Point()] }
selector.lines = [
Line(selector.points[0],selector.points[1]),Line(selector.points[1],selector.points[2]),Line(selector.points[2],selector.points[3]),Line(selector.points[3],selector.points[0])
];
const rectangle = { points: [Point(),Point()] }
rectangle.lines = [
Line(rectangle.points[0],rectangle.points[1]),Line(rectangle.points[1],rectangle.points[2]),Line(rectangle.points[2],rectangle.points[3]),Line(rectangle.points[3],rectangle.points[0])
];
function isInside(point,points) {
var i = 0,p1 = points[points.length - 1];
while (i < points.length) {
const p2 = points[i++];
if ((p2.x - p1.x) * (point.y - p1.y) - (p2.y - p1.y) * (point.x - p1.x) < 0) { return false }
p1 = p2;
}
return true;
}
function doLinesIntercept(l1,l2) {
const v1x = l1.p2.x - l1.p1.x;
const v1y = l1.p2.y - l1.p1.y;
const v2x = l2.p2.x - l2.p1.x;
const v2y = l2.p2.y - l2.p1.y;
const c = v1x * v2y - v1y * v2x;
if(c !== 0){
const u = (v2x * (l1.p1.y - l2.p1.y) - v2y * (l1.p1.x - l2.p1.x)) / c;
if(u >= 0 && u <= 1){
const u = (v1x * (l1.p1.y - l2.p1.y) - v1y * (l1.p1.x - l2.p1.x)) / c;
return u >= 0 && u <= 1;
}
}
return false;
}
function isPolyOver(p1,p2) { // is poly p2 under any part of poly p1
if (p2.points.some(p => isInside(p,p1.points))) { return true };
if (p1.points.some(p => isInside(p,p2.points))) { return true };
return p1.lines.some(l1 => p2.lines.some(l2 => doLinesIntercept(l1,l2)));
}
const ctx = canvas.getContext("2d");
var dragging = false;
const marquee = [0,0];
const rotate = 0.01;
var startX,startY,hasSize = false;
const BB = canvas.getBoundingClientRect();
const offsetX = BB.left;
const offsetY = BB.top;
class Rectangle {
constructor(x,width,height,rotation) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.rotation = rotation;
this.active = false;
}
toPoints(points = [Point(),Point()]) {
const xAx = Math.cos(this.rotation) / 2;
const xAy = Math.sin(this.rotation) / 2;
const x = this.x,y = this.y;
const w = this.width,h = this.height;
points[0].set(-w * xAx + h * xAy + x,-w * xAy - h * xAx + y);
points[1].set( w * xAx + h * xAy + x,w * xAy - h * xAx + y);
points[2].set( w * xAx - h * xAy + x,w * xAy + h * xAx + y);
points[3].set(-w * xAx - h * xAy + x,-w * xAy + h * xAx + y);
}
draw() {
ctx.setTransform(1,1,this.x,this.y);
ctx.fillStyle = 'rgba(255,255,1)';
ctx.strokeStyle = this.active ? 'rgba(255,1)' : 'rgba(0,1)';
ctx.lineWidth = this.active ? 3 : 1;
ctx.beginPath();
ctx.arc(0,3,2 * Math.PI,true);
ctx.fill();
ctx.rotate(this.rotation);
ctx.beginPath();
ctx.rect(-this.width / 2,- this.height / 2,this.width,this.height);
ctx.stroke();
}
}
function draw(){
rect.rotation += rotate;
ctx.setTransform(1,0);
ctx.clearRect(0,canvas.width,canvas.height);
rect.draw();
drawSelector();
requestAnimationFrame(draw);
}
function drawSelector() {
if (dragging && hasSize) {
rect.toPoints(rectangle.points);
rect.active = isPolyOver(selector,rectangle);
ctx.setTransform(1,0);
ctx.fillStyle = "rgba(200,200,0.5)";
ctx.strokeStyle = "white";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.rect(...marquee);
ctx.fill();
ctx.stroke();
} else {
rect.active = false;
}
}
function dragStart(e) {
startX = e.clientX - offsetX;
startY = e.clientY - offsetY;
drag(e);
canvas.addEventListener("mousemove",drag);
}
function drag(e) {
dragging = true;
const x = e.clientX - offsetX;
const y = e.clientY - offsetY;
const left = Math.min(startX,x);
const top = Math.min(startY,y);
const w = Math.max(startX,x) - left;
const h = Math.max(startY,y) - top;
marquee[0] = left;
marquee[1] = top;
marquee[2] = w;
marquee[3] = h;
if (w > 0 || h > 0) {
hasSize = true;
selector.points[0].set(left,top);
selector.points[1].set(left + w,top);
selector.points[2].set(left + w,top + h);
selector.points[3].set(left,top + h);
} else {
hasSize = false;
}
}
function dragEnd(e) {
dragging = false;
rect.active = false;
canvas.removeEventListener("mousemove",drag);
}
const rect = new Rectangle(canvas.width / 2,canvas.height / 2,90,Math.PI / 4);
body
{
margin:0;
}
#canvas
{
width: 360px;
height: 180px;
border: 1px solid grey;
background-color: grey;
}
<canvas id="canvas" width="360" height="180"></canvas>
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。