Collision Detection Javascript Not Working(rather Im not working) - javascript

I wrote a simple canvas with several bubbles that float around. They are supposed to be green when not colliding and red when any of them are. For some reason they are all green but one. And when the bubbles collide the only ones that turn red are the ones touching that original bubble that wont start off green. I know I am missing something really obvious to you guys, but I went over it a thousand times and just can't see it. If anyone can please let me know, I am ready to feel stupid... thanks.
Here is a link to the GitHub gist:
https://gist.github.com/anonymous/e172bb18c078a2e9a797b8a30fdafcc3
Here is a snippet from the collision detection to drawing the canvas:
// Draw to Canvas
var draw = function() {
ctx.clearRect(0,0,600,400);
for(var i = 0 ; i < spawnArr.length; i++){
// Collision Detect & Correct
for(var j = 0; j < spawnArr.length; j++) {
var dx = spawnArr[i].x - spawnArr[j].x;
var dy = spawnArr[i].y - spawnArr[j].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < spawnArr[i].rad + spawnArr[j].rad) {
ctx.strokeStyle = "#FF0000";
ctx.beginPath();
ctx.arc(spawnArr[i].x, spawnArr[i].y, spawnArr[i].rad, 0, 2*Math.PI);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(spawnArr[j].x, spawnArr[j].y, spawnArr[j].rad, 0, 2*Math.PI);
ctx.closePath();
ctx.stroke();
// console.log('hit');
}
else {
ctx.strokeStyle = "#00FF00";
ctx.beginPath();
ctx.arc(spawnArr[i].x, spawnArr[i].y, spawnArr[i].rad, 0, 2*Math.PI);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.arc(spawnArr[j].x, spawnArr[j].y, spawnArr[j].rad, 0, 2*Math.PI);
ctx.closePath();
ctx.stroke();
//console.log('miss');
}
}

The problem is you are drawing over the circles many times. When a circle is touching another you draw it red, then if it does not hit the next circle you draw it green. Also you are checking if the circle hit itself, so you draw all the circles red twice It can only be green or red, and it should only be drawn once.
The following will solve you problems and make it run somewhat quicker. I have added a style to each circle that defines its colour. It test for a hit and if found set the colour to red.
The second for loop starts from the position of the first for loop plus one. There is no point in checking if B hit A when you already know A hit B
// Draw to Canvas
var draw = function () {
ctx.clearRect(0, 0, 600, 400);
function drawThing(thing) {
ctx.strokeStyle = thing.style;
ctx.beginPath();
ctx.arc(thing.x, thing.y, thing.rad, 0, 2 * Math.PI);
ctx.stroke();
}
for (var i = 0; i < spawnArr.length; i++) {
var t1 = spawnArr[i];
var t1.style = "#00FF00";
// Collision Detect & Correct
for (var j = i + 1; j < spawnArr.length; j++) {
var t2 = spawnArr[j];
var dx = t1.x - t2.x;
var dy = t1.y - t2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < t1.rad + t2.rad) {
t1.style = "#FF0000";
}
}
drawThing(t1);
}
}

Related

Rotating figure 8 - leaving the path on screen in time intervals

I would like the figure 8 thats rotating around the center point to print itself (and stay there) along the circle in certain frequencies.
I need to see the overlap printed on the screen and draw its pattern around its orbit.
requestAnimationFrame(animate);
function animate(){
requestAnimationFrame(animate);
ctx.beginPath();
ctx.arc(cx,cy,10,0,Math.PI*2);
ctx.closePath();
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fill();
ctx.save();
ctx.translate(cx,cy);
ctx.rotate(rotation);
ctx.beginPath();
for (var t = 0.0; t < 15; t += 0.1){
console.log(x, y);
x = Math.sin(t)*20 + 150;
y = Math.sin(a * t + b)*20 + 150;
dy = Math.cos(a * t + b) * a;
ctx.lineTo(x, y);
if (dy < 0.1 && dy > -0.1)
ctx.fillRect(x,y,0,0);
ctx.stroke();
}
ctx.restore();
rotation+=Math.PI/50;
}
I started a codepen
https://codepen.io/braydendevito/pen/zQEJjz
I'm guessing it needs a loop, but I can't seem to get this to work.
I appreciate it.

