Too many objects getting created while creating shapes in html5 canvas - javascript

I'm creating a simple paint app in html5 canvas and JavaScript which creates triangles of variable size. I have just figured out how to do this and am now trying to drag and move the triangles that have been created. For this, I'm using a "triangles" array to store all the triangle objects.
When I try to push one object of one triangle into the array, too many objects get created. How do I fix this? I just want one object per triangle.
My code:
HTML:
<body>
<h1>Simple Paint App</h1>
<div id="canvs">
<canvas id="paint-canvas" width="800" height="400"></canvas>
<button id="buttn" >Clear/Reset</button>
</div>
<noscript>This site requires JavaScript to be activated.</noscript>
</body>
JavaScript:
let canvas = document.querySelector("#paint-canvas");
let context = canvas.getContext("2d");
let triangles = []
let offsetX = canvas.getBoundingClientRect().left;
let offsetY = canvas.getBoundingClientRect().top;
let startX, startY;
let dragOk = false;
let imageData;
function getCoordinates(e){
const X = parseInt(e.clientX - offsetX);
const Y = parseInt(e.clientY - offsetY);
return {x: X, y: Y};
}
function getRandomColor(){
const colors =
["#DFFF00", "#FFBF00", "#FF7F50", "#DE3163", "#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF"];
return colors[Math.floor(Math.random() * colors.length)];
}
canvas.onmousedown = mouseDown;
canvas.onmouseup = mouseUp;
canvas.onmousemove = mouseMove;
document.querySelector("#buttn").addEventListener("click", () => resetCanvas());
function mouseDown(e){
dragOk = true;
const start = getCoordinates(e);
startX = start.x;
startY = start.y;
storeImageData();
}
function mouseUp(e){
dragOk = false;
restoreImageData();
let position = getCoordinates(e);
drawTriangle(position);
console.log(triangles);
}
function mouseMove(e){
if (!dragOk) {
return;
}
let position;
restoreImageData()
position = getCoordinates(e);
drawTriangle(position);
}
function storeImageData() {
imageData = context.getImageData(0, 0, canvas.width, canvas.height);
}
function restoreImageData() {
context.putImageData(imageData, 0, 0);
}
function drawTriangle(position) {
let tri = { x:startX, y:startY, points: [{x:0,y:0},{x:0,y:0},{x:0,y:0}], outline: "#000000", fill:"#000000", dragOk: false };
const radius = Math.sqrt(Math.pow((startX - position.x), 2) + Math.pow((startY - position.y), 2));
let i = 0;
let angle = 100;
// 3 because triangle has 3 sides
for (i = 0; i < 3; i++) {
tri.points[i].x = startX + radius * Math.cos(angle);
tri.points[i].y = startY - radius * Math.sin(angle);
angle += (2 * Math.PI) / 3;
}
context.beginPath();
context.moveTo(tri.points[0].x, tri.points[0].y);
for (i = 1; i < 3; i++) {
context.lineTo(tri.points[i].x, tri.points[i].y);
}
context.closePath();
// the outline
context.lineWidth = 5;
tri.outline = "#000000"
context.strokeStyle = tri.outline;
context.stroke();
// the fill color
tri.fill = getRandomColor();
context.fillStyle = tri.fill;
context.fill();
triangles.push(tri)
}
function resetCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
Edit:
I am pushing the newly created triangles in the drawTriangle function.
JSFiddle: Code

