Javascript/Canvas: Mouse coordinates don't match after scaling (collision) - javascript

After resizing the square, there is a collision problem, GIF animation problem, sample https://jsfiddle.net/8jkxdhfv/. What can i do? Should i untransformed mouse coordinates to transformed coordinates ? But how? How can i update x and y in my collision function?
HTML
<canvas id="test" width="480" height="380"></canvas>
<div id="text">Use mouse wheel to change square size</div>
JAVASCRIPT
var ctx = test.getContext('2d');
var obj = { x:100,y: 100,width: 100,height: 100}
var mouse = {x:0, y:0, width:10, height:10};
var zoom = 1;
setInterval(function(){
ctx.clearRect(0,0,test.width,test.height);
ctx.save();
var cx = obj.x+obj.width/2;
var cy = obj.y+obj.height/2;
// draw
ctx.translate(cx, cy);
ctx.scale(zoom,zoom);
ctx.translate(-cx,-cy);
ctx.fillRect(obj.x,obj.y,obj.width,obj.height);
ctx.restore();
// check collision
if(collision(obj,mouse)){
ctx.fillText("===== COLLISION =====", 110,90);
}
},1000/60);
function collision(obj1,obj2){
if(obj1.x < obj2.x + obj2.width * zoom &&
(obj1.x + obj1.width * zoom) > obj2.x &&
obj1.y < obj2.y + obj2.height * zoom &&
(obj1.height * zoom + obj1.y) > obj2.y){
return true;
}
return false;
}
window.addEventListener('mousewheel', function(e){
if(e.deltaY>0 && zoom<2){
zoom+=0.5;
}
if(e.deltaY<0 && zoom>0.5){
zoom-=0.5;
}
}, false);
window.addEventListener('mousemove', function(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
}, false);

You are getting mouse position based on entire window, not canvas. Some math and you will get what you want.
test.addEventListener("mousemove", function(evt) {
var mousePos = getMousePos(test, evt);
mouse.x = mousePos.x;
mouse.y = mousePos.y;
});
function getMousePos(canvas, event) {
var rect = canvas.getBoundingClientRect();
return {
x: event.clientX - rect.left,
y: event.clientY - rect.top
};
}

I have updated the function and it works:
function collision(obj1,obj2){
var eW = (obj1.width-(obj1.width*zoom))/2;
var eH = (obj1.height-(obj1.height*zoom))/2;
//console.log(eW);
if(obj1.x+eW < obj2.x + obj2.width * zoom &&
(obj1.x + obj1.width * zoom) + eW> obj2.x &&
obj1.y + eH < obj2.y + obj2.height * zoom &&
(obj1.height * zoom + obj1.y) + eH > obj2.y){
return true;
}
return false;
}

Related

Two.js - Zoom/Pan ONLY if not on a shape + click handle on shapes

