Cursor trail algorithm for p5.js - javascript

I found this little coding exercise on Processing's website (https://processing.org/examples/storinginput.html) and decided to make a p5.js version of it.
The part that I do not understand about this algorithm is how the ellipses drawn (in the trail) shrinks when variable i, which is used as the scale of the ellipse, is increasing. I suspect that it has something to do with the value of variable index but I am unable piece it together.
Does anyone know how this algorithm works? Any help would be appreciated.
Here is the Javascript version of code:
var num = 60;
var mx = [];
var my = [];
function setup() {
createCanvas(windowHeight, windowHeight);
noStroke();
fill('rgba(0,0,0, 0.5)');
noCursor();
}
function draw() {
background(255);
var array_pos = (frameCount) % num;
mx[array_pos] = mouseX;
my[array_pos] = mouseY;
for (var i = 0; i < num; i++) {
var index = (array_pos + 1 + i) % num;
ellipse(mx[index], my[index], i, i);
}
}

The current mouse position is stored in an array in every frame. When the array is full, it will be filled again from the beginning. This is achieved using the modulo (%) operator (% computes the remainder of a division).
var array_pos = frameCount % num;
mx[array_pos] = mouseX;
my[array_pos] = mouseY;
The diameter of the circle depends on the control variable of the loop (i). The smallest circle is the circle with index array_pos + 1. Therefore with i == 0 the circle with the index array_pos + 1 is drawn. The following circles become larger as i increases. Again, the modulo operator (%) is used to prevent the array from being accessed out of bounds.
var index = (array_pos + 1 + i) % num;
var num = 60;
var mx = [];
var my = [];
function setup() {
createCanvas(windowWidth, windowHeight);
noCursor();
}
function draw() {
var array_pos = frameCount % num;
mx[array_pos] = mouseX;
my[array_pos] = mouseY;
background(255);
noStroke();
fill(255, 0, 0, 127);
for (var i = 0; i < num; i++) {
var index = (array_pos + 1 + i) % num;
ellipse(mx[index], my[index], i, i);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>

Related

Force calculation for magnetic pendulum and numerical integration

I've been working on a simulation of a magnetic pendulum (Magnetic Pendulum for reference). Now, I have a question concerning how you calculate the forces/acceleration:
In examples you find online of the magnetic pendulum (such as the one provided above), and other physics force and acceleration calculations, the forces are calculated at a starting position, the position updated according to time-step dt and then new forces at time t+dt calculated to get a new position and so on. Basic numerical integration, makes sense.
However, I noticed, that if I calculate the forces this way, things don't go as one would expect. For example: when the pendulum is attracted by a magnet it just stops at the particluar magnet even tough you would expect it to "overswing" a bit. Therefore, I introduced three variables for each force which is all previous forces added together, and now it somehow seems to work correctly.
I'm wondering if
the way of calculating the forces I used makes sense in a physics simulation and
if not, then what am I doing wrong? Because it seems to work for other people.
Here's my code:
p5.disableFriendlyErrors = true;
let magnete = [];
let particles = [];
var maganzahl = 3; //number of magnets
var magradius = 100; //radius of magnets from mid-point
var G = 0.002; //coefficient of force towards middle (gravity)
var R = 0.2; //friction coefficient
var h = 50; //height of bob over magnets
var M = 150000; //strength of magnets
var m = 1; //mass (might not work)
var dt = 0.25; //timestep
var d = 2; //pixel density
var counter2 = 0;
var Reset;
var end = false;
//--------------------------------------------
function setup() {
createCanvas(600, 600);
pixelDensity(d);
background(60);
Reset = createButton('Reset');
Reset.position(width + 19, 49);
Reset.mousePressed(resetcanvas);
//construction of magnets
for (var i = 0; i < maganzahl; i++) {
magnete.push(new Magnet((width / 2) + magradius * cos(TWO_PI * (i / maganzahl)), (height / 2) + magradius * sin(TWO_PI * (i / maganzahl)), i));
}
}
//construction of a new "starting position" by mouse-click
function mousePressed() {
if (mouseX < width && mouseY < height) {
particles.push(new Particle(mouseX, mouseY));
}
}
function draw() {
for (var i = 0; i < particles.length; i++) {
if (particles[i].counter < 1000) {
//5 updates per frame(to speed it up)
for (var k = 0; k < 5; k++) {
for (var j = 0; j < magnete.length; j++) {
particles[i].attracted(magnete[j]);
}
particles[i].update();
particles[i].show2();
}
} else if (particles[i].counter < 1001) {
particles[i].counter += 1;
var nearest = [];
for (var j = 0; j < magnete.length; j++) {
nearest.push(particles[i].near(magnete[j]));
}
if (nearest.indexOf(min(nearest)) == 0) {
var c = color("green");
}
if (nearest.indexOf(min(nearest)) == 1) {
var c = color("purple");
}
if (nearest.indexOf(min(nearest)) == 2) {
var c = color("orange");
}
if (nearest.indexOf(min(nearest)) == 3) {
var c = color("blue");
}
if (nearest.indexOf(min(nearest)) == 4) {
var c = color("red");
}
if (nearest.indexOf(min(nearest)) == 5) {
var c = color("yellow");
}
//show particle trace according to nearest magnet
particles[i].show(c);
}
}
//displaying magnets
for (var i = 0; i < magnete.length; i++) {
magnete[i].show();
}
//displaying mid-point
stroke(255);
circle(width / 2, height / 2, 3);
}
function resetcanvas() {
background(60);
}
function Particle(x, y) {
this.orgpos = createVector(x, y);
this.pos = createVector(x, y);
this.prev = createVector(x, y);
this.vel = createVector();
this.acc = createVector();
this.accpre = createVector();
this.accprepre = createVector();
this.velprediction = this.vel;
this.magnetf = createVector();
this.gravity = createVector();
this.friction = createVector();
this.shape = new Array();
this.counter = 0;
//calculating new positions
this.update = function() {
//predictor for velocity -> Beeman's algorithm
this.velprediction.add(this.accpre.mult(3 / 2 * dt).add(this.accprepre.mult(-1 / 2 * dt)));
var momgrav = createVector(width / 2 - this.pos.x, height / 2 - this.pos.y);
var momfric = createVector(this.velprediction.x, this.velprediction.y);
momgrav.mult(G * m); //force due to gravity
momfric.mult(-R); //force due to friction
this.gravity.add(momgrav);
this.friction.add(momfric);
//a = F/m
this.acc = createVector((this.magnetf.x + this.gravity.x + this.friction.x) / m, (this.magnetf.y + this.gravity.y + this.friction.y) / m);
//-=Beeman's Algorithm=-
this.vel.add(this.acc.mult(dt * 1 / 3).add(this.accpre.mult(dt * 5 / 6)).add(this.accprepre.mult(-1 / 6 * dt)));
this.pos.add(this.vel.mult(dt).add(this.accpre.mult(dt * dt * 2 / 3)).add(this.accprepre.mult(-1 / 6 * dt * dt)));
this.accprepre = createVector(this.accpre.x, this.accpre.y);
this.accpre = createVector(this.acc.x, this.acc.y);
this.velprediction = createVector(this.vel.x, this.vel.y);
this.counter += 1;
this.shape.push(new p5.Vector(this.pos.x, this.pos.y));
}
//calculating force due to magnets -> attracted called earlier than update in sketch.js
this.attracted = function(target) {
var magn = createVector(target.pos.x - this.pos.x, target.pos.y - this.pos.y);
var dist = sqrt(sq(h) + sq(magn.x) + sq(magn.y)); //distance bob - magnet
strength = M / (Math.pow(dist, 3));
magn.mult(strength);
this.magnetf.add(magn);
}
//calculating distance to target
this.near = function(target) {
var dist = sqrt(sq(h) + sq(this.pos.x - target.pos.x) + sq(this.pos.y - target.pos.y));
return (dist);
}
//trace
this.show = function(col) {
beginShape();
stroke(col);
for (var i = 0; i < this.shape.length - 1; i++) {
line(this.shape[i].x, this.shape[i].y, this.shape[i + 1].x, this.shape[i + 1].y);
strokeWeight(2);
noFill();
}
endShape();
}
//dots
this.show2 = function() {
strokeWeight(1)
point(this.pos.x, this.pos.y);
}
}
function Magnet(x, y, n) {
this.pos = createVector(x, y);
this.n = n;
this.show = function() {
noStroke();
//color for each magnet
if (n == 0) {
fill("green");
}
if (n == 1) {
fill("purple");
}
if (n == 2) {
fill("orange");
}
if (n == 3) {
fill("blue");
}
if (n == 4) {
fill("red");
}
if (n == 5) {
fill("yellow");
}
strokeWeight(4);
circle(this.pos.x, this.pos.y, 10);
}
}
Any help would be greatly appreciated!
I found the issue! So apparently in p5.js you have to be careful with your vector calculations. If you for example do something like:
[some variable] = [Vector].add([anotherVector].mult(2));
both [Vector] and [anotherVector] change their value. Makes sense now that I think about it...
The fact that it still seemed to work somewhat "realistically" and that I managed to generate some beautiful pictures using it (Such as this one 1 and this one 2) is still quite mysterious to me but I guess sometimes numbers just do their magic ;)
Run it:
Code Preview
If you want to change some variables/edit:
p5.js Web Editor

Making Balls Bounce

//Initializing General Setup Variables
'''var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var h = canvas.height;
var w = canvas.width;
var sAngle = 0;
const PI = Math.PI;
var r = 10;
totalNum = 5;'''
//total num is the number of balls wanted
//Function to Create The Initial Positions of All The Balls
'''var initialPos = [];
function makePositions(){
for (var i = 1; i <= totalNum; i++)
{initialPos.push([i*100*Math.random(),i*100*Math.random()])}
console.log(initialPos);
}
makePositions();'''
//Function to Draw Balls at initial positions in the array makePositions
function drawBalls (){
for (i = 0; i <= totalNum - 1; i++) {
ctx.beginPath();
ctx.arc(initialPos[i][0], initialPos[i][1], r, sAngle, 2*PI);
ctx.stroke();
ctx.fill();
}
}
drawBalls();
//Initializing Variables to Move Balls
var dt = 0.01;
var vx = 50;
var vy = 50;
//Creating New Positions With Velocities
function changePos (num){return num + vx*dt} //Chenges position based on velocity
and time
function changePos2 (num){return num - vx*dt} //Chenges position based on velocity
and time
var allPos = [];
function move () {
allPos = [];
var newPos;
for (j = 0; j <= totalNum - 1; j++){
if (0 < initialPos[j][0]< 500 && 0 < initialPos[j][1]< 500)
{newPos = initialPos[j].map(changePos)}
if (initialPos[j][0] < 0 || initialPos[j][0] > w)
{var newPos = [changePos2(initialPos[j][0]),changePos(initialPos[j][1])]}
if (initialPos[j][1] < 0 || initialPos[j][1] > h) {var newPos =
[changePos(initialPos[j][0]),changePos2(initialPos[j][1])]}
allPos.push(newPos);
}
initialPos = allPos;
console.log(allPos)
}
/* new positions are pushed into the array allPos
map iterates through the array intiialPos and performs the changePos function on each element in the array */
//Clearing Canvas
function resetCanvas () {
canvas.width = canvas.width;
}
//Drawing the Balls at their new positions
function drawBalls2 (){
for (var i = 0; i <= totalNum - 1; i++) {
ctx.beginPath();
ctx.arc(allPos[i][0], allPos[i][1], r, sAngle, 2*PI);
ctx.stroke();
ctx.fill();
}
}
//Drawing Balls -> Moving Balls -> Reset Canvas -> Draw
function ball (){
resetCanvas();
move();
drawBalls2();
}
setInterval(ball, 100 * dt);
I'm trying to make it so that in my move function when the balls hit the canvas borders, they bounce off in the opposite direction, however with my current parameters in my move function, it is not working. The balls are gathering in a corner and then freezing. Any ideas on how to fix this?
In general there are multiple flaws within your move function.
1) 0 < initialPos[j][0] < 500
Checking for ranges inJavaScript dosn't work like this.
This will boil down to (0 < initialPos[j][0]) < 500. This gets evaluated to 0 or 1 < 500 which is true for all cases.
2) initialPos[j].map(changePos)
When I understood that correctly, initialPos is a two dimensional array,
containing an array with the positions for all balls.
But with the map(changePos) you're applying the X-vector to the X and Y coordinate of the ball.
3) You're not permanently changing the vector for the given ball (The cause for the ball being stuck)
Within your corner case blocks your applying the changePos2 transformation correctly.
But since the new position will then be back into the bounds you're falling back to the normal newPos = initialPos[j].map(changePos) transition without changing the movement direction.
In order to solve that you'll need an array containing the vectors of each ball.
Then within the move loopapply this vector to the balls position. If the new position is out of the game, invert the vector component (x or y) and the ball will bounce off the wall again.