Canvas Transparency creating perminant after-image

I am trying to achieve a tracing effect where the lines have a faded trail. The way I am trying to do it is simply by drawing the solid background once, and then on further frames draw a transparent background before drawing the new lines, so that you can still see a little of the image before it.
The issue is that I do want the lines to fade out completely after some time, but they seem to leave a permanent after image, even after drawing over them repeatedly.
I've tried setting different globalCompositeOperation(s) and it seemed like I was barking up the wrong tree there.
This code is called once
//initiate trace bg
traceBuffer.getContext("2d").fillStyle = "rgba(0, 30, 50, 1)";
traceBuffer.getContext("2d").fillRect(0, 0, traceBuffer.width, traceBuffer.height);
then inside the setInterval function it calls
//draw transparent background
ctx.fillStyle = "rgba(0, 30, 50, 0.04)";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
//set drawing settings
ctx.strokeStyle = "#AAAAAA";
ctx.lineWidth = 4;
for (let i = 0; i < tracer.layer2.length; i++){
ctx.beginPath();
ctx.moveTo(newX, newY);
ctx.lineTo(oldX, oldY);
ctx.stroke();
}
Here's an example: https://i.imgur.com/QTkeIVf.png
On the left is what I am currently getting, and on the right is the edit of what I actually want to happen.
This is how I would do it. I would build a history of the particles moving on the track. The older the position the smaller the value of the alpha value for the fill. Also for a nicer effect I would reduce the size of the circle.
I hope this is what you need.
PS: I would have loved to have your curve. Since I don't have it I've drawn a different one.
const hypotrochoid = document.getElementById("hypotrochoid");
const ctx = hypotrochoid.getContext("2d");
const cw = (hypotrochoid.width = 300);
const ch = (hypotrochoid.height = 300);
const cx = cw / 2,
cy = ch / 2;
ctx.lineWidth = 1;
ctx.strokeStyle = "#d9d9d9";
// variables for the hypotrochoid
let a = 90;
let b = 15;
let h = 50;
// an array where to save the points used to draw the track
let track = [];
//add points to the track array. This will be used to draw the track for the particles
for (var t = 0; t < 2 * Math.PI; t += 0.01) {
let o = {};
o.x = cx + (a - b) * Math.cos(t) + h * Math.cos((a - b) / b * t);
o.y = cy + (a - b) * Math.sin(t) - h * Math.sin((a - b) / b * t);
track.push(o);
}
// a function to draw the track
function drawTrack(ry) {
ctx.beginPath();
ctx.moveTo(ry[0].x, ry[0].y);
for (let t = 1; t < ry.length; t++) {
ctx.lineTo(ry[t].x, ry[t].y);
}
ctx.closePath();
ctx.stroke();
}
// a class of points that are moving on the track
class Point {
constructor(pos) {
this.pos = pos;
this.r = 3;//the radius of the circle
this.history = [];
this.historyLength = 40;
}
update(newPos) {
let old_pos = {};
old_pos.x = this.pos.x;
old_pos.y = this.pos.y;
//save the old position in the history array
this.history.push(old_pos);
//if the length of the track is longer than the max length allowed remove the extra elements
if (this.history.length > this.historyLength) {
this.history.shift();
}
//gry the new position on the track
this.pos = newPos;
}
draw() {
for (let i = 0; i < this.history.length; i++) {
//calculate the alpha value for every element on the history array
let alp = i * 1 / this.history.length;
// set the fill style
ctx.fillStyle = `rgba(0,0,0,${alp})`;
//draw an arc
ctx.beginPath();
ctx.arc(
this.history[i].x,
this.history[i].y,
this.r * alp,
0,
2 * Math.PI
);
ctx.fill();
}
}
}
// 2 points on the track
let p = new Point(track[0]);
let p1 = new Point(track[~~(track.length / 2)]);
let frames = 0;
let n, n1;
function Draw() {
requestAnimationFrame(Draw);
ctx.clearRect(0, 0, cw, ch);
//indexes for the track position
n = frames % track.length;
n1 = (~~(track.length / 2) + frames) % track.length;
//draw the track
drawTrack(track);
// update and draw the first point
p.update(track[n]);
p.draw();
// update and draw the second point
p1.update(track[n1]);
p1.draw();
//increase the frames counter
frames++;
}
Draw();
canvas{border:1px solid}
<canvas id="hypotrochoid"></canvas>