I have recently been asked to learn about Two.js, and specificaly to modify this example. I have tried for few day to alter this code ..
var two = new Two({
fullscreen: true,
autostart: true
}).appendTo(document.body);
var stage = new Two.Group();
for (var i = 0; i < 100; i++) {
var x = Math.random() * two.width * 2 - two.width;
var y = Math.random() * two.height * 2 - two.height;
var size = 50;
var shape = new Two.Rectangle(x, y, size, size);
shape.rotation = Math.random() * Math.PI * 2;
shape.noStroke().fill = '#ccc';
stage.add(shape);
}
shape.fill = 'red';
shape.position.set(two.width / 2, two.height / 2);
two.add(stage);
addZUI();
function addZUI() {
var domElement = two.renderer.domElement;
var zui = new Two.ZUI(stage);
var mouse = new Two.Vector();
var touches = {};
var distance = 0;
var dragging = false;
zui.addLimits(0.06, 8);
domElement.addEventListener('mousedown', mousedown, false);
domElement.addEventListener('mousewheel', mousewheel, false);
domElement.addEventListener('wheel', mousewheel, false);
domElement.addEventListener('touchstart', touchstart, false);
domElement.addEventListener('touchmove', touchmove, false);
domElement.addEventListener('touchend', touchend, false);
domElement.addEventListener('touchcancel', touchend, false);
function mousedown(e) {
mouse.x = e.clientX;
mouse.y = e.clientY;
var rect = shape.getBoundingClientRect();
dragging = mouse.x > rect.left && mouse.x < rect.right
&& mouse.y > rect.top && mouse.y < rect.bottom;
window.addEventListener('mousemove', mousemove, false);
window.addEventListener('mouseup', mouseup, false);
}
function mousemove(e) {
var dx = e.clientX - mouse.x;
var dy = e.clientY - mouse.y;
if (dragging) {
shape.position.x += dx / zui.scale;
shape.position.y += dy / zui.scale;
} else {
zui.translateSurface(dx, dy);
}
mouse.set(e.clientX, e.clientY);
}
function mouseup(e) {
window.removeEventListener('mousemove', mousemove, false);
window.removeEventListener('mouseup', mouseup, false);
}
function mousewheel(e) {
var dy = (e.wheelDeltaY || - e.deltaY) / 1000;
zui.zoomBy(dy, e.clientX, e.clientY);
}
function touchstart(e) {
switch (e.touches.length) {
case 2:
pinchstart(e);
break;
case 1:
panstart(e)
break;
}
}
function touchmove(e) {
switch (e.touches.length) {
case 2:
pinchmove(e);
break;
case 1:
panmove(e)
break;
}
}
function touchend(e) {
touches = {};
var touch = e.touches[ 0 ];
if (touch) { // Pass through for panning after pinching
mouse.x = touch.clientX;
mouse.y = touch.clientY;
}
}
function panstart(e) {
var touch = e.touches[ 0 ];
mouse.x = touch.clientX;
mouse.y = touch.clientY;
}
function panmove(e) {
var touch = e.touches[ 0 ];
var dx = touch.clientX - mouse.x;
var dy = touch.clientY - mouse.y;
zui.translateSurface(dx, dy);
mouse.set(touch.clientX, touch.clientY);
}
function pinchstart(e) {
for (var i = 0; i < e.touches.length; i++) {
var touch = e.touches[ i ];
touches[ touch.identifier ] = touch;
}
var a = touches[ 0 ];
var b = touches[ 1 ];
var dx = b.clientX - a.clientX;
var dy = b.clientY - a.clientY;
distance = Math.sqrt(dx * dx + dy * dy);
mouse.x = dx / 2 + a.clientX;
mouse.y = dy / 2 + a.clientY;
}
function pinchmove(e) {
for (var i = 0; i < e.touches.length; i++) {
var touch = e.touches[ i ];
touches[ touch.identifier ] = touch;
}
var a = touches[ 0 ];
var b = touches[ 1 ];
var dx = b.clientX - a.clientX;
var dy = b.clientY - a.clientY;
var d = Math.sqrt(dx * dx + dy * dy);
var delta = d - distance;
zui.zoomBy(delta / 250, mouse.x, mouse.y);
distance = d;
}
}
.. in order to make it work this way :
Zoom : on wheel, no change.
Dragging : only if mousedown in space between shapes.
Shape click : execute a function.
I tried many things base on answers found here and doc, but nothing work at all, an some pieces of code could be seen as a damn blasphemy.
I don't ask for a complete working code (if someone can provide it I will study it gladly), but any explanations or hints will be greatly appreciate.

How to calculate the position of a click on a draggable canvas element?