Smoothing algorithm for map tiling in JavaScript

I'm using JsIso (found it on github) to (hopefully) make a fun little browser game. I modified the hardcoded values for a height map, into a variable and function to generate terrain randomly. What I would like to do, but can't picture in my head at all, is to have a given tile no more or less than 2 levels different than the tile next to it, getting rid of towers and pits.
This is my current code:
var tileHeightMap = generateGround(10, 10); //Simple usage
function generateGround(height, width)
{
var ground = [];
for (var y = 0 ; y < height; y++)
{
ground[y] = [];
for (var x = 0; x < width; x++)
{
ground[y][x] = tile();
}
}
return ground;
function tile()
{
return (Math.random() * 5 | 0);
}
}
It looks like it would be best to modify the tile function, perhaps passing it the value of the previous tile, and not the generate ground function. If more info is needed, let me know!
You can use a two-dimensional Value Noise.
It basically works like this:
Octave #1: Create a number of random points (8, for example) that are evenly spaced in x direction and interpolate between them (if you choose linear interpolation, it could look like this):
Octave #2: Do the same thing as in #1, but double the amount of points. The amplitude should be the half of the amplitude in #1. Now interpolate again and add the values from both octaves together.
Octave #3: Do the same thing as in #2, but with the double amount of points and an amplitude that is the half of the amplitude in #2.
Continue these steps as long as you want.
This creates a one-dimensional Value Noise. The following code generates a 2d Value Noise and draws the generated map to the canvas:
function generateHeightMap (width, height, min, max) {
const heightMap = [], // 2d array containing the heights of the tiles
octaves = 4, // 4 octaves
startFrequencyX = 2,
startFrequencyY = 2;
// linear interpolation function, could also be cubic, trigonometric, ...
const interpolate = (a, b, t) => (b - a) * t + a;
let currentFrequencyX = startFrequencyX, // specifies how many points should be generated in this octave
currentFrequencyY = startFrequencyY,
currentAlpha = 1, // the amplitude
octave = 0,
x = 0,
y = 0;
// fill the height map with zeros
for (x = 0 ; x < width; x += 1) {
heightMap[x] = [];
for (y = 0; y < height; y += 1) {
heightMap[x][y] = 0;
}
}
// main loop
for (octave = 0; octave < octaves; octave += 1) {
if (octave > 0) {
currentFrequencyX *= 2; // double the amount of point
currentFrequencyY *= 2;
currentAlpha /= 2; // take the half of the amplitude
}
// create random points
const discretePoints = [];
for (x = 0; x < currentFrequencyX + 1; x += 1) {
discretePoints[x] = [];
for (y = 0; y < currentFrequencyY + 1; y += 1) {
// create a new random value between 0 and amplitude
discretePoints[x][y] = Math.random() * currentAlpha;
}
}
// now interpolate and add to the height map
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
const currentX = x / width * currentFrequencyX,
currentY = y / height * currentFrequencyY,
indexX = Math.floor(currentX),
indexY = Math.floor(currentY),
// interpolate between the 4 neighboring discrete points (2d interpolation)
w0 = interpolate(discretePoints[indexX][indexY], discretePoints[indexX + 1][indexY], currentX - indexX),
w1 = interpolate(discretePoints[indexX][indexY + 1], discretePoints[indexX + 1][indexY + 1], currentX - indexX);
// add the value to the height map
heightMap[x][y] += interpolate(w0, w1, currentY - indexY);
}
}
}
// normalize the height map
let currentMin = 2; // the highest possible value at the moment
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
if (heightMap[x][y] < currentMin) {
currentMin = heightMap[x][y];
}
}
}
// currentMin now contains the smallest value in the height map
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
heightMap[x][y] -= currentMin;
}
}
// now, the minimum value is guaranteed to be 0
let currentMax = 0;
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
if (heightMap[x][y] > currentMax) {
currentMax = heightMap[x][y];
}
}
}
// currentMax now contains the highest value in the height map
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
heightMap[x][y] /= currentMax;
}
}
// the values are now in a range from 0 to 1, modify them so that they are between min and max
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
heightMap[x][y] = heightMap[x][y] * (max - min) + min;
}
}
return heightMap;
}
const map = generateHeightMap(40, 40, 0, 2); // map size 40x40, min=0, max=2
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
for (let x = 0; x < 40; x += 1) {
for (let y = 0; y < 40; y += 1) {
const height = map[x][y];
ctx.fillStyle = 'rgb(' + height * 127 + ', 127, 127)';
// draw the tile (tile size 5x5)
ctx.fillRect(x * 5, y * 5, 5, 5);
}
}
<canvas width="200" height="200"></canvas>
Note that the values in this height map can reach from -2 to 2. To change that, change the method that is used to create the random values.
Edit:
I made a mistake there, the version before the edit reached from -1 to 1. I modified it so that you can easily specify the minimum and maximum value.
First, I normalize the height map so that the values really reach from 0 to 1. Then, I modify all values so that they are between the specified min and max value.
Also, I changed how the heights are displayed. Instead of land and water, it now displays a smooth noise. The more red a point contains, the higher it is.
By the way, this algorithm is widely used in Procedural Content Generation for games.
If you want further explanation, just ask!

