drawStar() with mouse inside canvas mouse up mouse down - javascript

What am I missing? The drawCircle and DrawPolygon (it is located in codepen https://codepen.io/mancod/pen/oNYdrVL work fine. I am still very new to all this still, and beat myself up as nothing in life should be this messy. A star is a circle as is a polygon. I get that the star has an inner and outer radius, but I cannot get this star. Thank you in advance for eyes that can fill in the part I am missing or have in the wrong order for function drawStar(). I have commented out the drawline and drawcircle. If you want to know it that even work you can view it on https://jsfiddle.net/mancod/mhbrqxk8/45/ where I have commented out the drawStar.
`enter code here`var canvas,
context,
dragging = false,
dragStartLocation,
snapshot;
`enter code here`function getCanvasCoordinates(event) {
var x = event.clientX - canvas.getBoundingClientRect().left,
y = event.clientY - canvas.getBoundingClientRect().top;
return {x: x, y: y};
}
`enter code here`function takeSnapshot (){
snapshot = context.getImageData(0, 0, canvas.width, canvas.height);
}
`enter code here`function restoreSnapshot() {
context.putImageData(snapshot, 0, 0);
}
`enter code here`function drawLine(position) {
context.beginPath();
context.moveTo(dragStartLocation.x, dragStartLocation.y);
context.lineTo(position.x, position.y);
context.stroke();
}
`enter code here`// this is for making circles
//d(P, Q) = p(x2 − x1)2 + (y2 − y1)2 {Distance formula}
//https://orion.math.iastate.edu/dept/links/formulas/form2.pdf
// comment out function to go back to drawing just straight lines.
function drawCircle (position) {
var radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
context.beginPath();
context.arc(position.x, position.y, radius, 0, 2 * Math.PI, false);
context.fill();
}
**function drawStar (position, points, outerRadius, innnerRadius) {
var coordinates=[],
radius = index%2 == 0? outerRadius : innerRadius,
index=0;
for (index = 0; index < points; index++) {
coordinates.push({x: dragStartLocation.x + radius * Math.cos(angle), y: dragStartLocation.y - radius * Math.sin(angle)});
angle += Math.PI / points;
context.beginPath();
context.drawStar(position, points, innerRadius, outerRadius);
context.moveTo(coordinates[0].x, coordinates[0].y+outerRadius);
//for (index = 1; index < points; index++) //{
//context.lineTo(coordinates[index].x + radius *Math.cos(angle), coordinates[index].y + radius * Math.sin(angle));
//}
}
context.closePath();
}**
function dragStart(event) {
dragging = true;
dragStartLocation = getCanvasCoordinates(event);
takeSnapshot();
}
function drag(event) {
var position;
if (dragging === true) {
restoreSnapshot();
position = getCanvasCoordinates(event);
//to not see the radius line just reverse the order of the two below
//drawCircle(position);
//drawLine(position);
drawStar(position, 6, 2, 15);
}
}
function dragStop(event) {
dragging = false;
restoreSnapshot();
var position = getCanvasCoordinates(event);
//to not see the radius line just reverse the order of the two below
//drawCircle(position);
//drawLine(position);
drawStar(postion,6, 2,15);
}
canvas = document.getElementById("cv0");
context = canvas.getContext('2d');
context.strokeStyle = 'orange';
context.fillStyle = 'hsl(' + 360*Math.random() +', 100%, 45%)';
context.lineWidth = 5;
canvas.addEventListener('mousedown', dragStart, false);
canvas.addEventListener('mousemove', drag, false);
canvas.addEventListener('mouseup', dragStop, false);

Let's have a look at the parameter definition for the drawStar() function:
drawStar (position, points, outerRadius, innnerRadius)
and remind ourselves what a typical stylized star looks like
Alright so far. There are two places the drawStar function gets called: inside draw and dragStop. In both cases you're calling it like
drawStar(position, 6, 2, 15);
This means we pass 6 as the number of points for the star shape - if we look above we can see that star is made up of 10 points.
The second mistake here is the hardcoded values 2 and 15 for the radius of the star. I think you want to dynamically size it according to the movement of the mouse, so we need to recalculate the radii on mouse move. Well as we don't have a use for the two parameters we can get rid of it altogether and just call it like:
drawStar(position, 10);
Inside the drawStar function we need to calculate the points for the star shape like:
for (index = 0; index < points; index++) {
if (index % 2 == 0) {
radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
} else {
radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2)) * 0.5;
}
coordinates.push({
x: dragStartLocation.x + radius * Math.cos(angle),
y: dragStartLocation.y - radius * Math.sin(angle)
});
angle += Math.PI / points * 2;
}
As you can see where dynamically calculating the radius for the inner and outer points, pushing the points into the coordinates array and ultimately adding 36° to the angle variable (360°/10 points=36°)
Finally let's iterate over the coordinates array and draw the lines to screen:
context.beginPath();
context.moveTo(coordinates[0].x, coordinates[0].y);
for (index = 1; index < points; index++) {
context.lineTo(coordinates[index].x, coordinates[index].y);
}
context.closePath();
context.fill();
Here's a working example based on your fiddle:
var canvas,
context,
dragging = false,
dragStartLocation,
snapshot;
function getCanvasCoordinates(event) {
var x = event.clientX - canvas.getBoundingClientRect().left,
y = event.clientY - canvas.getBoundingClientRect().top;
return {
x: x,
y: y
};
}
function takeSnapshot() {
snapshot = context.getImageData(0, 0, canvas.width, canvas.height);
}
function restoreSnapshot() {
context.putImageData(snapshot, 0, 0);
}
function drawLine(position) {
context.beginPath();
context.moveTo(dragStartLocation.x, dragStartLocation.y);
context.lineTo(position.x, position.y);
context.stroke();
}
// this is for making circles
//d(P, Q) = p(x2 − x1)2 + (y2 − y1)2 {Distance formula}
//https://orion.math.iastate.edu/dept/links/formulas/form2.pdf
// comment out function to go back to drawing just straight lines.
function drawCircle(position) {
var radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
context.beginPath();
context.arc(position.x, position.y, radius, 0, 2 * Math.PI, false);
context.fill();
}
function drawStar(position, points) {
var coordinates = [];
var index;
var radius;
var angle = Math.PI / 2;
for (index = 0; index < points; index++) {
if (index % 2 == 0) {
radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2));
} else {
radius = Math.sqrt(Math.pow((dragStartLocation.x - position.x), 2) + Math.pow((dragStartLocation.y - position.y), 2)) * 0.5;
}
coordinates.push({
x: dragStartLocation.x + radius * Math.cos(angle),
y: dragStartLocation.y - radius * Math.sin(angle)
});
angle += Math.PI / points * 2;
}
context.beginPath();
context.moveTo(coordinates[0].x, coordinates[0].y);
for (index = 1; index < points; index++) {
context.lineTo(coordinates[index].x, coordinates[index].y);
}
context.closePath();
context.fill();
}
function dragStart(event) {
dragging = true;
dragStartLocation = getCanvasCoordinates(event);
takeSnapshot();
}
function drag(event) {
var position;
if (dragging === true) {
restoreSnapshot();
position = getCanvasCoordinates(event);
//to not see the radius line just reverse the order of the two below
// drawCircle(position);
//drawLine(position);
drawStar(position, 10);
}
}
function dragStop(event) {
dragging = false;
restoreSnapshot();
var position = getCanvasCoordinates(event);
//to not see the radius line just reverse the order of the two below
// drawCircle(position);
//drawLine(position);
drawStar(position, 10);
}
canvas = document.getElementById("cv0");
context = canvas.getContext('2d');
context.strokeStyle = 'orange';
context.fillStyle = 'hsl(' + 360 * Math.random() + ', 100%, 45%)';
context.lineWidth = 5;
canvas.addEventListener('mousedown', dragStart, false);
canvas.addEventListener('mousemove', drag, false);
canvas.addEventListener('mouseup', dragStop, false);
#cv0 {
border: solid gray;
}
<canvas id='cv0' width=400 height=300></canvas>