I need help with a pretty difficult problem. I am currently making a game with React and Redux. In this game I use a canvas to create a map data from my redux store. Currently the map is just a matrix of black and white squares. Let's say that I wanted to change the color of a square when you click on it, but also maintain the ability to drag the element. The problem is that it is difficult to pinpoint exactly where the mouse is clicking given that the element can be dragged anywhere on the page. As far as I can tell, none of the mouseclick event object properties seem to be able to tell me. I thought offsetX and offsetY would tell me, but they don't seem to stay the same when the canvas object moves for some reason.
I am using React and Redux for this project, and the CanvasMap element is wrapped in a Draggable from this library: https://www.npmjs.com/package/react-draggable#draggablecore
import React from 'react'
import { connect } from 'react-redux'
import './CanvasMap.css'
class CanvasMap extends React.Component{
componentDidMount() {
const map = this.props.map //Data representing the map
const canvas = this.refs.canvas
const context = canvas.getContext('2d')
//Building the map on the canvas
for(let i = 0; i < map.length; i++){
for(let j = 0; j < map[i].length; j++){
const x=i*100
const y=j*100
const isBlackSquare= map[i][j] === 'black' ? true : false
if(isBlackSquare) context.fillRect(x, y, 100, 100)
else context.strokeRect(x, y, 100, 100)
}
}
function handleClick(e){
//None of the event properties seem to stay the same when the canvas is moved
console.log(e)
}
canvas.addEventListener('click', handleClick)
}
render(){
return (
<div className='Map'>
<canvas ref='canvas' width={8000} height={8000} />
</div>
)
}
}
function mapStateToProps(state){
return {
map: [...state.map]
}
}
const connectedComponent = connect(mapStateToProps)(CanvasMap)
export { connectedComponent as CanvasMap }
In most cases when you click on an HTML element you can use the rectangleBounding Box and get coordinators from that like
domRect = element.getBoundingClientRect();
in a canvas click position it is a little more difficult
Here is a script I did a while ago to draw while dragging the mouse the on the canvas .Maybe you can apply this method
<html>
<head>
<style>
* { margin:0; padding:0; } /* to remove the top and left whitespace */
html, body { width:100%; height:100%; } /* just to be sure these are full screen*/
canvas { display:block; } /* To remove the scrollbars */
</style>
</head>
<body>
<canvas id="canvas" ></canvas>
<script>
////////////////////////////////////////
(function() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var elemLeft = canvas.offsetLeft;
var elemTop = canvas.offsetTop;
var BB=canvas.getBoundingClientRect();
var offsetX=BB.left;
var offsetY=BB.top;
// resize the canvas to fill browser window dynamically
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
/**
* Your drawings need to be inside this function otherwise they will be reset when
* you resize the browser window and the canvas goes will be cleared.
*/
drawStuff();
}
resizeCanvas();
function drawStuff() {
// do your drawing stuff here
var img = new Image();
img.src = 'images/3PkBe.gif';
img.onload = function()
{
//var canvas = document.getElementById('canvas');
// create pattern
var ptrn = ctx.createPattern(img, 'repeat'); // Create a pattern with this image, and set it to "repeat".
ctx.fillStyle = ptrn;
ctx.fillRect(0, 0, canvas.width, canvas.height); // context.fillRect(x, y, width, height);
ctx.shadowBlur=20;
//ctx.shadowColor="black";
//ctx.fillStyle="green";
//ctx.fillRect(20,160,100,80);
ctx.strokeStyle = "lightgray";
//var canvasOffset = canvas.offset();
//var offsetX = canvasOffset.left;
//var offsetY = canvasOffset.top;
var mouseIsDown = false;
var lastX = 0;
var lastY = 0;
var elements = [];
makeShip( 30 , 30,120, 120, '#119' , "romea");
makeShip( 30, 160,120, 120, '#393', "fomar");
makeShip( 30, 290,120, 120, '#955', "ojab");
makeShip( 30, 420,120, 120, '#6ff', "eliot");
// Add event listener for `click` events.
canvas.addEventListener('click', function(event) {
var x = event.pageX - elemLeft,
y = event.pageY - elemTop;
console.info(x, y);
elements.forEach(function(element) {
if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
console.log(element.name);
}
});
}, false);
canvas.addEventListener('mousedown', function(event) {
var x = event.pageX - elemLeft,
y = event.pageY - elemTop;
console.info(x, y);
elements.forEach(function(element) {
if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
console.info(element.name);
handleMouseDown(element);
}
});
}, false);
canvas.addEventListener('mousemove', function(event) {
var x = event.pageX - elemLeft,
y = event.pageY - elemTop;
console.info(x, y);
elements.forEach(function(element) {
if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
console.info(element.name);
handleMouseMove(element,x,y);
}
});
}, false);
canvas.addEventListener('mouseup', function(event) {
var x = event.pageX - elemLeft,
y = event.pageY - elemTop;
//console.info(x, y);
elements.forEach(function(element) {
//if (y > element.y && y < element.y + element.height && x > element.x && x < element.x + element.width) {
console.info(element.name + "mouse up evenr=========");
handleMouseUp(element);
//}
});
}, false);
function makeShip(x, y, width, height, colour,ShipName) {
var ship = {
name: ShipName,
colour: colour,
width: width,
height: height,
x: x,
y: y
}
elements.push(ship);
return (ship);
}
function drawShip(ship) {
//ctx.fillStyle = ship.colour;
//ctx.fillRect(ship.x, ship.y, ship.width, ship.height);
//ctx.fillRect(element.x, element.y, element.width, element.height);
}
function drawAllShips() {
// ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < elements.length; i++) {
var ship = elements[i]
//drawShip(ship);
ctx.fillStyle = ship.colour;
ctx.fillRect(ship.x , ship.y, ship.width, ship.height);
// ctx.fillStyle = ship.fill;
// ctx.fill();
// ctx.stroke();
}
}
// Add element.
//elements.push({
//colour: '#05EFFF',
//width: 150,
//height: 100,
//x: 20,
//y: 15
//});
// Render elements.
// elements.forEach(function(element) {
// ctx.fillStyle = element.colour;
// ctx.fillRect(element.x, element.y, element.width, element.height);
// });
drawAllShips();
function handleMouseDown(e) {
mouseX = e.x ;
mouseY = e.y ;
//mouseX = parseInt(e.x - offsetX);
//mouseY = parseInt(e.y - offsetY);
console.log("===========Problem "+mouseX);
// mousedown stuff here
lastX = mouseX;
lastY = mouseY;
mouseIsDown = true;
//alert("mouse Handle");
}
function handleMouseUp(e) {
//mouseX = parseInt(e.clientX - offsetX);
//mouseY = parseInt(e.clientY - offsetY);
ctx.onmousemove = null;
// mouseup stuff here
mouseIsDown = false;
return
}
function handleMouseMove(e,x,y) {
if (mouseIsDown) {
//console.log(' no fuck');
mouseX = e.x ;
mouseY = e.y ;
console.log(e.name+"is truing to drag");
// mousemove stuff here
//for (var i = 0; i < elements.length; i++) {
//if (ctx.isPointInPath(mouseX, mouseY)) {
//console.log('============== no fuck');
var ship =e;// elements[i];
ship.x = x-15;//(mouseX - lastX);
ship.y = y-20;//(mouseY -lastY);
// ship.right = ship.x + ship.width;
// ship.bottom = ship.y + ship.height;
//drawShip(ship);
//}
//}
lastX = mouseX;
lastY = mouseY;
drawAllShips();
}
}
<!-- ctx.mousedown(function (e) { -->
<!-- handleMouseDown(e); -->
<!-- }); -->
<!-- ctx.mousemove(function (e) { -->
<!-- handleMouseMove(e); -->
<!-- }); -->
<!-- ctx.mouseup(function (e) { -->
<!-- handleMouseUp(e); -->
<!-- }); -->
}
}
})();
</script>
</body>
</html>
SOLVED IT!
I got lost in the slew of data around elements and click events, I was trying to figure out the right combination of pageX, clientX, offsetLeft, screenX, etc. However, the final solution is incredibly simple once you know exactly what to do. Here it is:
function handleClick(e){
const rect = e.target.getBoundingClientRect()
const x = e.pageX - rect.left
const y = e.pageY - rect.top
}
This should get you the exact x and y coordinate of your mouse in relation to the element, no matter where you drag and reposition the element.