Canvas: draw lots of elements with a changing gradient (emulate angular gradient)

for this project http://biduleohm.free.fr/ledohm/ (sorry, the user interface is in french but the code is in english) I need an angular gradient but it doesn't exists in native so I've implemented it using a linear gradient on a line and I draw the lines more and more longer to form a triangle. The result is graphically OK but the speed isn't really good (1850 ms for 125 triangles). It's in the tab [Répartition], it redraws if there is a keyup event on one of the inputs, don't be afraid of the apparent slowness, I've limited to maximum one redraw every 2000 ms.
Before I used a simple linear gradient on the whole triangle (but this doesn't match the reality) and the speed was OK, it draws thousands of triangles in less than a second. This function was used :
drawFrontLightForColor : function(x, y, w, h, color) {
var x2 = x - w;
var x3 = x + w;
var gradient = Distri.frontCanvas.createLinearGradient(x2, y, x3, y);
gradient.addColorStop(0, 'rgba(' + color + ', ' + Distri.lightEdgeAlpha + ')');
gradient.addColorStop(0.5, 'rgba(' + color + ', ' + (color == Distri.lightColors.cw ? Distri.lightCenterAlphaCw : Distri.lightCenterAlphaOther) + ')');
gradient.addColorStop(1, 'rgba(' + color + ', ' + Distri.lightEdgeAlpha + ')');
Distri.frontCanvas.fillStyle = gradient;
Distri.frontCanvas.beginPath();
Distri.frontCanvas.moveTo(x, y);
Distri.frontCanvas.lineTo(x2, (y + h));
Distri.frontCanvas.lineTo(x3, (y + h));
Distri.frontCanvas.lineTo(x, y);
Distri.frontCanvas.fill();
Distri.frontCanvas.closePath();
},
Then I switched to this function :
drawFrontLightForColor : function(x, y, w, h, centerColor, edgeColor) {
var ratio = w / h;
var tmpY;
var tmpW;
var x2;
var x3;
var gradient;
Distri.frontCanvas.lineWidth = 1;
for (var tmpH = 0; tmpH < h; tmpH++) {
tmpY = y + tmpH;
tmpW = Math.round(tmpH * ratio);
x2 = x - tmpW;
x3 = x + tmpW;
gradient = Distri.frontCanvas.createLinearGradient(x2, tmpY, x3, tmpY);
gradient.addColorStop(0, edgeColor);
gradient.addColorStop(0.5, centerColor);
gradient.addColorStop(1, edgeColor);
Distri.frontCanvas.beginPath();
Distri.frontCanvas.moveTo(x2, tmpY);
Distri.frontCanvas.lineTo(x, tmpY);
Distri.frontCanvas.lineTo(x3, tmpY);
Distri.frontCanvas.strokeStyle = gradient;
Distri.frontCanvas.stroke();
Distri.frontCanvas.closePath();
}
},
You can find the whole source here
I can't put the beginPath, stroke, closePath out of the loop because of the gradient which is changing every iteration (I've tried but it used the last gradient for every line (which, ironically, is identical to the first function...) which is understandable but not what I want).
I accept any advice (including redo the whole function and modify his caller to outsource some code) to improve the speed let's say 5x (ideally more).
I think you took the wrong way from the start : when doing so much changes of color, you have better operate at the pixel level.
So yes that could be with a webgl pixel shader, but you'll have to fight just to get the boilerplate running ok on all platform (or get a lib to do that for you).
And anyway there's a solution perfect for your need, and fast enough (a few ms) : use raw pixel data, update them one by one with the relevant function, then draw the result.
The steps to do that are :
- create a buffer same size as the canvas.
- iterate through it's pixel, keeping track of the x,y of the point.
- normalize the coordinates so they match your 'space'.
- compute the value for the normalized (x,y) out of all the data that you have.
- write a color (in my example i choose greyscale) out of that value.
- draw the whole buffer to canvas.
I did a jsfiddle, and here's the result with 4 data points :
fiddle is here :
http://jsfiddle.net/gamealchemist/KsM9c/3/
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
var width = canvas.width,
height = canvas.height;
// builds an image for the target canvas
function buildImage(targetCanvas, valueForXY, someData) {
var width = targetCanvas.width;
var height = targetCanvas.height;
var tempImg = ctx.createImageData(width, height);
var buffer = tempImg.data;
var offset = 0;
var xy = [0, 0];
function normalizeXY(xy) {
xy[0] = xy[0] / width ;
xy[1] = xy[1] / height;
}
for (var y = 0; y < height; y++)
for (var x = 0; x < width; x++, offset += 4) {
xy[0] = x; xy[1]=y;
normalizeXY(xy);
var val = Math.floor(valueForXY(xy, someData) * 255);
buffer[offset] = val;
buffer[offset + 1] = val;
buffer[offset + 2] = val;
buffer[offset + 3] = 255;
}
ctx.putImageData(tempImg, 0, 0);
}
// return normalized (0->1) value for x,y and
// provided data.
// xy is a 2 elements array
function someValueForXY(xy, someData) {
var res = 0;
for (var i = 0; i < someData.length; i++) {
var thisData = someData[i];
var dist = Math.pow(sq(thisData[0] - xy[0]) + sq(thisData[1] - xy[1]), -0.55);
localRes = 0.04 * dist;
res += localRes;
}
if (res > 1) res = 1;
return res;
}
var someData = [
[0.6, 0.2],
[0.35, 0.8],
[0.2, 0.5],
[0.6, 0.75]
];
buildImage(canvas, someValueForXY, someData);
// ------------------------
function sq(x) {
return x * x
}
In fact the GameAlchemist's solution isn't fast or I do something really wrong. I've implemented this algo only for the top view because the front view is much more complex.
For 120 lights the top view take 100-105 ms with the old code and it take 1650-1700 ms with this code (and moreover it still lacks a few things in the new code like the color for example):
drawTopLightForColor_ : function(canvasW, canvasD, rampX, rampY, rampZ, ledsArrays, color) {
function sq(x) {
return x * x;
}
var tmpImg = Distri.topCanvasCtx.createImageData(canvasW, canvasD);
var rawData = tmpImg.data;
var ledsArray = ledsArrays[color];
var len = ledsArray.length;
var i = 0;
for (var y = 0; y < canvasD; y++) {
for (var x = 0; x < canvasW; x++, i += 4) {
var intensity = 0;
for (var j = 0; j < len; j++) {
intensity += 2 * Math.pow(
sq((rampX + ledsArray[j].x) - x) +
sq((rampZ + ledsArray[j].y) - y),
-0.5
);
}
if (intensity > 1) {
intensity = 1;
}
intensity = Math.round(intensity * 255);
rawData[i] = intensity;
rawData[i + 1] = intensity;
rawData[i + 2] = intensity;
rawData[i + 3] = 255;
}
}
Distri.topCanvasCtx.putImageData(tmpImg, 0, 0);
},
Am I doing something wrong?