The problem is that your drawTriangle function is chained to the onMouseMove event. Of course this event is required to size the triangles but you shouldn't use it to stuff objects inside the triangles array, as this means as long as the mouse is moving, your creating new objects which are pushed into the array.
Better do it like this:
create a global variable tempTriangle
inside the drawTriangle function assign tri to tempTriangle
if the mouse button is released push tempTriangle into the triangles array
Here's an example based on your code:
let canvas = document.querySelector("#paint-canvas");
let context = canvas.getContext("2d");
let triangles = []
let offsetX = canvas.getBoundingClientRect().left;
let offsetY = canvas.getBoundingClientRect().top;
let startX, startY;
let dragOk = false;
let imageData;
let tempTriangle;
function getCoordinates(e) {
const X = parseInt(e.clientX - offsetX);
const Y = parseInt(e.clientY - offsetY);
return {
x: X,
y: Y
};
}
function getRandomColor() {
const colors = ["#DFFF00", "#FFBF00", "#FF7F50", "#DE3163", "#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF"];
return colors[Math.floor(Math.random() * colors.length)];
}
canvas.onmousedown = mouseDown;
canvas.onmouseup = mouseUp;
canvas.onmousemove = mouseMove;
function mouseDown(e) {
dragOk = true;
const start = getCoordinates(e);
startX = start.x;
startY = start.y;
storeImageData();
}
function mouseUp(e) {
dragOk = false;
restoreImageData();
let position = getCoordinates(e);
drawTriangle(position);
triangles.push(tempTriangle)
console.log(triangles, triangles.length);
}
function mouseMove(e) {
if (!dragOk) {
return;
}
let position;
restoreImageData()
position = getCoordinates(e);
drawTriangle(position);
}
function storeImageData() {
imageData = context.getImageData(0, 0, canvas.width, canvas.height);
}
function restoreImageData() {
context.putImageData(imageData, 0, 0);
}
function drawTriangle(position) {
let tri = {
x: startX,
y: startY,
points: [{
x: 0,
y: 0
}, {
x: 0,
y: 0
}, {
x: 0,
y: 0
}],
outline: "#000000",
fill: "#000000",
dragOk: false
};
const radius = Math.sqrt(Math.pow((startX - position.x), 2) + Math.pow((startY - position.y), 2));
let i = 0;
let angle = 100;
// 3 because triangle has 3 sides
for (i = 0; i < 3; i++) {
tri.points[i].x = startX + radius * Math.cos(angle);
tri.points[i].y = startY - radius * Math.sin(angle);
angle += (2 * Math.PI) / 3;
}
context.beginPath();
context.moveTo(tri.points[0].x, tri.points[0].y);
for (i = 1; i < 3; i++) {
context.lineTo(tri.points[i].x, tri.points[i].y);
}
context.closePath();
// the outline
context.lineWidth = 5;
tri.outline = "#000000"
context.strokeStyle = tri.outline;
context.stroke();
// the fill color
tri.fill = getRandomColor();
context.fillStyle = tri.fill;
context.fill();
tempTriangle = tri;
}
function resetCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
#import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
h1 {
display: flex;
justify-content: center;
font-family: 'Roboto', sans-serif;
}
#paint-canvas {
border: 1vh solid black;
}
#canvs {
display: flex;
flex-flow: column;
align-items: center;
}
#buttn {
margin: 5vh 0 0 0;
}
<div id="canvs">
<canvas id="paint-canvas" width="800" height="400"></canvas>
<button id="buttn" onclick="resetCanvas()">Clear/Reset</button>
</div>

Related

How to drag points with it's connecting line in html5 canvas?