how to draw a triangle with mouse like in Microsoft windows paint app?

I have written the code but it's for drawing a rectangle, i need to draw triangle shape by dragging the mouse while onClick, like in windows paint App. how to drag the mouse so that triangle forms automatically?
initDraw(document.getElementById('canvas'));
function initDraw(canvas) {
var mouse = {
x: 0,
y: 0,
startX: 0,
startY: 0
};
function setMousePosition(e) { // setting the postion for mouse
var ev = e || window.event;
if (ev.pageX) {
mouse.x = ev.pageX + window.pageXOffset;
mouse.y = ev.pageY + window.pageYOffset;
} else if (ev.clientX) {
mouse.x = ev.clientX + document.body.scrollLeft;
mouse.z = ev.clientZ + document.body.scrollLeft;
} };
var element = null;
canvas.onmousemove = function (e) {// on move
setMousePosition(e);
if (element !== null) {
element.style.width = Math.abs(mouse.x - mouse.startX) + 'px';
element.style.height = Math.abs(mouse.y - mouse.startY) + 'px';
element.style.left = (mouse.x - mouse.startX < 0) ? mouse.x + 'px' : mouse.startX + 'px';
element.style.top = (mouse.y - mouse.startY < 0) ? mouse.y + 'px' : mouse.startY + 'px';
}}};
canvas.onclick = function (e) {// on click function
if (element !== null) {
element = null;
canvas.style.cursor = "default";
console.log("finsihed.");
} else {
console.log("begun.");
mouse.startX = mouse.x;
mouse.startY = mouse.y;
element = document.createElement('div');
element.className = 'triangle'
element.style.left = mouse.x + 'px';
element.style.top = mouse.y + 'px';
canvas.appendChild(element)
canvas.style.cursor = "crosshair";
}}}