Algorithm for drawing a 5 point star

I'm currently working on a solution for drawing a standard 5-point star on the canvas using JavaScript. I'm part way there but can't figure it out entirely. I'd appreciate any tips or pointers anyone might have.
I made some changes to the code that Chris posted so it would work for me:
var alpha = (2 * Math.PI) / 10;
var radius = 12;
var starXY = [100,100]
canvasCtx.beginPath();
for(var i = 11; i != 0; i--)
{
var r = radius*(i % 2 + 1)/2;
var omega = alpha * i;
canvasCtx.lineTo((r * Math.sin(omega)) + starXY[0], (r * Math.cos(omega)) + starXY[1]);
}
canvasCtx.closePath();
canvasCtx.fillStyle = "#000";
canvasCtx.fill();
Hope it helps...
n point star, points are distributed evenly around a circle. Assume the first point is at 0,r (top), with the circle centred on 0,0, and that we can construct it from a series of triangles rotated by 2π/(2n+1):
Define a rotation function:
function rotate2D(vecArr, byRads) {
var mat = [ [Math.cos(byRads), -Math.sin(byRads)],
[Math.sin(byRads), Math.cos(byRads)] ];
var result = [];
for(var i=0; i < vecArr.length; ++i) {
result[i] = [ mat[0][0]*vecArr[i][0] + mat[0][1]*vecArr[i][1],
mat[1][0]*vecArr[i][0] + mat[1][1]*vecArr[i][1] ];
}
return result;
}
Construct a star by rotating n triangles:
function generateStarTriangles(numPoints, r) {
var triangleBase = r * Math.tan(Math.PI/numPoints);
var triangle = [ [0,r], [triangleBase/2,0], [-triangleBase/2,0], [0,r] ];
var result = [];
for(var i = 0; i < numPoints; ++i) {
result[i] = rotate2D(triangle, i*(2*Math.PI/numPoints));
}
return result;
}
Define a function to draw any given array of polygons:
function drawObj(ctx, obj, offset, flipVert) {
var sign=flipVert ? -1 : 1;
for(var objIdx=0; objIdx < obj.length; ++objIdx) {
var elem = obj[objIdx];
ctx.moveTo(elem[0][0] + offset[0], sign*elem[0][1] + offset[1]);
ctx.beginPath();
for(var vert=1; vert < elem.length; ++vert) {
ctx.lineTo(elem[vert][0] + offset[0], sign*elem[vert][1] + offset[1]);
}
ctx.fill();
}
}
Use the above to draw a 5 point star:
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
var offset = [canvas.width/2, canvas.height/2];
ctx.fillStyle="#000000";
var penta = generateStarTriangles(5, 200);
drawObj(ctx, penta, offset, true);
See it here http://jsbin.com/oyonos/2/
This is a problem where Turtle Geometry makes things simple:
5-point star:
repeat 5 times:
fwd 100,
right 144,
fwd 100,
left 72,
You need to draw the inner bits and a complete circle is 2 * PI radians. In the example below r is the radius of the encompassing circle. Code below is from an open source project (http://github.com/CIPowell/PhyloCanvas)
var alpha = (2 * Math.PI) / 10;
// works out the angle between each vertex (5 external + 5 internal = 10)
var r_point = r * 1.75; // r_point is the radius to the external point
for(var i = 11; i != 0; i--) // or i could = 10 and you could use closePath at the end
{
var ra = i % 2 == 1 ? rb: r;
var omega = alpha * i; //omega is the angle of the current point
//cx and cy are the center point of the star.
node.canvas.lineTo(cx + (ra * Math.sin(omega)), cy + (ra * Math.cos(omega)));
}
//Store or fill.
NB: This is one of those many ways to skin a cat things, I'm sure someone else has another way of doing it. Also, the reason for the decremental loop rather than the incremental is preformance. i != 0 is more efficient than i < 10 and i-- is more efficient than i++. But performance matters a lot for my code, it might not be so crucial for yours.
I was looking for such an algorithm myself and wondered if I could invent one myself. Turned out not to be too hard. So here is a small function to create stars and polygons, with options to set the number of point, outer radius, and inner radius (the latter does only apply to stars).
function makeStar(c, s, x, y , p, o, i) {
var ct = c.getContext('2d');
var points = p || 5;
var outer_radius = o || 100;
var inner_radius = i || 40;
var start_x = x || 100;
var start_y = y || 100;
var new_outer_RAD, half_new_outer_RAD;
var RAD_distance = ( 2 * Math.PI / points);
var RAD_half_PI = Math.PI /2;
var i;
ct.moveTo(start_x, start_y);
ct.beginPath();
for (i=0; i <= points; i++) {
new_outer_RAD = (i + 1) * RAD_distance;
half_new_outer_RAD = new_outer_RAD - (RAD_distance / 2);
if (s) {
ct.lineTo(start_x + Math.round(Math.cos(half_new_outer_RAD - RAD_half_PI) * inner_radius), start_y + Math.round(Math.sin(half_new_outer_RAD - RAD_half_PI) * inner_radius));
}
ct.lineTo(start_x + Math.round(Math.cos(new_outer_RAD - RAD_half_PI) * outer_radius), start_y + Math.round(Math.sin(new_outer_RAD - RAD_half_PI) * outer_radius));
}
ct.stroke();
}
var canvas = document.getElementById('canvas');
makeStar(canvas);
makeStar(canvas, true, 120,200, 7, 110, 40);

Categories