Related

Why bluring all circle isn`t only stroke of circle

context.filter = "blur(2px)";
Why this ^ element blur all circle space not only edges
I want to make circles blur on edges to look like blured street lights
i fetch stroke in
if (stroke) { context.lineWidth = 1; context.strokeStyle = stroke; context.stroke();}
and set the value in function invoke
createCircle(x + Math.random() * 30 + 10, y - Math.random() * 50, (context.filter = "blur(2px)"));
EDIT: I want to blur only the edges of cirlce not all space(fill) to look like street light
EDIT::: ATTACH FULL CODE
let Drawing = false;
const fireworks = document.querySelector(".fireworks");
const context = fireworks.getContext("2d");
let x;
let y;
let numberCircles = 5;
const colors = ["#780000", "#c1121f", "#fdf0d5", "#003049", "#669bbc"];
//function seting window Size
function windowSize() {
fireworks.width = window.innerWidth * 2;
fireworks.height = window.innerHeight * 2;
fireworks.style.height = window.innerHeight + "px";
fireworks.style.width = window.innerWidth + "px";
context.scale(2, 2);
// console.log("windowSize");
}
//create circle
function createCircle(x, y, stroke) {
context.beginPath();
radius = Math.random() * 5 + 3;
let fill = colors[Math.round(Math.random() * colors.length - 1)];
context.arc(x, y, radius, 0, 2 * Math.PI, false);
if (fill) {
context.fillStyle = fill;
context.fill();
}
if (stroke) {
context.lineWidth = 1;
context.strokeStyle = stroke;
context.stroke();
}
}
//make circle spray DEPEND on angle
function sprayCircle() {
for (i = 0; i < numberCircles; i++) {
let los = Math.round(Math.random() * 3);
switch (los) {
case 0:
createCircle(x + Math.random() * 30 + 10, y + Math.random() * 50, (context.filter = "blur(2px)"));
break;
case 1:
createCircle(x + Math.random() * 30 + 10, y - Math.random() * 50, (context.filter = "blur(2px)"));
break;
case 2:
createCircle(x - Math.random() * 5, y - Math.random() * 5);
break;
}
}
}
//mouse follow
fireworks.addEventListener("mousedown", function (e) {
Drawing = true;
});
fireworks.addEventListener("mousemove", function (e) {
if (Drawing === true) {
sprayCircle();
x = e.offsetX;
y = e.offsetY;
}
});
window.addEventListener("mouseup", function (e) {
if (Drawing === true) {
Drawing = false;
}
});
//end
windowSize();
window.addEventListener("resize", windowSize, false);
<canvas class="fireworks" width="560" height="868"></canvas>

How to move object to target naturally and smoothly?

Can somebody fix it script to make it works properly?
What I expects:
Run script
Click at the canvas to set target (circle)
Object (triangle) starts to rotate and move towards to target (circle)
Change target at any time
How it works:
Sometimes object rotates correctly, sometimes isn't
Looks like one half sphere works well, another isn't
Thanks!
// prepare 2d context
const c = window.document.body.appendChild(window.document.createElement('canvas'))
.getContext('2d');
c.canvas.addEventListener('click', e => tgt = { x: e.offsetX, y: e.offsetY });
rate = 75 // updates delay
w = c.canvas.width;
h = c.canvas.height;
pi2 = Math.PI * 2;
// object that moves towards the target
obj = {
x: 20,
y: 20,
a: 0, // angle
};
// target
tgt = undefined;
// main loop
setInterval(() => {
c.fillStyle = 'black';
c.fillRect(0, 0, w, h);
// update object state
if (tgt) {
// draw target
c.beginPath();
c.arc(tgt.x, tgt.y, 2, 0, pi2);
c.closePath();
c.strokeStyle = 'red';
c.stroke();
// update object position
// vector from obj to tgt
dx = tgt.x - obj.x;
dy = tgt.y - obj.y;
// normalize
l = Math.sqrt(dx*dx + dy*dy);
dnx = (dx / l);// * 0.2;
dny = (dy / l);// * 0.2;
// update object position
obj.x += dnx;
obj.y += dny;
// angle between +x and tgt
a = Math.atan2(0 * dx - 1 * dy, 1 * dx + 0 * dy);
// update object angle
obj.a += -a * 0.04;
}
// draw object
c.translate(obj.x, obj.y);
c.rotate(obj.a);
c.beginPath();
c.moveTo(5, 0);
c.lineTo(-5, 4);
c.lineTo(-5, -4);
//c.lineTo(3, 0);
c.closePath();
c.strokeStyle = 'red';
c.stroke();
c.rotate(-obj.a);
c.translate(-obj.x, -obj.y);
}, rate);
This turned out to be a bit more challenging than I first thought and I ended up just re-writing the code.
The challenges:
Ensure the ship only rotated to the exact point of target. This required me to compare the two angle from the ship current position to where we want it to go.
Ensure the target did not rotate past the target and the ship did not translate past the target. This required some buffer space for each because when animating having this.x === this.x when an object is moving is very rare to happen so we need some room for the logic to work.
Ensure the ship turned in the shortest direction to the target.
I have added notes in the code to better explain. Hopefully you can implement this into yours or use it as is. Oh and you can change the movement speed and rotation speed as you see fit.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;
let mouse = { x: 20, y: 20 };
let canvasBounds = canvas.getBoundingClientRect();
let target;
canvas.addEventListener("mousedown", (e) => {
mouse.x = e.x - canvasBounds.x;
mouse.y = e.y - canvasBounds.y;
target = new Target();
});
class Ship {
constructor() {
this.x = 20;
this.y = 20;
this.ptA = { x: 15, y: 0 };
this.ptB = { x: -15, y: 10 };
this.ptC = { x: -15, y: -10 };
this.color = "red";
this.angle1 = 0;
this.angle2 = 0;
this.dir = 1;
}
draw() {
ctx.save();
//use translate to move the ship
ctx.translate(this.x, this.y);
//angle1 is the angle from the ship to the target point
//angle2 is the ships current rotation angle. Once they equal each other then the rotation stops. When you click somewhere else they are no longer equal and the ship will rotate again.
if (!this.direction(this.angle1, this.angle2)) {
//see direction() method for more info on this
if (this.dir == 1) {
this.angle2 += 0.05; //change rotation speed here
} else if (this.dir == 0) {
this.angle2 -= 0.05; //change rotation speed here
}
} else {
this.angle2 = this.angle1;
}
ctx.rotate(this.angle2);
ctx.beginPath();
ctx.strokeStyle = this.color;
ctx.moveTo(this.ptA.x, this.ptA.y);
ctx.lineTo(this.ptB.x, this.ptB.y);
ctx.lineTo(this.ptC.x, this.ptC.y);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
driveToTarget() {
//get angle to mouse click
this.angle1 = Math.atan2(mouse.y - this.y, mouse.x - this.x);
//normalize vector
let vecX = mouse.x - this.x;
let vecY = mouse.y - this.y;
let dist = Math.hypot(vecX, vecY);
vecX /= dist;
vecY /= dist;
//Prevent continuous x and y increment by checking if either vec == 0
if (vecX != 0 || vecY != 0) {
//then also give the ship a little buffer incase it passes the given point it doesn't turn back around. This allows time for it to stop if you increase the speed.
if (
this.x >= mouse.x + 3 ||
this.x <= mouse.x - 3 ||
this.y >= mouse.y + 3 ||
this.y <= mouse.y - 3
) {
this.x += vecX; //multiple VecX by n to increase speed (vecX*2)
this.y += vecY; //multiple VecY by n to increase speed (vecY*2)
}
}
}
direction(ang1, ang2) {
//converts rads to degrees and ensures we get numbers from 0-359
let a1 = ang1 * (180 / Math.PI);
if (a1 < 0) {
a1 += 360;
}
let a2 = ang2 * (180 / Math.PI);
if (a2 < 0) {
a2 += 360;
}
//checks whether the target is on the right or left side of the ship.
//We use then to ensure it turns in the shortest direction
if ((360 + a1 - a2) % 360 > 180) {
this.dir = 0;
} else {
this.dir = 1;
}
//Because of animation timeframes there is a chance the ship could turn past the target if rotating too fast. This gives the ship a 1 degree buffer to either side of the target to determine if it is pointed in the right direction.
//We then correct it to the exact degrees in the draw() method above once the if statment defaults to 'else'
if (
Math.trunc(a2) <= Math.trunc(a1) + 1 &&
Math.trunc(a2) >= Math.trunc(a1) - 1
) {
return true;
}
return false;
}
}
let ship = new Ship();
class Target {
constructor() {
this.x = mouse.x;
this.y = mouse.y;
this.r = 3;
this.color = "red";
}
draw() {
ctx.strokeStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
ctx.stroke();
}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillRect(0, 0, canvas.width, canvas.height);
ship.draw();
ship.driveToTarget();
if (target) {
target.draw();
}
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></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>

How to find out which part of the circle was clicked, using HTML5 canvas?

I am trying to create Simon game using HTML5 canvas and vanilla JavaScript. I am confused about the coordinate system in the arc() method. I have divided the circle into 4 quadrants and would like to alert the number of the quadrant clicked. But, I am not sure how to find out which part of the circle was clicked. https://jsfiddle.net/xawpLdys/1/
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = canvas.width / 2;
var y = canvas.height / 2;
var pads = [];
var angle = 2 * Math.PI / 4;
var color = ["green","red","blue","yellow"];
var Pads = function(x, y, radius, start, end) {
this.x = x;
this.y = y;
this.radius = radius;
this.start = start;
this.end = end;
};
function drawSimon(radius) {
for (var i = 0; i < 4; i++) {
context.beginPath();
context.moveTo(x, y);
context.arc(x, y, radius, i*angle, (i+1)*angle, false);
context.lineWidth = radius;
context.fillStyle = color[i];
context.fill();
context.lineWidth = 2;
context.strokeStyle = '#444';
context.stroke();
var pad = new Pads(x, y, radius, i*angle, (i+1)*angle);
pads.push(pad);
}
}
drawSimon(150);
$('#myCanvas').click(function (e) {
/*for (var i = 0; i < pads.length; i++) {
if (//condition matches) {
alert (i);
}
}*/
});
Try This
This example just translates the clicked e.pageX and e.pageY to normal quadrant system. And after some condition, you can determine which part has been clicked.
$('#myCanvas').click(function (e) {
var nx,ny;
nx=-(x- e.pageX);
ny=y- e.pageY;
if (nx>0 && ny>0){
alert('Yellow');
}else if (nx<0 && ny>0){
alert('Blue');
}else if (nx>0 && ny<0){
alert('Green');
}else if (nx<0 && ny<0){
alert('Red');
}
});
Here is the fiddle https://jsfiddle.net/xawpLdys/3/
UPDATE
John S was right, (It counts clicks that are outside the circle). To prevent the clicks outside the circle from considering, we need to just find the distance from the center of the circle and the clicked point. Then compare the distance with the circle's radius to see it is inside radius.
The updated code :
$('#myCanvas').click(function (e) {
var nx,ny;
nx=-(x- e.pageX);
ny=y- e.pageY;
var dx = Math.abs(Math.abs(x)-Math.abs(e.pageX));
var dy = Math.abs(Math.abs(y)-Math.abs(e.pageY));
var distance_clicked = Math.sqrt((dx*dx)+(dy*dy));
if(distance_clicked <= radius){
if (nx>0 && ny>0){
alert('Yellow');
}else if (nx<0 && ny>0){
alert('Blue');
}else if (nx>0 && ny<0){
alert('Green');
}else if (nx<0 && ny<0){
alert('Red');
}
}
});
Here is the updated fiddle https://jsfiddle.net/xawpLdys/8/
It still have the limitations of dividing the circle more than 4 slices.
The accepted answer seems a bit limited. It counts clicks that are outside the circle. That could be fixed fairly easily, but it would still be limited to four sections.
To determine if a point is in a sector:
First check if it is within the circle. The Pythagorean theorem comes into play here. Square the x and y values, if their sum is less than or equal to the radius squared, the point is in the circle.
If the point is in the circle, then check if its angle is between the start and end angles for the sector. You can get the point's angle using the arc tangent function from trigonometry.
Try this jsfiddle.
Here are the types that help make this work:
var Circle = function(center, radius) {
this.center = center;
this.radius = radius;
this._radiusSquared = Math.pow(this.radius, 2);
}
$.extend(Circle.prototype, {
containsPoint: function(point) {
var relPoint = this.pointToRelPoint(point);
return Math.pow(relPoint.x, 2) + Math.pow(relPoint.y, 2)
<= this._radiusSquared;
},
getAngleForPoint: function(point) {
var relPoint = this.pointToRelPoint(point);
return Math.atan2(-relPoint.y, -relPoint.x) + Math.PI;
},
pointToRelPoint: function(point) {
return { x: point.x - this.center.x, y: point.y - this.center.y }
}
});
var CircleSector = function(startAngle, endAngle) {
this.startAngle = startAngle;
this.endAngle = endAngle;
};
$.extend(CircleSector.prototype, {
containsAngle: function(angle) {
return (angle >= this.startAngle) && (angle < this.endAngle);
},
containsPoint: function(circle, point) {
return circle.containsPoint(point)
&& this.containsAngle(circle.getAngleForPoint(point));
}
});

Javascript Canvas apply Radial Gradient to Segment?

I am trying to create a shadow system for my 2D Game in a HTML5 Canvas. Right now, I am rendering my shadows like so:
function drawShadows(x, y, width) {
if (shadowSprite == null) {
shadowSprite = document.createElement('canvas');
var tmpCtx = shadowSprite.getContext('2d');
var shadowBlur = 20;
shadowSprite.width = shadowResolution;
shadowSprite.height = shadowResolution;
var grd = tmpCtx.createLinearGradient(-(shadowResolution / 4), 0,
shadowResolution, 0);
grd.addColorStop(0, "rgba(0, 0, 0, 0.1)");
grd.addColorStop(1, "rgba(0, 0, 0, 0)");
tmpCtx.fillStyle = grd;
tmpCtx.shadowBlur = shadowBlur;
tmpCtx.shadowColor = "#000";
tmpCtx.fillRect(0, 0, shadowResolution, shadowResolution);
}
graph.save();
graph.rotate(sun.getDir(x, y));
graph.drawImage(shadowSprite, 0, -(width / 2), sun.getDist(x, y), width);
graph.restore();
}
This renders a cube with a linear gradient that fades from black to alpha 0.
This however does not produce a realistic result, since it will always be a rectangle. Here is an illustration to describe the problem:
Sorry i'm not very artistic. It would not be an issue to draw the trapezoid shape. (Seen in blue). The issue is that I still there to be a gradient. Is it possible to draw a shape like that with a gradient?
The canvas is very flexible. Almost anything is possible. This example draws the light being cast. But it can just as easily be the reverse. Draw the shadows as a gradient.
If you are after realism then instead of rendering a gradient for the lighting (or shadows) use the shape created to set a clipping area and then render a accurate lighting and shadow solution.
With lineTo and gradients you can create any shape and gradient you my wish. Also to get the best results use globalCompositeOperation as they have a large variety of filters.
The demo just shows how to mix a gradient and a shadow map. (Very basic no recursion implemented, and shadows are just approximations.)
var canvas = document.getElementById("canV");
var ctx = canvas.getContext("2d");
var mouse = {
x:0,
y:0,
};
function mouseMove(event){
mouse.x = event.offsetX; mouse.y = event.offsetY;
if(mouse.x === undefined){ mouse.x = event.clientX; mouse.y = event.clientY;}
}
// add mouse controls
canvas.addEventListener('mousemove',mouseMove);
var boundSize = 10000; // a number....
var createImage = function(w,h){ // create an image
var image;
image = document.createElement("canvas");
image.width = w;
image.height = h;
image.ctx = image.getContext("2d");
return image;
}
var directionC = function(x,y,xx,yy){ // this should be inLine but the angles were messing with my head
var a; // so moved it out here
a = Math.atan2(yy - y, xx - x); // for clarity and the health of my sanity
return (a + Math.PI * 2) % (Math.PI * 2); // Dont like negative angles.
}
// Create background image
var back = createImage(20, 20);
back.ctx.fillStyle = "#333";
back.ctx.fillRect(0, 0, 20, 20);
// Create background image
var backLight = createImage(20, 20);
backLight .ctx.fillStyle = "#ACD";
backLight .ctx.fillRect(0, 0, 20, 20);
// create circle image
var circle = createImage(64, 64);
circle.ctx.fillStyle = "red";
circle.ctx.beginPath();
circle.ctx.arc(32, 32, 30, 0, Math.PI * 2);
circle.ctx.fill();
// create some circles semi random
var circles = [];
circles.push({
x : 200 * Math.random(),
y : 200 * Math.random(),
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random() + 200,
y : 200 * Math.random(),
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random() + 200,
y : 200 * Math.random() + 200,
scale : Math.random() * 0.8 + 0.3,
});
circles.push({
x : 200 * Math.random(),
y : 200 * Math.random() + 200,
scale : Math.random() * 0.8 + 0.3,
});
// shadows on for each circle;
var shadows = [{},{},{},{}];
var update = function(){
var c, dir, dist, x, y, x1, y1, x2, y2, dir1, dir2, aAdd, i, j, s, s1 ,nextDir, rev, revId;
rev = false; // if inside a circle reverse the rendering.
// set up the gradient at the mouse pos
var g = ctx.createRadialGradient(mouse.x, mouse.y, canvas.width * 1.6, mouse.x, mouse.y, 2);
// do each circle and work out the two shadow lines coming from it.
for(var i = 0; i < circles.length; i++){
c = circles[i];
dir = directionC(mouse.x, mouse.y, c.x, c.y);
dist = Math.hypot(mouse.x - c.x, mouse.y - c.y);
// cludge factor. Could not be bother with the math as the light sourse nears an object
if(dist < 30* c.scale){
rev = true;
revId = i;
}
aAdd = (Math.PI / 2) * (0.5 / (dist - 30 * c.scale));
x1 = Math.cos(dir - (Math.PI / 2 + aAdd)) * 30 * c.scale;
y1 = Math.sin(dir - (Math.PI / 2 + aAdd)) * 30 * c.scale;
x2 = Math.cos(dir + (Math.PI / 2 + aAdd)) * 30 * c.scale;
y2 = Math.sin(dir + (Math.PI / 2 + aAdd)) * 30 * c.scale;
// direction of both shadow lines
dir1 = directionC(mouse.x, mouse.y, c.x + x1, c.y + y1);
dir2 = directionC(mouse.x, mouse.y, c.x + x2, c.y + y2);
// create the shadow object to hold details
shadows[i].dir = dir;
shadows[i].d1 = dir1;
if (dir2 < dir1) { // make sure second line is always greater
dir2 += Math.PI * 2;
}
shadows[i].d2 = dir2;
shadows[i].x1 = (c.x + x1); // set the shadow start pos
shadows[i].y1 = (c.y + y1);
shadows[i].x2 = (c.x + x2); // for both lines
shadows[i].y2 = (c.y + y2);
shadows[i].circle = c; // ref the circle
shadows[i].dist = dist; // set dist from light
shadows[i].branch1 = undefined; //.A very basic tree for shadows that interspet other object
shadows[i].branch2 = undefined; //
shadows[i].branch1Dist = undefined;
shadows[i].branch2Dist = undefined;
shadows[i].active = true; // false if the shadow is in a shadow
shadows[i].id = i;
}
shadows.sort(function(a,b){ // sort by distance from light
return a.dist - b.dist;
});
// cull shdows with in shadows and connect circles with joined shadows
for(i = 0; i < shadows.length; i++){
s = shadows[i];
for(j = i + 1; j < shadows.length; j++){
s1 = shadows[j];
if(s1.d1 > s.d1 && s1.d2 < s.d2){ // if shadow in side another
s1.active = false; // cull it
}else
if(s.d1 > s1.d1 && s.d1 < s1.d2){ // if shodow intercepts going twards light
s1.branch1 = s;
s.branch1Dist = s1.dist - s.dist;
s.active = false;
}else
if(s.d2 > s1.d1 && s.d2 < s1.d2){ // away from light
s.branch2 = s1;
s.branch2Dist = s1.dist - s.dist;
s1.active = false;
}
}
}
// keep it quick so not using filter
// filter culled shadows
var shadowsShort = [];
for (i = 0; i < shadows.length; i++) {
if ((shadows[i].active && !rev) || (rev && shadows[i].id === revId)) { // to much hard work makeng shadow from inside the circles. Was a good idea at the time. But this i just an example after all;
shadowsShort.push(shadows[i])
}
}
// sort shadows in clock wise render order
if(rev){
g.addColorStop(0.3, "rgba(210,210,210,0)");
g.addColorStop(0.6, "rgba(128,128,128,0.5)");
g.addColorStop(1, "rgba(0,0,0,0.9)");
shadowsShort.sort(function(a,b){
return b.dir - a.dir;
});
// clear by drawing background image.
ctx.drawImage(backLight, 0, 0, canvas.width, canvas.height);
}else{
g.addColorStop(0.3, "rgba(0,0,0,0)");
g.addColorStop(0.6, "rgba(128,128,128,0.5)");
g.addColorStop(1, "rgba(215,215,215,0.9)");
shadowsShort.sort(function(a,b){
return a.dir - b.dir;
});
// clear by drawing background image.
ctx.drawImage(back, 0, 0, canvas.width, canvas.height);
}
// begin drawin the light area
ctx.fillStyle = g; // set the gradient as the light
ctx.beginPath();
for(i = 0; i < shadowsShort.length; i++){ // for each shadow move in to the light across the circle and then back out away from the light
s = shadowsShort[i];
x = s.x1 + Math.cos(s.d1) * boundSize;
y = s.y1 + Math.sin(s.d1) * boundSize;
if (i === 0) { // if the start move to..
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
ctx.lineTo(s.x1, s.y1);
if (s.branch1 !== undefined) { // if braching. (NOTE this is not recursive. the correct solution would to math this a function and use recursion to climb in an out)
s = s.branch1;
x = s.x1 + Math.cos(s.d1) * s.branch1Dist;
y = s.y1 + Math.sin(s.d1) * s.branch1Dist;
ctx.lineTo(x, y);
ctx.lineTo(s.x1, s.y1);
}
ctx.lineTo(s.x2, s.y2);
if (s.branch2 !== undefined) {
x = s.x2 + Math.cos(s.d2) * s.branch2Dist;
y = s.y2 + Math.sin(s.d2) * s.branch2Dist;
ctx.lineTo(x, y);
s = s.branch2;
ctx.lineTo(s.x2, s.y2);
}
x = s.x2 + Math.cos(s.d2) * boundSize;
y = s.y2 + Math.sin(s.d2) * boundSize;
ctx.lineTo(x, y);
// now fill in the light between shadows
s1 = shadowsShort[(i + 1) % shadowsShort.length];
nextDir = s1.d1;
if(rev){
if (nextDir > s.d2) {
nextDir -= Math.PI * 2
}
}else{
if (nextDir < s.d2) {
nextDir += Math.PI * 2
}
}
x = Math.cos((nextDir+s.d2)/2) * boundSize + canvas.width / 2;
y = Math.sin((nextDir+s.d2)/2) * boundSize + canvas.height / 2;
ctx.lineTo(x, y);
}
// close the path.
ctx.closePath();
// set the comp to lighten or multiply
if(rev){
ctx.globalCompositeOperation ="multiply";
}else{
ctx.globalCompositeOperation ="lighter";
}
// draw the gradient
ctx.fill()
ctx.globalCompositeOperation ="source-over";
// draw the circles
for (i = 0; i < circles.length; i++) {
c = circles[i];
ctx.drawImage(circle, c.x - 32 * c.scale, c.y - 32 * c.scale, 64 * c.scale, 64 * c.scale);
}
// feed the herbervors.
window.requestAnimationFrame(update);
}
update();
.canC { width:400px; height:400px;}
<canvas class="canC" id="canV" width=400 height=400></canvas>

Categories

Resources