HTML5 Canvas clearRect not working with beginPath and closePath

Im using HTML5 to draw on canvas. I have an array of points in which i loop through to change the x and y coordinates each cycle of draw. What i have now is the dots moving but not clearing the old one, so just lines moving across instead of dots. I looked up other answers which said that i need to use the beginPath with clearRect but that doesn't work. Is there something wrong with the placement of the clearRect can someone help me with this canvas stuff.
draw: function () {
var ctx = this.context;
if(this.isReady){
var PI2 = Math.PI * 2;
for (var i = 0; i < this.points.length; i++) {
var point = this.points[i];
var ic = this.calculateGravity(point.x, point.y);
ctx.clearRect(0, 0, this.cw, this.ch);
ctx.beginPath();
ctx.arc(point.x - ic.nX, point.y - ic.nY, point.radius, 0, PI2);
ctx.fillStyle= point.color;
ctx.strokeStyle= point.color;
ctx.closePath();
ctx.fill();
ctx.stroke();
this.points[i].x = point.x - ic.nX;
this.points[i].y = point.y - ic.nY;
}
}
},
You should clear your canvas just before rendering your points:
if(this.isReady){
ctx.clearRect(0, 0, this.cw, this.ch);
var PI2 = Math.PI * 2;
for (var i = 0; i < this.points.length; i++) {
/* ... */
}

Change a straight line into a curved line when length is overtaken

I want to display several legs into a rectangular form in canvas.
Based on an array which groups the miles of my legs, I've made the algo to represent them proportionately on a canvas given.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var width = c.width;
var somme = 0;
var prevValue = 0;
var recapProp = [];
function drawArrow(fromx, fromy, tox, toy){
//variables to be used when creating the arrow
var headlen = 5;
var angle = Math.atan2(toy-fromy,tox-fromx);
//starting path of the arrow from the start square to the end square and drawing the stroke
ctx.beginPath();
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.stroke();
//starting a new path from the head of the arrow to one of the sides of the point
ctx.beginPath();
ctx.moveTo(tox, toy);
ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
//path from the side point of the arrow, to the other side point
ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7));
//path from the side point back to the tip of the arrow, and then again to the opposite side point
ctx.lineTo(tox, toy);
ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
//draws the paths created above
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
}
function drawCircle(centerXFrom, centerYFrom){
var radius = 3;
ctx.beginPath();
ctx.arc(centerXFrom, centerYFrom, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'green';
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = '#003300';
ctx.stroke();
ctx.beginPath();
}
function sumTab(tabTT){
for (var i = 0; i < tabTT.length; i++){
somme += tabTT[i];
}
return somme;
}
function findProportion(tabTT){
var tailleMax = tabTT.length;
sumTab(tabTT);
for(var i = 0; i < tabTT.length; i++){
var percentLeg = (tabTT[i]/somme)*100;
var tailleLeg = ((width- 20)*percentLeg)/100 ;
recapProp.push(tailleLeg);
}
for(var i = 0; i <= recapProp.length; ++i){
console.log(prevValue);
drawCircle(prevValue +5, 5);
drawArrow(prevValue + 7, 5, prevValue+recapProp[i],5);
prevValue += recapProp[i];
}
}
var tabTT = [0,5,1,8,2];
findProportion(tabTT);
<canvas id="myCanvas" height="200" width="500"></canvas>
Then, I want to display then in a rectangular form, to make a loop (below is not rectangular, but it helps you to understand) :
I've tried to manipulate quadracticCurveTo() but that's not really conclusive..
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
function drawArrow(fromx, fromy, tox, toy, radius){
//variables to be used when creating the arrow
var headlen = 5;
var r = fromx + tox;
var b = fromy + toy;
var angle = Math.atan2(r,b);
//starting path of the arrow from the start square to the end square and drawing the stroke
ctx.beginPath();
ctx.moveTo(fromx+radius, fromy);
ctx.lineTo(r-radius, fromy);
ctx.quadraticCurveTo(r, fromy, r, fromy+radius);
ctx.lineWidth = "2";
ctx.strokeStyle = '#ff0000';
ctx.stroke();
//starting a new path from the head of the arrow to one of the sides of the point
ctx.beginPath();
ctx.moveTo(r, b);
ctx.lineTo(r-headlen*Math.cos(angle-Math.PI/7),b-headlen*Math.sin(angle-Math.PI/7));
//path from the side point of the arrow, to the other side point
ctx.lineTo(r-headlen*Math.cos(angle+Math.PI/7),b-headlen*Math.sin(angle+Math.PI/7));
//path from the side point back to the tip of the arrow, and then again to the opposite side point
ctx.lineTo(r, b);
ctx.lineTo(r-headlen*Math.cos(angle-Math.PI/7),b-headlen*Math.sin(angle-Math.PI/7));
//draws the paths created above
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
}
drawArrow(50,5, 80,25, 25);
<canvas id="myCanvas" height="2000" width="2000"></canvas>
Finally, I've created the snippet I will need when I'll know how to curve my lines and keep its length !. I've calculated the perimeter of my canvas surface in order to re-calculate the proportions of my legs.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var width = c.width;
var height = c.height;
var perimetre = (width*2 + height*2);
var up = 0;
var right = 0;
var left = 0;
var bot = 0;
var somme = 0;
var prevValue = 0;
var recapProp = [];
/**********************************/
/*****<<Straight>> Arrows*********/
/********************************/
function drawArrow(fromx, fromy, tox, toy){
var headlen = 5;
var angle = Math.atan2(toy-fromy,tox-fromx);
ctx.beginPath();
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(tox, toy);
ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7));
ctx.lineTo(tox, toy);
ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = "blue";
ctx.fill();
}
/**********************************/
/************Points***************/
/********************************/
function drawCircle(centerXFrom, centerYFrom){
var radius = 3;
ctx.beginPath();
ctx.arc(centerXFrom, centerYFrom, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'green';
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = '#003300';
ctx.stroke();
ctx.beginPath();
}
function sumTab(tabTT){
for (var i = 0; i < tabTT.length; i++){
somme += tabTT[i];
}
return somme;
}
/***************************************************/
/************Get length for each leg***************/
/*************************************************/
function findProportion(tabTT){
var tailleMax = tabTT.length;
sumTab(tabTT);
for(var i = 0; i < tabTT.length; i++){
var percentLeg = (tabTT[i]/somme)*100;
var tailleLeg = ((perimetre - 20)*percentLeg)/100 ;
recapProp.push(tailleLeg);
}
/* For each leg I draw the circle and the arrow, due to the length calculated previously. If the length > the width of the canva, the arrow has to be curved */
for(var i = 0; i <= recapProp.length; ++i){
if(prevValue > width && top == 1){
drawCircle(prevValue +5, 5);
drawArrowBot(prevValue + 7, 5, prevValue+recapProp[i],5);
right = 1;
top = 0;
}
else if(prevValue > height && right == 1){
drawCircle(prevValue +5, 5);
drawArrowLeft(prevValue + 7, 5, prevValue+recapProp[i],5);
bot = 1;
right = 0;
}
else if (prevValue > width && bot == 1){
drawCircle(prevValue +5, 5);
drawArrowTop(prevValue + 7, 5, prevValue+recapProp[i],5);
bot = 0;
left = 0;
}
else {
drawCircle(prevValue +5, 5);
drawArrow(prevValue + 7, 5, prevValue+recapProp[i],5);
}
prevValue += recapProp[i];
}
}
var tabTT = [0,5,1,8,2];
findProportion(tabTT);
<canvas id="myCanvas" height="200" width="500" style="border:1px solid #000000;"></canvas>
I've commented all my code in order to help you understand the logic and what I want.
So, is it possible to curve the lines in a generic way?
I would probably do something like this:
Define a holding array with number of entries based on a resolution
Map the lines into that array setting 1's very there would be a line range, 0's for the gap.
Define a target shape such as an oval (can be any shape really!) which consists of equally many parts as the array resolution. Store each part and it's coordinate in an array (same length as the line array).
Morph each part using interpolation between the shape array and line array
Now you can produce the lines into almost any shape and form you desire.
Tip: you can of course skip one shape by mapping it directly the first time.
Tip 2: the shapes can be defined in normalized coordinates which makes it easier to translate and scale them.
Example
Here we define a rounded square and circle, then map the lines onto either, we can morph between the shapes to find a combination we like and use that (note: as the square in this example starts in "upper-right" corner and not where the circle has it's 0° there will be a small rotation as well, this can be dealt with separately as an exercise).
The rounded square could be a a bunny for that matter (for a more "tight" rounded square you can use cubic Bezier instead of quadratic as here). The key point is that the shape can be defined independently of the lines themselves. This may be overkill, but it's not so complicated and it's versatile, ie. generic.
See this answer for one way to add an arrow to the lines.
var ctx = document.querySelector("canvas").getContext("2d"),
resolution = 2000,
raster = new Uint8Array(resolution), // line raster array
shape = new Float32Array(resolution * 2), // target shape array (x2 for x/y)
shape2 = new Float32Array(resolution * 2),// target shape array 2
lines = [100, 70, 180, 35], // lines, lengths only
tLen = 0, // total length of lines + gaps
gap = 20, // gap in pixels
gapNorm, // normalized gap value for mapping
p = 0, // position in lines array
radius = 100, // target circle radius
angleStep = Math.PI * 2 / resolution, // angle step to reach circle / res.
cx = 150, cy = 150, // circle center
interpolation = 0.5, // t for interpolation
i;
// get total length of lines + gaps so we can normalize
for(i = 0; i < lines.length; i++) tLen += lines[i];
tLen += (lines.length - 2) * gap;
gapNorm = gap / tLen * 0.5;
// convert line and gap ranges to "on" in the lines array
for(i = 0; i < lines.length; i++) {
var sx = p, // start position in lines array
ex = p + ((lines[i] / tLen) * resolution)|0; // end position in lines array (int)
// fill array
while(sx <= ex) raster[sx++] = 1;
// update arrqay pointer incl. gap
p = ex + ((gapNorm * resolution)|0);
}
// Create a circle target shape split into same amount of segments as lines array:
p = 0; // reset pointer for shape array
for(var angle = 0; angle < Math.PI*2; angle += angleStep) {
shape[p++] = cx + radius * Math.cos(angle);
shape[p++] = cy + radius * Math.sin(angle);
}
// create a rounded rectangle
p = i = 0;
var corners = [
{x1: 250, y1: 150, cx: 250, cy: 250, x2: 150, y2: 250}, // bottom-right
{x1: 150, y1: 250, cx: 50, cy: 250, x2: 50, y2: 150}, // bottom-left
{x1: 50, y1: 150, cx: 50, cy: 50, x2: 150, y2: 50}, // upper-left
{x1: 150, y1: 50, cx: 250, cy: 50, x2: 250, y2: 150} // upper-right
],
c, cres = resolution * 0.25;
while(c = corners[i++]) {
for(var t = 0; t < cres; t++) {
var pos = getQuadraticPoint(c.x1, c.y1, c.cx, c.cy, c.x2, c.y2, t / cres);
shape2[p++] = pos.x;
shape2[p++] = pos.y;
}
}
// now we can map the lines array onto our shape depending on the values
// interpolation. Make it a reusable function so we can regulate the "morph"
function map(raster, shape, shape2, t) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
for(var i = 0, x, y, x1, y1, x2, y2, prev = 0; i < resolution; i++) {
x1 = shape[i*2];
y1 = shape[i*2 + 1];
x2 = shape2[i*2];
y2 = shape2[i*2 + 1];
x = x1 + (x2 - x1) * t;
y = y1 + (y2 - y1) * t;
// do we have a change?
if (prev !== raster[i]) {
if (raster[i]) { // it's on, was off. create sub-path
ctx.moveTo(x, y);
}
else { // it's off, was on, render and reset path
ctx.stroke();
ctx.beginPath();
// create "arrow"
ctx.moveTo(x + 3, y);
ctx.arc(x, y, 3, 0, 6.28);
ctx.fill();
ctx.beginPath();
}
}
// add segment if on
else if (raster[i]) {
ctx.lineTo(x, y);
}
prev = raster[i];
}
}
ctx.fillStyle = "red";
map(raster, shape, shape2, interpolation);
document.querySelector("input").onchange = function() {
map(raster, shape, shape2, +this.value / 100);
};
function getQuadraticPoint(z0x, z0y, cx, cy, z1x, z1y, t) {
var t1 = (1 - t), // (1 - t)
t12 = t1 * t1, // (1 - t) ^ 2
t2 = t * t, // t ^ 2
t21tt = 2 * t1 * t; // 2(1-t)t
return {
x: t12 * z0x + t21tt * cx + t2 * z1x,
y: t12 * z0y + t21tt * cy + t2 * z1y
}
}
<script src="https://cdn.rawgit.com/epistemex/slider-feedback/master/sliderfeedback.min.js"></script>
<label>Interpolation: <input type="range" min=0 max=400 value=50></label><br>
<canvas width=400 height=400></canvas>
Calculate the middle control point that makes a quadratic Bezier curve become a specified length.
Given:
p0, p2: the QCurves starting and ending points.
length: the desired arc-length of the quadratic Bezier Curve.
You can calculate the control point that makes the QCurve's total arc-length equal length:
Calculate the midpoint between p0 & p2.
Calculate the angle of between p0 & p2.
Calculate a point (p1) perpendicular to that midpoint at a specified distance. This is a possible control point. The perpendicular angle is the calculated angle from step#2 minus 90 degrees.
Calculate the QCurve's arc-length using p0, p1 & p2 (calculatedLength).
You've got the right middle control point if calculatedLength equals the desired length.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
var $length=$('#length');
var PI2=Math.PI*2;
var radius=5+1; // 5==fill, 1=added stroke
var p0={x:50,y:100,color:'red'};
var p2={x:175,y:150,color:'gold'};
var p1={x:0,y:0,color:'green'};
var midpoint={x:0,y:0,color:'purple'};
var perpendicularPoint={x:0,y:0,color:'cyan'};
//var points=[p0,p1,p2];
//var draggingPoint=-1;
setQLength(p0,p2,150,1);
draw();
function draw(){
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
ctx.quadraticCurveTo(p1.x,p1.y,p2.x,p2.y);
ctx.strokeStyle='blue';
ctx.lineWidth=3;
ctx.stroke();
dot(p0);
dot(p1);
dot(p2);
dot(midpoint);
dot(perpendicularPoint)
$length.text('Curve length: '+parseInt(QCurveLength(p0,p1,p2)))
}
//
function dot(p){
ctx.beginPath();
ctx.arc(p.x,p.y,radius,0,PI2);
ctx.closePath();
ctx.fillStyle=p.color;
ctx.fill();
ctx.lineWidth=1;
ctx.strokeStyle='black';
ctx.stroke();
}
function setQLength(p0,p2,length,tolerance){
var dx=p2.x-p0.x;
var dy=p2.y-p0.y;
var alength=Math.sqrt(dx*dx+dy*dy);
// impossible to fit
if(alength>length){
alert('The points are too far apart to have length='+length);
return;
}
// fit
for(var distance=0;distance<200;distance++){
// calc the point perpendicular to midpoint at specified distance
var p=pointPerpendicularToMidpoint(p0,p2,distance);
p1.x=p.x;
p1.y=p.y;
// calc the result qCurve length
qlength=QCurveLength(p0,p1,p2);
// draw the curve
draw();
// break if qCurve's length is within tolerance
if(Math.abs(length-qlength)<tolerance){
break;
}
}
return(p1);
}
function pointPerpendicularToMidpoint(p0,p2,distance){
var dx=p2.x-p0.x;
var dy=p2.y-p0.y;
var perpAngle=Math.atan2(dy,dx)-Math.PI/2;
midpoint={ x:p0.x+dx*0.50, y:p0.y+dy*0.50, color:'purple' };
perpendicularPoint={
x: midpoint.x+distance*Math.cos(perpAngle),
y: midpoint.y+distance*Math.sin(perpAngle),
color:'cyan'
};
return(perpendicularPoint);
}
// Attribution: Mateusz Matczak
// http://www.malczak.linuxpl.com/blog/quadratic-bezier-curve-length/
function QCurveLength(p0,p1,p2){
var a={x: p0.x-2*p1.x+p2.x, y: p0.y-2*p1.y+p2.y}
var b={x:2*p1.x-2*p0.x, y:2*p1.y-2*p0.y}
var A=4*(a.x*a.x+a.y*a.y);
var B=4*(a.x*b.x+a.y*b.y);
var C=b.x*b.x+b.y*b.y;
var Sabc=2*Math.sqrt(A+B+C);
var A2=Math.sqrt(A);
var A32=2*A*A2;
var C2=2*Math.sqrt(C);
var BA=B/A2;
if(A2==0 || BA+C2==0){
var dx=p2.x-p0.x;
var dy=p2.y-p0.y;
var length=Math.sqrt(dx*dx+dy*dy);
}else{
var length=(A32*Sabc+A2*B*(Sabc-C2)+(4*C*A-B*B)*Math.log((2*A2+BA+Sabc)/(BA+C2)))/(4*A32)
}
return(length);
};
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4 id=length>Curve length:</h4>
<h4>Red,Gold == start and end points<br>Purple == midpoint between start & end<br>Cyan == middle control point.</h4>
<canvas id="canvas" width=300 height=300></canvas>
var angle = 0;
draw = function() {
background(0, 0, 0);
for(var j = 0;j<20;j++){
fill(j*100,j*10,j);
var offset = 0;
for(var i =-27;i<20;i++){
var a = angle +offset;
var h = map(sin(a),-1,1,100,300);
ellipse(i*20+j*20,h,20,20);
offset+=10;
}
}
angle+=2;
};

Filling a custom-drawn shape in Javascript

I'm trying to fill an ellipse that I've made, but although I can get it to draw the outline, I can't get it to fill it. I've looked at a bunch of resources, including http://www.html5canvastutorials.com/tutorials/html5-canvas-shape-fill/ and https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Drawing_shapes, but following the advice there didn't fix the problem. I've tried to account for other errors - like spelling mistakes, errors with passing parameters, or errors with my ellipse-drawing method, but they all work independently just fine. I can draw the outline of ellipse. I can pass the context to a function. I can fill a non-ellipse. But I can't get my ellipse to fill. Here is what the code looks like:
main();
function main(){
var canvas = document.getElementById('landscape');
var context = canvas.getContext('2d');
// var mySky = new sky(0, 0);
// mySky.render(context);
var myLake = new lake(400, 500, context);
myLake.render(context);
var ctx = context;
ctx.beginPath();
ctx.moveTo(75,50);
ctx.lineTo(100,75);
ctx.lineTo(100,25);
ctx.fill();
}
function lake(x, y, context){
this.context = context;
this.x = x;
this.y = y;
var width = this.context.canvas.width/2;
var height = this.context.canvas.height/4;
var a = width/2;
var b = height/2;
var phi = Math.PI/2;
this.render = function(context){
var inc = (2*Math.PI)/200;
var end = 200*inc;
var oldX = oldY = newX = newY = 0;
var x_0 = xcoord(0);
var y_0 = ycoord(0);
console.log("" + x_0 + ", " + y_0);
var i = 0;
context.beginPath();
context.moveTo(x_0, y_0);
while(i < end){
i += inc;
newX = xcoord(i);
newY = ycoord(i);
context.lineTo(newX, newY);
context.moveTo(newX, newY);
console.log("" + newX + ", " + newY);
}
context.lineTo(x_0, y_0); // close up the ellipse
context.moveTo(x_0, y_0);
context.closePath();
context.fillStyle = '#6EB1F5';
context.fill();
}
function xcoord(t){
return x + a*Math.cos(t)*Math.sin(phi) + b*Math.sin(t)*Math.cos(phi);
}
function ycoord(t){
return y + a*Math.cos(t)*Math.cos(phi) - b*Math.sin(t)*Math.sin(phi);
}
Am I using the fill() function correctly? Is it because my ellipse isn't closed up properly? Please don't give me too much information if possible - I'd like to do it on my own, I just can't figure out what's wrong and I've spent nearly 3 hours trying to figure this out now.
Try by removing the moveTo the following places:
context.beginPath();
context.moveTo(x_0, y_0); /// keep this
while(i < end){
i += inc;
newX = xcoord(i);
newY = ycoord(i);
context.lineTo(newX, newY);
///context.moveTo(newX, newY); /// remove this
console.log("" + newX + ", " + newY);
}
///context.lineTo(x_0, y_0); /// not needed as closePath will close it
///context.moveTo(x_0, y_0); /// remove this
context.closePath();
As you are using moveTo for each new coordinates you will create sub-paths consisting of only a single line which can't be filled. You want to create a continuous line which are closed at the end forming a closed polygon.
Besides from that, you are using fill() correctly.
Since you're asking, there are easier and cheaper ways of drawing an ellipse.
Something like the following:
function ellipse(context, x, y, a, b, theta) {
context.beginPath();
context.save();
/* translate to avoid having our x and y values scaled */
context.translate(x, y);
/* we can even do some rotation. (rotate before stretching!) */
context.rotate(theta);
/* now stretch the axes */
context.scale(a, b);
/* circle of radius 1, centred at the origin */
context.arc(0, 0, 1, 0, 2*Math.PI, false);
/* undo transformations */
context.restore();
context.closePath();
}
JSFiddle here.
function ellipse(context, x, y, a, b, theta) {
context.beginPath();
context.save();
/* translate to avoid having our x and y values scaled */
context.translate(x, y);
context.scale(Math.random() * 1 ,Math.random() * 1);
/* we can even do some rotation. (rotate before stretching!) */
context.rotate(theta);
/* now stretch the axes */
context.scale(a, b);
/* circle of radius 1, centred at the origin */
context.arc(0, 0, 1, 0, 2*Math.PI, false);
/* undo transformations */
context.restore();
context.closePath();};
setInterval(function(){abc()}, 100);
var c = document.getElementById("c");
var ctx = c.getContext("2d");
function abc()
{
c.width = c.width;
for (var i=0; i < 20; i++) {
ellipse(ctx, i*50+25, 100, 20, 30, Math.random() * 10 - 5);
if (i % 3)
ctx.fill();
else
ctx.stroke();
};
};

Categories