Move svg viewport on mousemove of other element

I have to make drag-able svg element (viewport).
$('.zoom_panel').mousedown(function(e) {
if (!drag.state && e.which == 1) {
drag.elem = $('#graph_stage svg .viewport');
drag.state = true;
currentX = $(drag.elem).offset().left;
currentY = $(drag.elem).offset().top;
}
return false;
});
$('.zoom_panel').mousemove(function(e) {
if (drag.state) {
var attrs = $(drag.elem).attr('transform').split(' ')[1];
dx = e.offsetX - $(drag.elem).offset().left;
dy = e.pageY - $(drag.elem).offset().top;
newMatrix = 'translate('+( dx )+','+( dy )+') '+attrs;
$(drag.elem).attr('transform',newMatrix);
}
});
svg not move its blinking.
jsfiddle
Try this jsfiddle
Try to set dx by using relative positions.
dx = e.offsetX - currentX + currentdx;
dy = e.offsetY - currentY + currentdy;

Bouncing Ball Game, Mouse Detect Event, JavaScript

I've already created a bouncing ball which bounces off the walls of the HTML5 Canvas that I have specified.
My goal is to make a "Game Over" screen appear when the pointer (mouse) hovers over the ball.
I have already searched and found some tutorials on mouse events in Javascript, but I'm not really sure how to implement them into my code =/.
Any help would be amazing.
<script>
var x = Math.floor((Math.random() * 600) + 1);
var y = Math.floor((Math.random() * 300) + 1);
var dx = 2;
var dy = 4;
function begin()
{
gameCanvas = document.getElementById('gameCanvas');
context = gameCanvas.getContext('2d');
return setInterval (draw, 20);
}
begin();
function draw()
{
context.clearRect(0,0,600,300);
context.fillStyle = "#0000FF";
context.beginPath();
context.arc(x,y,80,0,Math.PI*2,true);
context.closePath();
context.fill();
if (x < 0 || x > 600) dx=-dx
if (y < 0 || y > 300) dy=-dy;
x += dx;
y += dy;
}
gameCanvas.onmousemove = function (e)
{
var gameCanvas = e.target;
var context = gameCanvas.getContext('2d');
var coords = RGraph.getMouseXY(e);
}
You need to check if the mouse is hovering over the ball (hit test) by checking the distance of the ball to the cursor. If the distance is smaller than radius of the ball, it means that the mouse is over the ball.
Note, that you need to adjust the code below to your needs
Example:
var mouse_x = 10, mouse_y = 10, ball_x = 10, ball_y = 10, ball_radius = 70, is_game_over = false
if( Math.sqrt( Math.pow( mouse_x - ball_x, 2 ) + Math.pow( mouse_x - ball_x, 2 )) < ball_radius && !is_game_over ) {
console.log('Cursor is over the mouse, game over')
is_game_over = true
}
Do it for every frame update.
you can add onmousemove=SetValues() to your body element like so:
<body onmousemove=SetValues()>
and change your code to this:
<script>
var x = Math.floor((Math.random() * 600) + 1);
var y = Math.floor((Math.random() * 300) + 1);
var dx = 2;
var dy = 4;
var mouseX;
var mouseY;
function setValues(e)
{
mouseX = e.pageX; //get mouse x
mouseY = e.pageY; //get mouse y
}
function begin()
{
gameCanvas = document.getElementById('gameCanvas');
context = gameCanvas.getContext('2d');
return setInterval (draw, 20);
}
begin();
function draw()
{
context.clearRect(0,0,600,300);
context.fillStyle = "#0000FF";
context.beginPath();
context.arc(x,y,80,0,Math.PI*2,true);
context.closePath();
context.fill();
if (x < 0 || x > 600) dx=-dx
if (y < 0 || y > 300) dy=-dy;
x += dx;
y += dy;
//check if the mouse is on the ball
var centerX = x + 80; //center of ball
var centerY = y; //center of ball
if(Math.pow((mouseX - centerX), 2) + Math.pow((mouseY - centerY), 2) <= 6400){
//do whatever to end game
}
}

Categories