Multiple canvas polygon detection map using isPointInPath() and responsive design - javascript

I have an array of canvas objects that draw correctly. i have three problems:
Offset. I have tested the code below in JS fiddle and it works, but when i export it my web page, the variables get skewed. The detection happens, but not in the right place. the page width is set in CSS, and the actual canvas area is centered using a margin:0 auto call, however it is smaller than the page width.
<canvas id="canvas" width="780" height="690" style="position:absolute;"></canvas>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
var $results = $("#results");
// define the polygon items
var polyArray = new Array (6);
polyArray [0] =[{x:50,y:236}, {x:200,y:115}, {x:350,y:50}, {x:350,y:300}, {x:232,y:325}, {x:75,y:300}];
polyArray [1] =[{x:350,y:55}, {x:350,y:300}, {x:510,y:300}, {x:510,y:205}, {x:578,y:172}, {x:690,y:96}, {x:650,y:17}];
polyArray [2] =[{x:510,y:300}, {x:510,y:200}, {x:715,y:113}, {x:780,y:200}, {x:780,y:485}, {x:625,y:468}, {x:605,y:456}, {x:605,y:428}];
polyArray [3] =[{x:0,y:446}, {x:284,y:320}, {x:255,y:540}, {x:240,y:566}, {x:73,y:600}, {x:0,y:565}];
polyArray [4] =[{x:355,y:305}, {x:510,y:305}, {x:604,y:423}, {x:604,y:460}, {x:628,y:484}, {x:610,y:513}, {x:587,y:468}, {x:537,y:426}, {x:500,y:400}, {x:447,y:424}, {x:312,y:365}, {x:307,y:314 }];
polyArray [5] =[{x:350,y:425}, {x:415,y:421}, {x:455,y:434}, {x:495,y:411}, {x:550,y:444}, {x:618,y:590}, {x:570,y:616}, {x:359,y:597}, {x:333,y:522}];
// call the function to draw all the objects in the array
define(polyArray);
// call through the array to draw the objects
function define(polygon) {
ctx.beginPath();
for (var i = 0; i < polygon.length; i++) {
ctx.moveTo(polygon[i][0].x, polygon[i][0].y);
for (var j = 1; j < polygon[i].length; j++) {
ctx.lineTo(polygon[i][j].x, polygon[i][j].y);
}
ctx.fill();
}
ctx.closePath();
}
function hitTest(polygon) {
// redefine the polygon
define(polygon);
// ask isPointInPath to hit test the mouse position
// against the current path
return (ctx.isPointInPath(mouseX, mouseY));
}
function handleMouseMove(e) {
e.preventDefault();
mouseX = parseInt(e.clientX - offsetX);
mouseY = parseInt(e.clientY - offsetY);
// check if the mouse is inside the polygon
var isInside = hitTest(polyArray);
if (isInside) {
canvas.style.cursor = 'pointer';
$results.text("Mouse is inside the area);
} else {
canvas.style.cursor = 'default';
$results.text("Outside");
}
}
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
Detecting which object has been hovered over. What needs to happen is on hover of one the array shapes should effect some CSS/JS. How can i assign an ID variable and detect it?
when i bring responsive design into the equation i'm a bit stuck for how to incorporate this offset and the poly co-ords to scale appropriately.
Any point in the right direction would be greatly appreciated.

Question#1: Getting accurate mouse position after the canvas has moved
Whenever you move your canvas (fex: margin: 0 auto), you must recalculate your offsetX and offsetY values:
If you manually change the canvas element's CSS (fex: canvas.style.margin='50px' inside javascript), then you must also manually call reOffset().
// cache the canvas's offset positions since the
// offset positions are used often
var offsetX,offsetY;
// call this once at the beginning of your app
// and whenever you change the canvas's position on the page
// (eg call when you change margins, scroll, etc)
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
// have the browser auto-reset offsetX & offsetX when
// the viewport scrolls or resizes
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
Question#2 Detecting hovers & blurs over your polygons
Your hitTest function will test if the mouse is currently inside a specified polygon. So inside handleMousemove you could call hitText for each of the polygons inside your polyArray.
Keep a flag variable indicating the index# of the last polygon the mouse was inside (or -1 to indicate the mouse was outside all polygons. When your flag variable value changes, you know there has been either a hover-event or a blur-event. Compare the last and current flag variables to determine which polygon is now hovered or blurred.
Question#3 Incorporating a responsive design
Mouse coordinates reported by the browser into e.clientX and e.clientY are always in unscaled values relative to the browser viewport.
So if you:
Click the mouse and use e.clientX/e.clientY to determine the mouse is at [100,100],
Scale your canvas: context.scale(2,2),
And reclick without moving the mouse from its original [100,100] position,
Then:
Using e.clientX/e.clientY to detect the mouse coordinates will still report the position as [100,100] even if the canvas has been scaled and the mouse is at [200,200] relative to the scaled canvas.
The fix:
You must scale the browser's reported mouse position to match the scaling factor of the canvas:
// Determine how much you want to scale the canvas
var scaleFactor=2.00;
// scale the canvas
context.scale(scaleFactor,scaleFactor);
// also scale the mouse position reported by the browser
mouseX=parseInt(e.clientX-offsetX)*scaleFactor;
mouseY=parseInt(e.clientY-offsetY)*scaleFactor;

Related

get mouse position inside resizing div using raycaster and vector

I am able to get the intersects from my click event when I use the window object to acquire height and width, but getting the intersects position on a canvas that's dynamically sized is proving much harder. I'm not certain of the formula I would need to use to calculate the vector.x and vector.y values with a div that isn't always the same size.
The canvas is the size of a div that always has a width: height ratio of 4:3 and resizes to fit in the window and is always positioned in the center of the window.
If I resize the window to be 4:3 then the following code works perfectly:
mouse.x = (ecx/div_width) *2 -1;
mouse.y= -(ecy/div_height) *2 + 1;
when I resize the window, whichever dimension is larger than the size of the canvas has the incorrect value. I've linked an image to roughly describe how the problem presents itself
Image of horizontal dimension issue
I initially thought that the matches would be as simple as dividing the difference between the the sizes of the window and the canvas by
My question is, how would I acquire the correct values to pass to the vector object for it's x and y attributes? (using Vector3 and Raycaster)
here is the function I'm using to try and get the object(s) being clicked:
function getClicked(event){
event.preventDefault();
var ecx = event.clientX;
var ecy = event.clientY;
//elem is the div containing the canvas
//the canvas is not the same size as the window
var elem_w = elem.innerWidth();
var elem_h = elem.innerHeight();
//most examples suggest using the window height and width
//to get the position of the mouse in the scene.
//since the scene isn't the same size as the window, that doesn't work
var ww = window.innerWidth;
var wh = window.innerHeight;
mouse.x = (ecx/ww) *2 -1;
mouse.y= -(ecy/wh) *2 + 1;
var objlist = []
rc.setFromCamera(mouse, camera);
var intersects = rc.intersectObjects(scene.children, true);
for (var i=0;i<names_to_spin.length;i++){
var obj = intersects[i];
objlist.push(obj);
}
//ideally, this should return a list of the objects under the cursor
return objlist;
}

How can I get the "rotated" coordinates of a mouse-click on a canvas?

The background
I have an image editing program that displays a canvas where the user can perform several actions such as drawing shapes. The shapes drawn by an user are sent to a server which then adds them as an element to an XML file. The XML file is sent back to the client. The client then reads this XML file and for each element it draws the corresponding shape.
I want to add new functionality to my program. I want to make it so that an user can rotate the view locally. That is, this user should be able to click on a button which will rotate the canvas 90 degrees to the right. So if a shape is at position (10, 10) of a 40x40 canvas, it will be at (30, 10) upon clicking on this button.
I have achieved this by using rotation and translation as follows:
ctx.translate(width/2,height/2);
ctx.rotate(90*Math.PI/180);
ctx.translate(-width/2,-height/2);
// draw shapes from returned XML file
So now when shapes are drawn from my XML files, they will appear rotated.
The problem
The problem appears when I try to draw a new shape on the rotated canvas. Let's say I am drawing a rectangle from (10, 10) to (15, 15). The program will send these coordinates to my server. The server will then add them to the XML file and send the XML file back to my client. When my client goes through its drawing cycle, it will draw the new shape. However, since the canvas has been rotated, it will draw at the shape at the rotated position.
So I am drawing from (10, 10) to (15, 15) but the shape I am seeing appear on the canvas goes from (30, 10) to (25, 15).
To deal with this, I had the idea that instead of sending the server the actual coordinates of the mouse, I should feed it the coordinates as if the canvas had been rotated in the opposite direction to the same degree as the rotation created by the rotation button.
So if the degree of rotation is 90 and I click on (10, 10), instead of feeding my program (10, 10) I would feed it (10, 30). That way when drawing it it would rotate it and it would appear in the right place.
Hence my question, how can I get the "rotated" coordinates of a mouse-click on a canvas? (Without any external libraries)
// Code to draw a rectangle
Rectangle.prototype.handleInput = function() {
var coord = {};
var anchor = {};
var imgData = s.ctx.getImageData(0, 0, s.width, s.height);
var drawingSquare = false;
var squareDrawn = false;
var color, thickness;
var mouseMoved;
s.canvas.onmousedown = function(e) {
mouseMoved = false;
color = getColor();
thickness = getThickness();
anchor.x = e.clientX - s.getX();
anchor.y = e.clientY - s.getY();
drawingSquare = true;
imgData = s.ctx.getImageData(0, 0, s.width, s.height);
};
s.canvas.onmousemove = function(e) {
if (drawingSquare) {
mouseMoved = true;
coord.x = e.clientX - s.getX();
coord.y = e.clientY - s.getY();
s.ctx.putImageData(imgData, 0, 0);
Rectangle.draw(anchor.x, anchor.y, coord.x, coord.y, color, thickness);
squareDrawn = true;
xml.editRect(anchor.x, anchor.y, getColor(), getThickness(), coord.x, coord.y);
}
};
s.canvas.onmouseup = function(e) {
drawingSquare = false;
// startX, startY, strokeStyle, lineWidth, endX, endY
if (mouseMoved) {
sm.save();
xml.addRect(anchor.x, anchor.y, getColor(), getThickness(), coord.x, coord.y);
}
If you're only rotating in 90 degree increments then the math is fairly straight forward. This example rotates coordinates 90° to the left.
unrotated.x = anchor.y;
unrotated.y = s.height - anchor.x;
Other rotations are similar, just swapping in and out x and y positions and widths and heights.

Get bounding client rectangle without borders

I made a function that transforms mouse coordinates to canvas pixel coordinates:
/* Returns pixel coordinates according to the pixel that's under the mouse cursor**/
HTMLCanvasElement.prototype.relativeCoords = function(event) {
var x,y;
//This is the current screen rectangle of canvas
var rect = this.getBoundingClientRect();
//Recalculate mouse offsets to relative offsets
x = event.clientX - rect.left;
y = event.clientY - rect.top;
//Also recalculate offsets of canvas is stretched
var width = rect.right - rect.left;
//I use this to reduce number of calculations for images that have normal size
if(this.width!=width) {
var height = rect.bottom - rect.top;
//changes coordinates by ratio
x = x*(this.width/width);
y = y*(this.height/height);
}
//Return as an array
return [x,y];
}
You can see demonstration of the pixel coordinate calculation. The problem is that the solutions fails for images having border property set.
How can I subtract the border width from rectangle? Performance does matter, as this calculation is often performed during mouse move events.
getComputedStyle contains the information you desire:
Fetch the border information once at the beginning of your app after the canvas border has been set.
// get a reference to the canvas element
var canvas=document.getElementById('yourCanvasId');
// get its computed style
var styling=getComputedStyle(canvas,null);
// fetch the 4 border width values
var topBorder=styling.getPropertyValue('border-top-width');
var rightBorder=styling.getPropertyValue('border-right-width');
var bottomBorder=styling.getPropertyValue('border-bottom-width');
var leftBorder=styling.getPropertyValue('border-left-width');
If you scope these border-width variables app-wide, you can use these prefetched variables in your HTMLCanvasElement.prototype.relativeCoords.
Good luck with your project!

Obtain co-ordinates of an HTML5 canvas on a page

I have a a canvas inside another canvas.
<canvas id ='canvas2' height="718" width="1316"></canvas>
its css is something
#canvas2{
position:absolute;
width :95%;
height:90%;
top:5%;
left:2.5%;
background: #ffff56;
cursor:pointer;
}
next I have drawn some rectangles on it. I need to colour those with mouse click. I used an action listener.
var canvas = document.getElementById("canvas2");
var ctx = canvas.getContext("2d");
canvas.addEventListener("mousedown", doMouseDown, false);
var $canvas = $("#canvas2");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
function doMouseDown(event){
event.preventDefault();
event.stopPropagation();
var x= parseInt(event.clientX - offsetX);
var y = parseInt(event.clientY - offsetY);
}
But this is not the right way I know as I am getting all the wrong canvas co-ordinates on x and y.
Can someone show the right way?
Has your canvas been scrolled?
If yes, then you also need to account for the distance the canvas has been scrolled in the browser.
You might check out canvas.getBoundingClientRect() as a way to get the canvas position with the scrolling accounted for:
function handleMousemove(e){
e.preventDefault();
e.stopPropagation();
// if the canvas is stationary (not scrolling) then you can do
// .getBoundingClientRect once at the start of the app
var BB=canvas.getBoundingClientRect();
// calc mouse position based on the bounding box
var mouseX=parseInt(e.clientX-BB.left);
var mouseY=parseInt(e.clientY-BB.top);
console.log(mouseX+"/"+mouseY);
}

Canvas accessing coordinate of translated position of panning background image

Cannot figure this out, how to find the translated position of the background relative to the canvas. I have the characters coordinates, and I have the coordinates from a mouse click within the canvas, but can't figure out how to find the offset.
In the canvas, when I click somewhere, I get an (x,y) value from (0,0) - (650,575), the size of the window, no matter where my character is. If the character is at (2000, 1500) on the canvas, my click/touch input will always send the character up and left towards 0,0 on the background coordinate.
At first I thought I should subtract the player X position from the max width, then add an offset half the width of the screen, and do the same for the Y position, but that didn't work.
Then I tried subtracting half the width/height of the screen from the current player x,y values but that doesn't work.
Anyone point me in the right direction, it seems elementary but I can't figure it out it's been years since math class???? Thanks
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 650;
canvas.height = 575;
var WIDTH=5000; //level width
var HEIGHT=3750; //level height
ctx.translate(-WIDTH*.5,-HEIGHT*.5); //starts in center of background
Where my player begins on load:
hero.x = WIDTH*.5+325; //offset half canvas width
hero.y = HEIGHT*.5+275; //offset half canvas height
For the Background:
ctx.drawImage(bgImage, BGsrcX , BGsrcY, 1250 , 938 ,-150, -150, BGdestW, BGdestH); `//image is stretched to 5000x3750`
This is the mouse input I'm using
if(navigator.userAgent.match(/(iPhone)|(iPod)|(iPad)/i)){
document.addEventListener('touchstart', function(e) {
if(e.touches.length == 1){ // Only deal with one finger
var touch = e.touches[0]; // Get the information for finger #1
var x = touch.pageX - canvas.offsetLeft;
var y = touch.pageY - canvas.offsetTop;
//clickEvent(x,y); //call your function to manage tweets
}
},false);
}
else{
document.addEventListener('mousedown',function(e) {
var x = e.pageX - canvas.offsetLeft;
var y = e.pageY - canvas.offsetTop;
console.log(x+":"+y);
clickEvent(x,y); //call your function to manage tweets
},false);
}
For the keyboard input to actually pan the background:
if(16 in keysDown && 38 in keysDown && hero.y > 200) {ctx.translate(0,12); }
Don't work with half-translated and non-translated coordinates, translate your mouse click coordinates AND your canvas coordinates.
Then you can just use simple subtraction to find the offset, and to find the distance, you you use the distance formula.

Categories