I am currently trying to detect a mouse click on two grids of boxes simultaneously. One grid is easy, and I've just been using:
var gridPosX = Math.floor(mouseClickX/BoxWidth);
var gridPosY = Math.floor(mouseClickY/BoxHeight);
Now I also want to detect a mouse click on a secondary grid of boxes, located at the corners of the first grid of boxes. This could be achieved in a similar way to the first grid. The problem comes in because I want to detect a click on either the first grid, or the second one, at the same time. What is the best way to differentiate a click on the first grid verses a click on the second grid? I've tried to remove the Math.floor and used the greater than and less than operators (> <) to see if the click was closer to one grid spot than the other, but I've had no luck with that so far.
This is an image example of the grid. The black being the main one, the red being the second one
var WIDTH = 1280, HEIGHT = 1280;
var canvas, context;
var grid = [];
var grid2 = [];
var gridWidth = 10, gridHeight = 10;
var boxWidth = WIDTH/gridWidth, boxHeight = HEIGHT/gridHeight;
function main(){
canvas = document.createElement("canvas");
canvas.width = WIDTH;
canvas.height = HEIGHT;
context = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas.onmousedown = function(e){
if(e.which == 1){
var gridPosX = Math.floor(e.offsetX/boxWidth);
var gridPosY = Math.floor(e.offsetY/boxHeight);
grid[gridPosX][gridPosY] = 0;
}
}
init();
setInterval(draw, 30);
}
function init(){
for(var x = 0; x < gridWidth; x++){
grid[x] = [];
grid2[x] = [];
for(var y = 0; y < gridHeight; y++){
grid[x][y] = 1;
grid2[x][y] = 1;
}
}
}
function draw(){
for(var x = 0; x < gridWidth; x++){
for(var y = 0; y < gridHeight; y++){
if(grid[x][y] == 1){
context.fillStyle = 'gray';
context.fillRect(x*boxWidth, y*boxHeight, boxWidth, boxHeight);
context.strokeRect(x*boxWidth, y*boxHeight, boxWidth, boxHeight);
}
}
}
for(var x = 0; x < gridWidth; x++){
for(var y = 0; y < gridHeight; y++){
if(grid2[x][y] == 1){
context.fillStyle = 'red';
context.fillRect((x*boxWidth)+(boxWidth)-(boxWidth/4), (y*boxHeight)+(boxHeight)-(boxHeight/4), boxWidth/2, boxHeight/2);
context.strokeRect((x*boxWidth)+(boxWidth)-(boxWidth/4), (y*boxHeight)+(boxHeight)-(boxHeight/4), boxWidth/2, boxHeight/2);
}
}
}
}
main();
Since the red grids show on top of the gray ones, I think you can first decide whether a mouse event is on a red grid or not. If not, then it must be on gray grids.
Based on the calculations below to check if a red grid is clicked:
var xRedIndex = Math.floor((e.offsetX - 3 / 4 * boxWidth) / (boxWidth / 2));
var yRedIndex = Math.floor((e.offsetY - 3 / 4 * boxHeight) / (boxHeight / 2));
if (xRedIndex % 2 === 0 && yRedIndex % 2 === 0) {
console.log("red");
console.log("Red grid x: " + (xRedIndex / 2));
console.log("Red grid y: " + (yRedIndex / 2));
} else {
console.log("gray");
var gridPosX = Math.floor(e.offsetX / boxWidth);
var gridPosY = Math.floor(e.offsetY / boxHeight);
grid[gridPosX][gridPosY] = 0;
}
Basically, you first subtract the initial gray area in the first column/row from the offsetX/Y, then see if the rest of the offsetX/Y contains an odd or even number of boxSize/2 (side length of red grid). An even number means the click is on red grids, otherwise it falls on the uncovered gray area.
Working fiddle: https://jsfiddle.net/mwxzgth6/1/
Related
Currently I'm drawing vertices to create polygons, I would like to add sliders to allow a user to increase or decrease the amount of sides the polygons have and the amount drawn. Also having the canvas update with refreshing.
I've tried adding a slider to control the noOfSides but I've had no luck.
Thanks for your time and help.
let noOfShapes = 3;
let noOfSides;
let rx, ry, drx, dry, rd1, rd2, drd1, drd2;
let threshold = 1000;
let fillColour;
let strokeThick;
let sidesSlider;
function setup() {
createCanvas(1240, 1754);
noLoop();
// background(0, 230)
colorMode(RGB)
rectMode(CENTER);
strokeWeight(3);
//noOfSides = 3;
fillColour = random(0, 255);
sidesSlider = createSlider(4, 12, 3);
sidesSlider.position(width + 20, 0);
sidesSlider.style('width', '150px');
}
function draw() {
background(0, 230)
noOfSides = sidesSlider.value();
for (let x = 0; x < noOfShapes; x++) {
do {
rx = [];
ry = [];
rd1 = [];
rd2 = [];
for (let y = 0; y < noOfSides; y++) {
rx.push(random(10, width - 20));
ry.push(random(10, height - 20));
rd1.push(rx[y] * 1 + ry[y] * 1);
rd2.push(rx[y] * 1 - ry[y] * 1);
}
drx = max(rx) - min(rx);
dry = max(ry) - min(ry);
drd1 = max(rd1) - min(rd1);
drd2 = max(rd2) - min(rd2);
}
while (drx < threshold || dry < threshold || drd1 < threshold || drd2 < threshold)
beginShape();
stroke(255);
fill(random(1, 255), random(1, 255), random(1, 255), 150);
for (let y = 0; y < rx.length; y++) {
vertex(rx[y], ry[y]);
}
endShape(CLOSE);
}
for (let x = 20; x <= width; x = x + 20) {
blendMode(DODGE);
stroke(255);
beginShape();
vertex(x, 0)
vertex(x, height + 20)
endShape();
}
}
First things first, you need to re-draw your shapes whenever slider changes since you are using noLoop().
To do that you can easily define an on change event like this:
sidesSlider.input(sliderChange);
And i suggest you to assign noOfSides variable's value in that function. After that call the draw funtion again.
function sliderChange() {
noOfSides = sidesSlider.value();
draw();
}
Since you would remove assigning the value to noOfSides in draw function you need to set a default value to that variable either on initialization or in `setup function.
...
noOfSides = 3;
...
Then you are almost good to go, only thing that i don't quite understand was you last part of the code. I removed that part and it works as expected at the moment.
Please be aware that you are setting background color with alpha value. That leads: on each rendering of shapes, latest shapes silhouette are still barely visible.
Here is the latest version of your code:
https://editor.p5js.org/darcane/sketches/5wpp6UgXI
I have a problem with JS canvas ctx.fill() filling outside of my polygonal shape.
Here's how my code works :
ctx.beginPath()
// Here are for loops that draws a the closed shape using
ctx.stroke();
ctx.fill();
Here are the for loops:
var sx1, sy1, ex1, ey1, sx2, sy2, ex2, ey2;
for(var i = 0; i < n; i += Math.floor(n/steps)){
var radius = Math.exp(-2*i/n)*rmax+rmin;
radius += frequencyData[i]/255*(n-i + 200)/n*50;
var angle = -Math.PI/2 - i/n*2*Math.PI;
var x = radius*Math.cos(angle) + w/2+rmin/2;
var y = radius*Math.sin(angle) + (h-110)/2+rmin/2 + analyser_offset;
if (i == 0) {
gctx.moveTo(x,y);
sx1 = x;
sy1 = y;
}else if (i == n-1){
ex1 = x;
ey1 = y;
}else{
gctx.lineTo(x,y);
}
spd += frequencyData[i];
}
for(var i = 0; i < n; i += Math.floor(n/steps)){
var radius = Math.exp(-2*i/n)*rmax+rmin;
radius -= frequencyData[i]/255*(n-i + 200)/n*50;
var angle = -Math.PI/2 - i/n*2*Math.PI;
var x = radius*Math.cos(angle) + w/2+rmin/2;
var y = radius*Math.sin(angle) + (h-110)/2+rmin/2 + analyser_offset;
if (i == 0) {
gctx.moveTo(x,y);
}else if (i == 20){
sx2 = x;
sy2 = y;
}else if (i == n-1){
ex2 = x;
ey2 = y;
} else {
gctx.lineTo(x,y);
}
}
gctx.moveTo(sx1, sy1);
gctx.lineTo(sx2, sy2);
gctx.moveTo(ex1, ey1);
gctx.lineTo(ex2, ey2);
So the first for loop draws the outter side of the shape, the second for loop draws the inner side. And then the sx1, sy1, ex1, ey1, sx2, sy2, ex2, ey2 variables are here to ensure that in the last 4 lines, it closes the shape (by adding vertical line between the outter and inner lines). Maybe this problem happens because I draw the lines in an unusual order? (like drawing a rectangle by starting with 2 horizontal lines and then adding 2 vertical ones)
Here's what I get after the fill() :
And this is what I would like to have:
So could you guide me on how I'm supposed to achieve this?
Ok I fixed it by making the second loop go in the reverse order like this: for (var i = n-1; i >= 0; i -= Math.floor(n/steps)) so it draws the polygon in a more usual order and that works! I don't even need to close it using the last 4 lines which was what I wanted so that's great!
I have the following code for working with my canvas:
function clickBox() //Get every cell by coordinate on the grid.
{
var xRectFill = 0; //We start our search on the left side of the board.
var yRectFill = 0; //We start our search at the top of the board.
var rectFillArrayX = []; //We will store every X-Coordinate of a cell here.
var rectFillArrayY = []; //We will store every Y-Coordinate of a cell here.
var mouseX = event.offsetX;
var mouseY = event.offsetY;
for (i3 = 0; i3 <= 8; i3++) //We will fill the X and Y values of our storage array here.
{
rectFillArrayX[i3] = xRectFill; //We will get each individual X-Coordinate and store from [0-8]
rectFillArrayY[i3] = yRectFill; //We will get each individual Y-Coordinate and store from [0-8]
xRectFill += 72; //After we fill an X value, we will use the value of the next cell.
yRectFill += 72; //After we fill a Y value, we will use the value of the next cell.
}
for (i4 = 0; i4 <= 8; i4++)
{
if (mouseX >= rectFillArrayX[i4] && mouseX <= (rectFillArrayX[i4] + 72))
{
for (i5 = 0; i5 <= 8; i5++)
{
if (mouseY >= rectFillArrayY[i5] && mouseY <= (rectFillArrayY[i5] + 72))
{
ctx.clearRect(rectFillArrayX[i4], rectFillArrayY[i5], 71, 71);
}
}
}
}
}
I am practicing with canvas by designing a sudoku puzzle. I have a 9 x 9 grid, and the function "clickBox" currently gets the coordinates of the mouse and clears a cell. Everything in this function works as expected when called from a mouse click event.
What I would like to do now, is to make a copy of the portion of the canvas that was cleared by clearRect, and place that copy back into the canvas whenever I click another area.
I have tried several different approaches and tinkered around a bit. I think I need to use the canvas functions getImageData and putImageData, but have been unsuccessful in getting it to work.
I have tried storing my X and Y coordinates at the moment a box gets cleared, then passing those coordinates to getImageData, then placing the putImageData in whenever a new click occurs. I am not sure whether I was doing it correctly, and get/put never seemed to do anything.
The theory I had was to use getImageData just before the clearRect function happens, then, on the next click, use putImageData on the previously clicked cell.
Can someone show me a proper use of getImageData and putImageData for this project?
This is my attempt to solve:
function clickBox() //Get every cell by coordinate on the grid.
{
var xRectFill = 0; //We start our search on the left side of the board.
var yRectFill = 0; //We start our search at the top of the board.
var rectFillArrayX = []; //We will store every X-Coordinate of a cell here.
var rectFillArrayY = []; //We will store every Y-Coordinate of a cell here.
var mouseX = event.offsetX;
var mouseY = event.offsetY;
if (lastLocation[0] != null)
{
ctx.putImageData(imgData, lastLocation[0], lastLocation[1], 70);
}
for (i3 = 0; i3 <= 8; i3++) //We will fill the X and Y values of our storage array here.
{
rectFillArrayX[i3] = xRectFill; //We will get each individual X-Coordinate and store from [0-8]
rectFillArrayY[i3] = yRectFill; //We will get each individual Y-Coordinate and store from [0-8]
xRectFill += 72; //After we fill an X value, we will use the value of the next cell.
yRectFill += 72; //After we fill a Y value, we will use the value of the next cell.
}
for (i4 = 0; i4 <= 8; i4++)
{
if (mouseX >= rectFillArrayX[i4] && mouseX <= (rectFillArrayX[i4] + 72))
{
for (i5 = 0; i5 <= 8; i5++)
{
if (mouseY >= rectFillArrayY[i5] && mouseY <= (rectFillArrayY[i5] + 72))
{
var imgData = ctx.getImageData(rectFillArrayX[i4], rectFillArrayY[i4], 72, 72);
var lastLocation = [rectFillArrayX[i4], rectFillArrayY[i5]];
ctx.clearRect(rectFillArrayX[i4], rectFillArrayY[i5], 71, 71);
}
}
}
}
}
I have also tried:
function clickBox() //Get every cell by coordinate on the grid.
{
var xRectFill = 0; //We start our search on the left side of the board.
var yRectFill = 0; //We start our search at the top of the board.
var rectFillArrayX = []; //We will store every X-Coordinate of a cell here.
var rectFillArrayY = []; //We will store every Y-Coordinate of a cell here.
var mouseX = event.offsetX;
var mouseY = event.offsetY;
var lastLocation = [];
if (typeof lastLocation[0] !== 'undefined')
{
alert("Array is defined");
ctx.putImageData(imgData, lastLocation[0], lastLocation[1], 70);
}
for (i3 = 0; i3 <= 8; i3++) //We will fill the X and Y values of our storage array here.
{
rectFillArrayX[i3] = xRectFill; //We will get each individual X-Coordinate and store from [0-8]
rectFillArrayY[i3] = yRectFill; //We will get each individual Y-Coordinate and store from [0-8]
xRectFill += 72; //After we fill an X value, we will use the value of the next cell.
yRectFill += 72; //After we fill a Y value, we will use the value of the next cell.
}
for (i4 = 0; i4 <= 8; i4++)
{
if (mouseX >= rectFillArrayX[i4] && mouseX <= (rectFillArrayX[i4] + 72))
{
for (i5 = 0; i5 <= 8; i5++)
{
if (mouseY >= rectFillArrayY[i5] && mouseY <= (rectFillArrayY[i5] + 72))
{
var imgData = ctx.getImageData(rectFillArrayX[i4], rectFillArrayY[i4], 72, 72);
var lastLocation = [rectFillArrayX[i4], rectFillArrayY[i5]];
ctx.clearRect(rectFillArrayX[i4], rectFillArrayY[i5], 71, 71);
}
}
}
}
}
Puzzle
I finally fixed it.
function clickBox() //Get every cell by coordinate on the grid.
{
var xRectFill = 0; //We start our search on the left side of the board.
var yRectFill = 0; //We start our search at the top of the board.
var rectFillArrayX = []; //We will store every X-Coordinate of a cell here.
var rectFillArrayY = []; //We will store every Y-Coordinate of a cell here.
var mouseX = event.offsetX;
var mouseY = event.offsetY;
if (typeof lastLocation !== 'undefined' && lastLocation.length > 0)
{
// the array is defined and has at least one element
ctx.putImageData(imgData, lastLocation[0], lastLocation[1]);
}
for (i3 = 0; i3 <= 8; i3++) //We will fill the X and Y values of our storage array here.
{
rectFillArrayX[i3] = xRectFill; //We will get each individual X-Coordinate and store from [0-8]
rectFillArrayY[i3] = yRectFill; //We will get each individual Y-Coordinate and store from [0-8]
xRectFill += 72; //After we fill an X value, we will use the value of the next cell.
yRectFill += 72; //After we fill a Y value, we will use the value of the next cell.
}
for (i4 = 0; i4 <= 8; i4++)
{
if (mouseX >= rectFillArrayX[i4] && mouseX <= (rectFillArrayX[i4] + 72))
{
for (i5 = 0; i5 <= 8; i5++)
{
if (mouseY >= rectFillArrayY[i5] && mouseY <= (rectFillArrayY[i5] + 72))
{
lastLocation = [rectFillArrayX[i4], rectFillArrayY[i5]];
ctx.clearRect(rectFillArrayX[i4], rectFillArrayY[i5], 71, 71);
}
}
}
}
}
I created a global getImageData variable elsewhere, and added a check near the start of the function to see if the array was created, instead of creating an empty array beforehand.
Final additions:
if (typeof lastLocation !== 'undefined' && lastLocation.length > 0)
{
// the array is defined and has at least one element
ctx.putImageData(imgData, lastLocation[0], lastLocation[1]);
}
In my opinion, you need to create a table with these portions as table cells. Then you can easily hide and unhide a cell on events or whatever.
You can set <tr id="your_cell" style="display: none;"> and then show it back with JavaScript:
var cell_with_data = document.getElementById('your_cell').style;
cell_with_data.display = 'table-row'/'block';
Cells can be removed by
var row = document.getElementById("myRow");
row.deleteCell(0);
Just a suggestion btw.
I have a HTML5 canvas that generates a bouncing box every time you click on it. The box array stores the x-value, y-value, x-velocity, and y-velocity of each box created. The box will travel in a random direction at first and will bounce of the sides of the canvas but if it hits a corner the box dissappears instead of bouncing back. EDIT: I answered my own question noticing that the soundY and soundX functions were causing the problem.
var box = new Array();
var width = window.innerWidth;
var height = window.innerHeight;
var field = document.getElementById('canvas');
field.width = width;
field.height = height;
field.ctx = field.getContext('2d');
field.ctx.strokeStyle = 'rgba(255,255,255,1)';
setInterval('redraw()', 200);
addEventListener('click', createBox, false);
function createBox(e) { // this box will always fail collision detection at the upper-left corner
box.push(100); // x-value is normally mouse position
box.push(100); // y-value is normally mouse position
box.push(-5); // x-speed is normally random
box.push(-5); // y-speed is normally random
}
function redraw() {
field.ctx.clearRect(0,0,width,height);
for(var i = 0; i < box.length; i+=4) {
if(box[i] < 0) { box[i+2] *= -1; soundY(box[i+1]); } // parameter of soundY is less than 0
else if(box[i] > width) { box[i+2] *= -1; soundY(box[i+1]); } // which is invalid and causes this to break
if(box[i+1] < 0) { box[i+3] *= -1; soundX(box[i]); }
else if(box[i+1] > height) { box[i+3] *= -1; soundX(box[i]); }
box[i] += box[i+2];
box[i+1] += box[i+3];
field.ctx.strokeRect(box[i], box[i+1], 4, 4);
}
}
function soundX(num) {
// play a sound file based on a number between 0 and width
}
function soundY(num) {
// play a sound file based on a number between 0 and height
}
The only way I could recreate the problem was by generating the box in one of the corners so that with the right x and y velocity the box was initially created outside the bounds of the canvas. When that happens, the inversion of the velocity isn't enough to bring the item back in bounds and so on the next frame the velocity is inverted again (and so on).
I think this might solve your problem:
var boxes = [];
var boxSize = 4;
var width = window.innerWidth;
var height = window.innerHeight;
var field = document.getElementById('canvas');
function redraw() {
field.ctx.clearRect(0, 0, width, height);
var box;
for (var i = 0; i < boxes.length; i++) {
box = boxes[i];
field.ctx.strokeRect(box.x, box.y, boxSize, boxSize);
if (box.x < 0) {
box.x = 0;
box.dx *= -1;
} else if (box.x > width - boxSize) {
box.x = width - boxSize;
box.dx *= -1;
}
if (box.y < 0) {
box.y = 0;
box.dy *= -1;
} else if (box.y > height - boxSize) {
box.y = height - boxSize;
box.dy *= -1;
}
box.x += box.dx;
box.y += box.dy;
}
}
field.width = width;
field.height = height;
field.ctx = field.getContext('2d');
field.ctx.strokeStyle = 'rgba(0,0,0,1)';
setInterval(redraw, 200);
addEventListener('click', createBox, false);
function createBox(e) {
boxes.push({
x: e.clientX - 10,
y: e.clientY - 10, // arbitrary offset to place the new box under the mouse
dx: Math.floor(Math.random() * 8 - boxSize),
dy: Math.floor(Math.random() * 8 - boxSize)
});
}
I fixed a few errors in your code and made some changes to make it a bit more readable (I hope). Most importantly, I extended your collision detection so that it resets the coordinates of the box to the bounds of your canvas should the velocity take it outside.
Created a jsfiddle which might be handy if further discussion is needed.
It was additional code (see edit) that I left out assuming it was unrelated to the issue, but removing the code solved the problem as it appears this use-case would cause an invalid input in this part of the code.
I'm coding a tile based game in javascript using canvas and was wondering how I could create a simple event handler for when the mouse enters the dimensions of a tile.
I've used jquery's http://api.jquery.com/mousemove/ in the past but for a very simple application but can't seem to wrap my head around how I'll do it in this case (quickly).
Hmm..
I started writing this post without a clue of how to do it, but I just tried using the jquery mousemove like I started above. I have a working version, but it seems 'slow' and very clunky. It's doesn't seem smooth or accurate.
I put all mode code into a js fiddle to share easily:
http://jsfiddle.net/Robodude/6bS6r/1/
so what's happening is:
1) jquery's mousemove event handler fires
2) Sends the mouse object info to the GameBoard
3) Sends the mouse object info to the Map
4) Loops through all the tiles and sends each one the mouse object
5) the individual tile then determines if the mouse coords are within its boundaries. (and does something - in this case, I just change the tiles properties to white)
but here are the sections I'm most concerned about.
$("#canvas").mousemove(function (e) {
mouse.X = e.pageX;
mouse.Y = e.pageY;
game.MouseMove(mouse);
Draw();
});
function GameBoard() {
this.Map = new Map();
this.Units = new Units();
this.MouseMove = function (Mouse) {
this.Map.MouseMove(Mouse);
};
}
function Map() {
this.LevelData = Level_1(); // array
this.Level = [];
this.BuildLevel = function () {
var t = new Tile();
for (var i = 0; i < this.LevelData.length; i++) {
this.Level.push([]);
for (var a = 0; a < this.LevelData[i].length; a++) {
var terrain;
if (this.LevelData[i][a] == "w") {
terrain = new Water({ X: a * t.Width, Y: i * t.Height });
}
else if (this.LevelData[i][a] == "g") {
terrain = new Grass({ X: a * t.Width, Y: i * t.Height });
}
this.Level[i].push(terrain);
}
}
};
this.Draw = function () {
for (var i = 0; i < this.Level.length; i++) {
for (var a = 0; a < this.Level[i].length; a++) {
this.Level[i][a].Draw();
}
}
};
this.MouseMove = function (Mouse) {
for (var i = 0; i < this.Level.length; i++) {
for (var a = 0; a < this.Level[i].length; a++) {
this.Level[i][a].MouseMove(Mouse);
}
}
};
this.BuildLevel();
}
function Tile(obj) {
//defaults
var X = 0;
var Y = 0;
var Height = 40;
var Width = 40;
var Image = "Placeholder.png";
var Red = 0;
var Green = 0;
var Blue = 0;
var Opacity = 1;
// ...
this.Draw = function () {
ctx.fillStyle = "rgba(" + this.Red + "," + this.Green + "," + this.Blue + "," + this.Opacity + ")";
ctx.fillRect(this.X, this.Y, this.Width, this.Height);
};
this.MouseMove = function (Mouse) {
if ((Mouse.X >= this.X) && (Mouse.X <= this.Xmax) && (Mouse.Y >= this.Y) && (Mouse.Y <= this.Ymax)) {
this.Red = 255;
this.Green = 255;
this.Blue = 255;
}
};
}
If you have a grid of tiles, then given a mouse position, you can retrieve the X and Y index of the tile by dividing the X mouse position by the width of a tile and Y position with the height and flooring both.
That would make Map's MouseMove:
this.MouseMove = function (Mouse) {
var t = new Tile();
var tileX = Math.floor(mouse.X / t.Width);
var tileY = Math.floor(mouse.Y / t.Height);
this.Level[tileY][tileX].MouseMove(Mouse);
};
Edit: You asked for some general suggestions. Here you go:
It's more common to use initial uppercase letters for only classes in JavaScript.
Mouse is a simple structure; I don't think it needs to have its own class. Perhaps use object literals. (like {x: 1, y: 2})
You may want to use JavaScript's prototype objects, rather than using this.method = function() { ... } for every method. This may increase performance, since it only has to create the functions once, and not whenever a new object of that class is made.