Following is my code. I have face issue when I drag a point. The problem is that whenever I drag point all the line connect to one point. I need to have draggable points in a html5 Canvas. But I have a supplementary constraint: the 2 points must be linked by a line. When I drag a point, the line must be dynamically drawn, and still linked to the 2 points.
let points= [];
let drag_point= -1;
let pointSize= 6;
let canvas = document.querySelector("#myCanvas");
let w = canvas.width;
let h = canvas.height;
var ctx = canvas.getContext("2d");
$("#myCanvas").mousedown(function (e) {
var pos = getPosition(e);
drag_point = getPointAt(pos.x, pos.y);
console.log("pos", drag_point);
if (drag_point == -1) {
// no point at that position, add new point
drawlines(pos.x, pos.y);
points.push(pos);
}
});
$("#myCanvas").mousemove(function (e) {
if (drag_point != -1) {
// if currently dragging a point...
var pos = getPosition(e);
//...update that.points position...
points[drag_point].x = pos.x;
points[drag_point].y = pos.y;
redraw(); // ... and redraw myCanvas
}
});
$("#myCanvas").mouseup(function (e) {
drag_point = -1;
});
function getPosition(event) {
var rect = canvas.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
console.log(x, y);
return { x: x, y: y };
}
function getPointAt(x, y) {
for (var i = 0; i < points.length; i++) {
if (
Math.abs(points[i].x - x) < pointSize &&
Math.abs(points[i].y - y) < pointSize
)
// check if x,y is inside points bounding box. replace with pythagoras theorem if you like.
return i;
}
return -1; // no point at x,y
}
function redraw() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
for (var i = 0; i < points.length; i++) {
// draw all points again
drawlines(points[i].x, points[i].y);
}
}
function drawlines(x, y) {
drawImages(x, y);
if (points.length > 0) {
var last = points[points.length - 1];
ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(x, y);
ctx.strokeStyle = "blue";
ctx.stroke();
}
}
function drawImages(x, y) {
var ctx = document.getElementById("myCanvas").getContext("2d");
ctx.beginPath();
ctx.arc(x, y, pointSize, 0, Math.PI * 2, true);
ctx.strokeStyle = "red";
ctx.stroke();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas
id="myCanvas"
width="1000"
height="1000"
style="border: 1px solid #d3d3d3"
></canvas>
.
See code below...
I refactored your drawlines and drawImages to be called independently not inside a common loop, in my code we draw all lines, then we draw all circles, that way we don't have to change colors back and forth all the time and prevents any overlaps of the lines over the circles, also another change is in the mousedown I call the redraw instead of drawlines.
Looking at your code educated guess the problem is in your:
var last = points[points.length - 1];
that seems very off, technically that is making the last always be the same
let points = [{x:10,y:10},{x:55,y:50},{x:100,y:10}];
let drag_point = -1;
let pointSize = 6;
let canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
canvas.onmousedown = function(e) {
var pos = getPosition(e);
drag_point = getPointAt(pos.x, pos.y);
if (drag_point == -1) {
points.push(pos);
redraw();
}
};
canvas.onmousemove = function(e) {
if (drag_point != -1) {
var pos = getPosition(e);
points[drag_point].x = pos.x;
points[drag_point].y = pos.y;
redraw();
}
};
canvas.onmouseup = function(e) {
drag_point = -1;
};
function getPosition(event) {
var rect = canvas.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
return {x, y};
}
function getPointAt(x, y) {
for (var i = 0; i < points.length; i++) {
if (
Math.abs(points[i].x - x) < pointSize &&
Math.abs(points[i].y - y) < pointSize
)
return i;
}
return -1;
}
function redraw() {
if (points.length > 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawLines()
drawCircles()
}
}
function drawLines() {
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
points.forEach((p) => {
ctx.lineTo(p.x, p.y);
})
ctx.stroke();
}
function drawCircles() {
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
points.forEach((p) => {
ctx.beginPath();
ctx.arc(p.x, p.y, pointSize, 0, Math.PI * 2, true);
ctx.stroke();
})
}
redraw()
<canvas id="myCanvas" width=160 height=160 style="border: 1px solid"></canvas>

Determine if a Selection Marquee is over a Rotated Rectangle

I have a Rectangle class for drawing to HTML Canvas. It has a rotation property that gets applied in its draw method. If the user drags within the canvas, a selection marquee is being drawn. How can I set the Rectangle's active attribute to true when the Rectangle is within the selection marquee using math? This is a problem I'm having in another language & context so I do not have all of Canvas' methods available to me there (e.g. isPointInPath).
I found a StackOverflow post about finding Mouse position within rotated rectangle in HTML5 Canvas, which I am implementing in the Rectangle method checkHit. It doesn't account for the selection marquee, however. It's just looking at the mouse X & Y, which is still off. The light blue dot is the origin around which the rectangle is being rotated. Please let me know if anything is unclear. Thank you.
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,255,1)';
ctx.beginPath();
ctx.arc(0, 0, 3, 0, 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,0,0.5)';
} else {
ctx.fillStyle = 'rgba(0,0,255,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,-3];
var BB=canvas.getBoundingClientRect();
var offsetX=BB.left;
var offsetY=BB.top;
var start_x,start_y;
let draw = () => {
ctx.clearRect(0,0, canvas.width, canvas.height);
//rect.rotation+=1;
rect.draw();
ctx.fillStyle = "rgba(200, 200, 255, 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[1]),parseInt(marquee[2]),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,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,-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>
Do convex polygons overlap
Rectangles are convex polygons.
Rectangle and marquee each have 4 points (corners) that define 4 edges (lines segments) connecting the points.
This solution works for all convex irregular polygons with 3 or more sides.
Points and edges must be sequential either Clockwise CW of Count Clockwise CCW
Test points
If any point of one poly is inside the other polygon then they must overlap. See example function isInside
To check if point is inside a polygon, get cross product of, edge start to the point as vector, and the edge as a vector.
If all cross products are >= 0 (to the left of) then there is overlap (for CW polygon). If polygon is CCW then if all cross products are <= 0 (to the right of) there is overlap.
It is possible to overlap without any points inside the other poly.
Test Edges
If any of the edges from one poly crosses any of the edges from the other then there must be overlap. The function doLinesIntercept returns true if two line segments intercept.
Complete test
Function isPolyOver(poly1, poly2) will return true if there is overlap of the two polys.
A polygon is defined by a set of Point's and Lines's connecting the points.
The polygon can be irregular, meaning that each edge can be any length > 0
Do not pass polygons with an edge of length === 0 or will not work.
Added
I added the function Rectangle.toPoints that transforms the rectangle and returning a set of 4 points (corners).
Example
Example is a copy of your code working using the above methods.
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(), 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(), 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,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, y, 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(), 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, 0, 0, 1, this.x, this.y);
ctx.fillStyle = 'rgba(255,255,255,1)';
ctx.strokeStyle = this.active ? 'rgba(255,0,0,1)' : 'rgba(0,0,255,1)';
ctx.lineWidth = this.active ? 3 : 1;
ctx.beginPath();
ctx.arc(0, 0, 3, 0, 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, 0, 1, 0, 0);
ctx.clearRect(0, 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, 0, 1, 0, 0);
ctx.fillStyle = "rgba(200, 200, 255, 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, 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>

Initializing a possibly infinite array?

I'm attempting to make sand fall from the mouse (i've changed the x and y of the sand to be the center of the canvas for testing purposes.)
I'm having some issues with one of my functions. drawSand() will not run - I believe it's because sandObj[] can't be initialized before hand, but i'm uncertain. i've tried initializing it by just limiting the array to 200 elements but that didn't seem to help any. I've added some booleans to test to see if the function is finishing and draw is the only one that isn't (besides the drawPaintTool, it's not enabled on purpose) Any help / criticism would be helpful!
"use strict";
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var ballX = 0;
var ballY = 0;
var ballRadius = 20;
var sandObj = [];
var sandActive = false;
var sandAmount = 10;
var sandX = 10;
var sandY = 0;
var testDrawPaintToolFunction = false;
var testPaintToolFunction = false;
var testDrawSandFunction = false;
var testMoveSandFunction = false;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
sandAmountDisplay();
paintTool();
moveSand();
drawSand();
}
function drawPaintTool() {
ctx.beginPath();
ctx.arc(ballX, ballY, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "white";
ctx.fill();
ctx.closePath();
testDrawPaintToolFunction = true;
}
function paintTool() {
if (sandActive == true) {
sandAmount++;
}
testPaintToolFunction = true;
}
function drawSand() {
for (var i = 0; i < sandAmount; i++) {
sandObj = {
x: canvas.width / 2,
y: canvas.height / 2
};
ctx.beginPath();
ctx.rect(sandObj[i].x, sandObj[i].y, 5, 5);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
testDrawSandFunction = true;
}
function moveSand() {
testMoveSandFunction = true;
}
function sandAmountDisplay() {
ctx.font = "16px Arial";
ctx.fillStyle = "black";
ctx.fillText("Sand Amount: " + sandAmount, 8, 20);
ctx.fillText("Sand Active: " + sandActive, 8, 40);
ctx.fillText("drawPaintTool: " + testDrawPaintToolFunction, 8, 60);
ctx.fillText("PaintTool: " + testPaintToolFunction, 8, 80);
ctx.fillText("drawSand: " + testDrawSandFunction, 8, 100);
ctx.fillText("moveSand: " + testMoveSandFunction, 8, 120);
}
document.addEventListener("mousemove", mouseMoveHandler, false);
document.addEventListener("mousedown", mouseDownHandler);
document.addEventListener("mouseup", mouseUpHandler);
function mouseMoveHandler(e) {
var relativeX = e.clientX - canvas.offsetLeft + ballRadius / 2;
var relativeY = e.clientY - canvas.offsetTop + ballRadius / 2;
if (relativeX > 0 && relativeX < canvas.width) {
ballX = relativeX - ballRadius / 2;
}
if (relativeY > 0 && relativeY < canvas.height) {
ballY = relativeY - ballRadius / 2;
}
}
function mouseDownHandler() {
sandActive = true;
}
function mouseUpHandler() {
sandActive = false;
}
setInterval(draw, 10);
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Gamedev Canvas Workshop</title>
<style>
* {
padding: 0;
margin: 15;
}
canvas {
background: #eee;
display: block;
margin: 0 auto;
}
</style>
</head>
<body>
<canvas id="myCanvas" width="480" height="320"></canvas>
<script src="sand.js">
</script>
</body>
</html>
so to just help out some, because if I comment the code I would most likely have to write a larger paragraph just to get this posted, i'm going to explain the drawSand function. So the for loop used is for re-drawing all the sand everytime it's called - however, i've had to set the x and y here because I couldn't think of a way to initialize something that could continuously spawn sand. I'm lost to be honest.
EDIT: Also the sandAmount is changing constantly which is most likely the problem with something like this. when the mouse is held down the sandAmount goes up - which is what paintTool is for.
Inside of this function:
function drawSand() {
for (var i = 0; i < sandAmount; i++) {
sandObj = {
x: canvas.width / 2,
y: canvas.height / 2
};
ctx.beginPath();
ctx.rect(sandObj[i].x, sandObj[i].y, 5, 5);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
testDrawSandFunction = true;
}
...you are assigning a new object value to sandObj, which was previously an array. You are accessing it as an array with sandObj[i]... further down, so I'm guessing that was a mistake on your part. Perhaps you meant to do:
sandObj[i] = {
x: canvas.width / 2,
y: canvas.height / 2
};
...rather than:
sandObj = {
x: canvas.width / 2,
y: canvas.height / 2
};

Canvas mouseover

I'm having a canvas on which i have created dynamically some rectangles and when you hover over them i need to show an username that's stored, i tried to do it like this but i didn't got the result that i desired. The rectangles are not html elements so i can't use classes or ids.
This is the code that i tried:
canvas.addEventListener('mouseover', (evt)=>{
mousePos = onMousePos(canvas,evt);
for(let i=0;i<rectArray.length;i++)
if(ctx.isPointInPath(mousePos.x,mousePos.y))
console.log(rectArray[i].username);
});
The onMousePos function checks the that the mouse is inside of an hexagon and i use the same function for mouseup, mousedown and mousemove and it works.
EDIT the onMousePos is this:
function onMousePos(canvas, evt) {
const rect = canvas.getBoundingClientRect();
if (history && timeline) {
return {
x: Math.round(evt.clientX - rect.left) * 1.18,
y: Math.round(evt.clientY - rect.top) * 1.05
};
}
if (timeline && history === false) {
return {
x: Math.round(evt.clientX - rect.left),
y: Math.round(evt.clientY - rect.top) * 1.05
};
}
if (history && timeline === false) {
return {
x: Math.round(evt.clientX - rect.left) * 1.18,
y: Math.round(evt.clientY - rect.top)
};
}
return {
x: Math.round(evt.clientX - rect.left),
y: Math.round(evt.clientY - rect.top)
};
}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: black;
}
canvas {
display: block;
margin: auto;
border: solid 1px white;
border-radius: 10px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="application/javascript">
void function() {
"use strict";
// Classes
/*
Constructor function
when called with new, everything attached to the 'this' keyword
becomes a new member of the new object
*/
function ToolTip(text) {
this.text = text;
}
/*
Constructor prototype, a collection
of values & functions that are shared across all instances,
use for constant values and member functions
*/
ToolTip.prototype = {
TEXT_SIZE: 15,
TEXT_FONT: "15px Arial",
TEXT_COLOUR: "#FFFFFFFF",
BOX_BORDER_COLOUR: "#000000FF",
BOX_BACKGROUND_COLOUR: "#990000FF",
render: function(ctx,x,y) {
ctx.fillStyle = this.BOX_BACKGROUND_COLOUR;
ctx.strokeStyle = this.BOX_BORDER_COLOUR;
ctx.font = this.TEXT_FONT;
ctx.beginPath();
ctx.rect(
x,
y - this.TEXT_SIZE,
ctx.measureText(this.text).width,
this.TEXT_SIZE
);
ctx.fill();
ctx.stroke();
ctx.fillStyle = this.TEXT_COLOUR;
ctx.fillText(this.text,x,y - 2);
}
};
function Rectangle(x,y,width,height,name) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.tooltip = new ToolTip(name);
}
Rectangle.prototype = {
BORDER_COLOUR: "#000000FF",
BACKGROUND_COLOR: "#0000AAFF",
contains: function(x,y) {
return x > this.x && x < this.x + this.width
&& y > this.y && y < this.y + this.height;
},
render: function(ctx) {
ctx.strokeStyle = this.BORDER_COLOUR;
ctx.fillStyle = this.BACKGROUND_COLOR;
ctx.beginPath();
ctx.rect(this.x,this.y,this.width,this.height);
ctx.fill();
ctx.stroke();
}
};
// Variables
var canvasWidth = 150;
var canvasHeight = 150;
var canvas = null;
var ctx = null;
var rectangles = null;
// Functions
function onMouseMove(e) {
var bounds = canvas.getBoundingClientRect();
var x = e.clientX - bounds.left;
var y = e.clientY - bounds.top;
draw();
for (var i = 0; i < rectangles.length; ++i) {
var rectangle = rectangles[i];
if (rectangle.contains(x,y)) {
rectangle.tooltip.render(ctx,x,y);
return;
}
}
}
function draw() {
ctx.fillStyle = "gray";
ctx.fillRect(0,0,canvasWidth,canvasHeight);
for (var i = 0; i < rectangles.length; ++i) {
rectangles[i].render(ctx);
}
}
// Entry Point
onload = function() {
canvas = document.getElementById("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
canvas.onmousemove = onMouseMove;
ctx = canvas.getContext("2d");
rectangles = [
new Rectangle(10,10,25,25,"User 1"),
new Rectangle(45,10,25,25,"User 2"),
new Rectangle(80,10,25,25,"User 3"),
new Rectangle(115,10,25,25,"User 4"),
new Rectangle(10,45,25,25,"User 5"),
new Rectangle(45,45,25,25,"User 6"),
new Rectangle(80,45,25,25,"User 7"),
new Rectangle(115,45,25,25,"User 8"),
new Rectangle(10,80,25,25,"User 9"),
new Rectangle(45,80,25,25,"User 10"),
new Rectangle(80,80,25,25,"User 11"),
new Rectangle(115,80,25,25,"User 12"),
new Rectangle(10,115,25,25,"User 13"),
new Rectangle(45,115,25,25,"User 14"),
new Rectangle(80,115,25,25,"User 15"),
new Rectangle(115,115,25,25,"User 16")
];
draw();
}
}();
</script>
</body>
</html>

How to update text written on canvas

When I try to change the scoreboard using the computerCount variable (as you can see in my if statement in the update function for the ball variable) the other numbers aren't shown properly. Keep in mind that at first 0 works properly.
JSFiddle format - You can change code by going at top right corner that says "Edit" in JSFiddle.
Code:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Pong</title>
<!-- Basic styling, centering the canvas -->
<style>
canvas{
position: absolute;
margin: auto;
top:60px;
bottom:0;
right:0;
left:0;
}
#scoreBoard{
position: absolute;
margin: auto;
top:0;
bottom:640px;
right:720px;
left:20px;
}
</style>
</head>
<body>
<canvas id = "scoreBoard" width="500" height = "100"></canvas>
<script>
var WIDTH = 700
var HEIGHT = 600
var pi = Math.PI
var canvas
var ctx
var keystate
var upArrow = 38;
var downArrow = 40;
var computerCount = 0;
var playerCount = 0;
var player = {
x: null,
y: null,
width: 20,
height: 100,
/**
* Update the position depending on pressed keys
*/
update: function() {
if(keystate[upArrow]){
this.y-=7;
}
if(keystate[downArrow]){
this.y+=7;
}},
/**
* Draw the player paddle to the canvas
*/
draw: function() {
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
/**
* The ai paddle
*
* #type {Object}
*/
var ai = {
x: null,
y: null,
width: 20,
height: 100,
/**
* Update the position depending on the ball position
*/
update: function() {
var desty = ball.y-(this.height - ball.side)*0.5
this.y += (desty-this.y)*0.2;
},
draw: function() {
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
/**
* The ball object
*
* #type {Object}
*/
var ball = {
x: null,
y: null,
vel:null,
side: 20,
speed:5,
update: function() {
this.x += this.vel.x;
this.y += this.vel.y;
if(0>this.y || this.y+this.side>HEIGHT){
var offset = this.vel.y<0 ? 0-this.y : HEIGHT-(this.y+this.side);
this.y+=2*offset
this.vel.y*=-1;
}
var AABBIntersect = function(ax, ay, aw, ah, bx, by, bw, bh) {
return ax < bx+bw && ay < by+bh && bx < ax+aw && by < ay+ah;
};
var pdle = this.vel.x<0 ? player : ai;
if(AABBIntersect(pdle.x,pdle.y,pdle.width,pdle.height, this.x, this.y, this.side, this.side)){
this.x = pdle===player ? player.x + player.width : ai.x - this.side;
var n = (this.y+this.side-pdle.y)/(pdle.height+this.side)
var phi = 0.25*pi*(2*n - 1) // pi/4 = 45
this.x = pdle===player ? player.x+player.width : ai.x - this.side;
var n = (this.y+this.side - pdle.y)/(pdle.height+this.side);
var phi = 0.25*pi*(2*n - 1); // pi/4 = 45
this.vel.x = (pdle===player ? 1 : -1)*this.speed*Math.cos(phi);
this.vel.y = this.speed*Math.sin(phi);
}
if(ball.x<0){
computerCount =+1;
ball.x = (WIDTH - ball.side) / 2;
ball.y = (HEIGHT - ball.side) / 2;
}
},
// reset the ball when ball outside of the canvas in the
draw: function() {
ctx.fillRect(this.x, this.y, this.side, this.side);
}
}
function score(){
}
/**
* Starts the game
*/
function main() {
// create, initiate and append game canvas
canvas = document.createElement("canvas");
canvas.width = WIDTH;
canvas.height = HEIGHT;
ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas2 = document.createElement("canvas");
canvas2.setAttribute("id", "scoreBoard");
canvas2.width = 200;
canvas2.height = 100;
ctx2 = canvas2.getContext("2d");
document.body.appendChild(canvas2);
keystate = {};
document.addEventListener("keydown" , function(event){
keystate[event.keyCode] = true;
})
document.addEventListener("keyup" , function(event){
delete keystate[event.keyCode];
})
init();
var loop = function() {
update();
draw();
window.requestAnimationFrame(loop, canvas);
};
window.requestAnimationFrame(loop, canvas);
}
/**
* Initatite game objects and set start positions
*/
function init() {
player.x = player.width;
player.y = (HEIGHT - player.height) / 2;
ai.x = WIDTH - (player.width + ai.width);
ai.y = (HEIGHT - ai.height) / 2;
ball.x = (WIDTH - ball.side) / 2;
ball.y = (HEIGHT - ball.side) / 2;
ball.vel = {
x:ball.speed,
y: 0
}
}
/**
* Update all game objects
*/
function update() {
ball.update();
player.update();
ai.update();
}
function draw(){
ctx.fillStyle = "black"
ctx.fillRect(0,0,WIDTH,HEIGHT)
ctx.save();
ctx2.fillStyle = "red"
ctx2.fillText(String(computerCount),100,100)
ctx2.fillText("VS",120,100)
ctx2.fillText(String(playerCount),175,100)
ctx2.font = "bold 40px monaco"
ctx.fillStyle = "white"
ball.draw();
player.draw();
ai.draw();
var w=4;
var x= (WIDTH-w)*0.5;
var y=2;
var step = HEIGHT/20;
while(y<HEIGHT){
ctx.fillRect(x,y+step*0.25,w,step*0.5)
y+=step;
}
ctx.restore();
}
// start and run the game
main();
</script>
</body>
</html>
You have two problems:
When you draw a number, you do not erase the previous number that is already printed. That is, when the score becomes 1, you render 1 on top of already rendered 0. The easiest way to address it is to do fillRect and draw a black rectangle on top of the previously written score before you draw a new score.
When you fix that, you will notice that your score never goes above 1. The reason is a typo in the code that increments it, replace
computerCount =+1;
with
computerCount += 1;
Mentioned by #Ishamael, line 139's computer count needs to be changed to computerCount += 1; //After this if statement add if the player scores.
In line 221,
function draw(){
ctx.fillStyle = "black";
ctx.fillRect(0,0,WIDTH,HEIGHT);
ctx.save();
ctx2.fillStyle = "black";
ctx2.fillRect(0,0,WIDTH, 100);
ctx2.save();
ctx2.fillStyle = "red";
ctx2.font = "bold 40px monaco";
ctx2.fillText(String(computerCount),100,100);
ctx2.fillText("VS",120,100);
ctx2.fillText(String(playerCount),175,100);
ctx2.save();
ctx.fillStyle = "white";
. . .
}
remember your semicolons and set text style before writing. I also added the repaint to erase the previous scoreboard.

Categories

Resources