Related
I have the following code that i've roughly redone from someone else to help learn javascript. I am getting the following error:
Uncaught TypeError: flock.addBoid is not a function
at setup (boids.js:15)
at boids.js:1
The function in question is called at the beginning in setup() and defined shortly after in the section labeled FLOCK. It is definitely not misspelled and these are the only occurrences of addBoid in the document. I'll include the whole document in case its relevant but the problem should just lie in the beginning.
I am not looking for any advice about my code other than the source and solution to this error, thanks.
setup();
draw();
//////////////////// SETUP ///////////////////////
var flock;
var ctx;
var c;
function setup(){
c = document.getElementById("boidCanvas");
ctx = c.getContext("2d");
flock = new Flock();
for(var i = 0; i < 100; i++){
var b = new Boid(ctx.width / 2, ctx.height / 2);
flock.addBoid(b);
}
}
function draw() {
ctx.fillStyle = "Blue";
ctx.fillRect(0,0,c.width,c.height);
//flock.run()
}
//////////////////// FLOCK ///////////////////////
function Flock(){
this.boids = [];
}
Flock.prototype.run = function(){
for(var i = 0; i < this.boids.length; i++){
this.boids[i].run(this.boids);
}
}
Flock.prototype.addBoid = function(b){
this.boids.push(b);
}
//////////////////// BOID ///////////////////////
function Boid(setx,sety){
this.acceleration = { x:0, y:0 };
this.velocity = { x:Math.floor(Math.random()*3)-1, y:Math.floor(Math.random()*3)-1 };
this.position = { x:setx, y:sety };
this.r = 3;
this.maxSpeed = 3;
this.maxForce = .05;
}
Boid.prototype.run = function(boids){
this.evaluate(boids);
this.update();
this.wrap();
this.render();
}
// force is a vector [x,y]
Boid.prototype.applyForce = function(force){
this.acceleration.x += force[0];
this.acceleration.y += force[1];
}
Boid.prototype.evaluate = function(boids){
var seperate = this.seperate(boids);
var align = this.align(boids);
var cohesion = this.cohesion(boids);
// Arbitrary Weights
seperate *= 1.5;
align *= 1.0;
cohesion *= 1.0;
this.applyForce(seperate);
this.applyForce(align);
this.applyForce(cohesion);
}
Boid.prototype.update = function(){
//update velocity
this.velocity += this.acceleration;
//fix velocity to max speed
var normal = normalize([this.velocity.x, this.velocity.y]);
this.velocity = constantMult(normal, this.maxSpeed);
//update position
this.position.x += this.velocity.x;
this.position.y += this.velocity.y;
//reset acceleration;
this.acceleration.x = 0;
this.acceleration.y = 0;
}
// target is a vector [x,y]
Boid.prototype.seek = function(target){
var desired = sub(target, [this.position.x, this.position.y]);
var normal = normalize(desired);
desired = constantMult(normal, this.maxSpeed);
var steer = sub(desired,[this.velocity.x, this.velocity.y])
normal = normalize(steer);
steer[0] = normal[0] * this.maxForce;
steer[1] = normal[1] * this.maxForce;
return steer;
}
Boid.prototype.render = function(){
var triangle = drawTriangle(this.velocity);
for(var i = 0; i < triangle.length; i++){
triangle[i] = constantMult(triangle[i], this.r);
}
for(i = 0; i < triangle.length; i++){
triangle[i] = add(triangle[i], this.position);
}
ctx.beginPath();
ctx.moveTo(triangle[0][0], triangle[0][1]);
for(i = 1; i < triangle.length; i++){
ctx.lineTo(triangle[i][0], triangle[i][1]);
}
ctx.closePath();
ctx.fillStyle = "White";
ctx.fill();
}
Boid.prototype.wrap = function(){
if(this.position.x < -this.r)
this.position.x = c.width + this.r;
else if(this.position.x > c.width + this.r)
this.position.x = -this.r;
if(this.position.y < -this.r)
this.position.y = c.height + this.r;
else if(this.position.y > c.height + this.r)
this.position.y = -this.r;
}
Boid.prototype.seperate = function(boids){
var desiredSeperation = 25.0;
var steer = [0,0];
var count = 0;
for(var i = 0; i < boids.length; i++){
var difference = sub(this.position, boids[i].position);
var d = Math.sqrt(Math.pow(difference[0],2), Math.pow(difference[1],2));
if(d < desiredSeperation){
var normalDiff = normalize(difference);
normalDiff = constantMult(normalDiff, 1/d);
steer = add(steer, normalDiff);
count++;
}
}
if(count > 0){
steer = constantMult(steer, 1/count);
steer = normalize(steer);
steer = constantMult(steer, this.maxSpeed);
steer = sub(steer, this.velocity);
steer = normalize(steer);
steer = constantMult(steer, this.maxForce);
}
return steer;
}
Boid.prototype.align = function(boids){
var neighborDistance = 50;
var sum = [0,0];
var count = 0;
for(var i = 0; i < boids.length; i++){
var difference = sub(this.position, boids[i].position);
var dist = Math.sqrt(Math.pow(difference[0],2), Math.pow(difference[1],2));
if(dist < neighborDistance){
sum = sum(sum, boids[i].velocity);
count++;
}
}
if(count > 0){
sum = constantMult(sum, 1/count);
sum = normalize(sum);
sum = constantMult(this.maxSpeed);
var steer = sub(sum, this.velocity);
steer = normalize(steer);
steer = constantMult(steer, this.maxForce);
return steer;
}
else
return [0,0];
}
Boid.prototype.cohesion = function(boids){
var neighborDistance = 50;
var sum = [0,0];
var count = 0;
for(var i = 0; i < boids.length; i++){
var difference = sub(this.position, boids[i].position);
var dist = Math.sqrt(Math.pow(difference[0],2), Math.pow(difference[1],2));
if(dist < neighborDistance){
sum = add(sum, boids[i].position);
count++;
}
}
if(count > 0){
sum = constantMult(sum, 1/count);
return this.seek(sum);
}
else
return [0,0];
}
//////////////////// HELPERS ///////////////////////
// returns the vector with the same direction as v but with magnitude 1 in the form [x,y]
// v is a vector in the form [x,y]
function normalize(v){
var magnitude = Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2))
var normalX = v[0] / magnitude;
var normalY = v[1] / magnitude;
return [normalX, normalY];
}
function add(a,b){
var x = a[0]+b[0];
var y = a[1]+b[1];
return [x,y];
}
// returns a-b, [ax-bx, ay-by]
function sub(a,b){
var x = a[0]-b[0];
var y = a[1]-b[1];
return [x,y];
}
function mult(a,b){
var x = a[0]*b[0];
var y = a[1]*b[1];
return [x,y];
}
function constantMult(a, n){
for(var i = 0; i < a.length; i++){
a[i] *= n;
}
}
// creates an unscaled issoceles triangle centered at the origin
// returns a list of 3 lists, each containing the coordinates of a vertex, the first being the tip
// ie. [ [x1,y1], [x2,y2], [x3,y3] ]
// heading is a vector describing the direction of the triangle in the form [x,y]
// heading does not need to be normalized
function drawTriangle(heading){
heading = normalize(heading);
var v1 = [1,0];
var v2 = [-1, .5];
var v3 = [-1,-.5];
var thetaX = Math.acos(heading[0]);
var thetaY = Math.asin(heading[1]);
var theta;
if(thetaX >= 0)
theta = (Math.PI / 2) - thetaY;
else
theta = (Math.PI / 2) - thetaX;
function rotate(v){
var xp = (v[0] * Math.cos(theta)) - (v[1] * Math.sin(theta));
var yp = (v[1] * Math.cos(theta)) + (v[0] * Math.sin(theta));
return [xp, yp];
}
v1 = rotate(v1);
v2 = rotate(v2);
v3 = rotate(v3);
return [v1,v2,v3];
}
Move the functions setup(); and draw(); to the end of the JavaScript file. The issue was that the function addBoid() was not hoisted to the top, making it undefined by setup(); and draw();.
You function declaration gets hoisted on the top of block.
function Flock(){
this.boids = [];
}
when you add properties to prototype those properties are not eligible for hoisting, its like accessing variables (let declared cause var is eligible for hoisting) before declaring them.
Flock.prototype.run = function(){
for(var i = 0; i < this.boids.length; i++){
this.boids[i].run(this.boids);
}
}
Flock.prototype.addBoid = function(b){
this.boids.push(b);
}
Add above lines before you call flock.addBoid i.e. move setup and draw call to the end of Javascript
//////////////////// SETUP ///////////////////////
var flock;
var ctx;
var c;
//////////////////// FLOCK ///////////////////////
function Flock(){
this.boids = [];
}
Flock.prototype.run = function(){
for(var i = 0; i < this.boids.length; i++){
this.boids[i].run(this.boids);
}
}
Flock.prototype.addBoid = function(b){
this.boids.push(b);
}
function setup(){
c = document.getElementById("boidCanvas");
ctx = c.getContext("2d");
flock = new Flock();
for(var i = 0; i < 100; i++){
var b = new Boid(ctx.width / 2, ctx.height / 2);
flock.addBoid(b);
}
}
function draw() {
ctx.fillStyle = "Blue";
ctx.fillRect(0,0,c.width,c.height);
//flock.run()
}
//////////////////// BOID ///////////////////////
function Boid(setx,sety){
this.acceleration = { x:0, y:0 };
this.velocity = { x:Math.floor(Math.random()*3)-1, y:Math.floor(Math.random()*3)-1 };
this.position = { x:setx, y:sety };
this.r = 3;
this.maxSpeed = 3;
this.maxForce = .05;
}
Boid.prototype.run = function(boids){
this.evaluate(boids);
this.update();
this.wrap();
this.render();
}
// force is a vector [x,y]
Boid.prototype.applyForce = function(force){
this.acceleration.x += force[0];
this.acceleration.y += force[1];
}
Boid.prototype.evaluate = function(boids){
var seperate = this.seperate(boids);
var align = this.align(boids);
var cohesion = this.cohesion(boids);
// Arbitrary Weights
seperate *= 1.5;
align *= 1.0;
cohesion *= 1.0;
this.applyForce(seperate);
this.applyForce(align);
this.applyForce(cohesion);
}
Boid.prototype.update = function(){
//update velocity
this.velocity += this.acceleration;
//fix velocity to max speed
var normal = normalize([this.velocity.x, this.velocity.y]);
this.velocity = constantMult(normal, this.maxSpeed);
//update position
this.position.x += this.velocity.x;
this.position.y += this.velocity.y;
//reset acceleration;
this.acceleration.x = 0;
this.acceleration.y = 0;
}
// target is a vector [x,y]
Boid.prototype.seek = function(target){
var desired = sub(target, [this.position.x, this.position.y]);
var normal = normalize(desired);
desired = constantMult(normal, this.maxSpeed);
var steer = sub(desired,[this.velocity.x, this.velocity.y])
normal = normalize(steer);
steer[0] = normal[0] * this.maxForce;
steer[1] = normal[1] * this.maxForce;
return steer;
}
Boid.prototype.render = function(){
var triangle = drawTriangle(this.velocity);
for(var i = 0; i < triangle.length; i++){
triangle[i] = constantMult(triangle[i], this.r);
}
for(i = 0; i < triangle.length; i++){
triangle[i] = add(triangle[i], this.position);
}
ctx.beginPath();
ctx.moveTo(triangle[0][0], triangle[0][1]);
for(i = 1; i < triangle.length; i++){
ctx.lineTo(triangle[i][0], triangle[i][1]);
}
ctx.closePath();
ctx.fillStyle = "White";
ctx.fill();
}
Boid.prototype.wrap = function(){
if(this.position.x < -this.r)
this.position.x = c.width + this.r;
else if(this.position.x > c.width + this.r)
this.position.x = -this.r;
if(this.position.y < -this.r)
this.position.y = c.height + this.r;
else if(this.position.y > c.height + this.r)
this.position.y = -this.r;
}
Boid.prototype.seperate = function(boids){
var desiredSeperation = 25.0;
var steer = [0,0];
var count = 0;
for(var i = 0; i < boids.length; i++){
var difference = sub(this.position, boids[i].position);
var d = Math.sqrt(Math.pow(difference[0],2), Math.pow(difference[1],2));
if(d < desiredSeperation){
var normalDiff = normalize(difference);
normalDiff = constantMult(normalDiff, 1/d);
steer = add(steer, normalDiff);
count++;
}
}
if(count > 0){
steer = constantMult(steer, 1/count);
steer = normalize(steer);
steer = constantMult(steer, this.maxSpeed);
steer = sub(steer, this.velocity);
steer = normalize(steer);
steer = constantMult(steer, this.maxForce);
}
return steer;
}
Boid.prototype.align = function(boids){
var neighborDistance = 50;
var sum = [0,0];
var count = 0;
for(var i = 0; i < boids.length; i++){
var difference = sub(this.position, boids[i].position);
var dist = Math.sqrt(Math.pow(difference[0],2), Math.pow(difference[1],2));
if(dist < neighborDistance){
sum = sum(sum, boids[i].velocity);
count++;
}
}
if(count > 0){
sum = constantMult(sum, 1/count);
sum = normalize(sum);
sum = constantMult(this.maxSpeed);
var steer = sub(sum, this.velocity);
steer = normalize(steer);
steer = constantMult(steer, this.maxForce);
return steer;
}
else
return [0,0];
}
Boid.prototype.cohesion = function(boids){
var neighborDistance = 50;
var sum = [0,0];
var count = 0;
for(var i = 0; i < boids.length; i++){
var difference = sub(this.position, boids[i].position);
var dist = Math.sqrt(Math.pow(difference[0],2), Math.pow(difference[1],2));
if(dist < neighborDistance){
sum = add(sum, boids[i].position);
count++;
}
}
if(count > 0){
sum = constantMult(sum, 1/count);
return this.seek(sum);
}
else
return [0,0];
}
//////////////////// HELPERS ///////////////////////
// returns the vector with the same direction as v but with magnitude 1 in the form [x,y]
// v is a vector in the form [x,y]
function normalize(v){
var magnitude = Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2))
var normalX = v[0] / magnitude;
var normalY = v[1] / magnitude;
return [normalX, normalY];
}
function add(a,b){
var x = a[0]+b[0];
var y = a[1]+b[1];
return [x,y];
}
// returns a-b, [ax-bx, ay-by]
function sub(a,b){
var x = a[0]-b[0];
var y = a[1]-b[1];
return [x,y];
}
function mult(a,b){
var x = a[0]*b[0];
var y = a[1]*b[1];
return [x,y];
}
function constantMult(a, n){
for(var i = 0; i < a.length; i++){
a[i] *= n;
}
}
// creates an unscaled issoceles triangle centered at the origin
// returns a list of 3 lists, each containing the coordinates of a vertex, the first being the tip
// ie. [ [x1,y1], [x2,y2], [x3,y3] ]
// heading is a vector describing the direction of the triangle in the form [x,y]
// heading does not need to be normalized
function drawTriangle(heading){
heading = normalize(heading);
var v1 = [1,0];
var v2 = [-1, .5];
var v3 = [-1,-.5];
var thetaX = Math.acos(heading[0]);
var thetaY = Math.asin(heading[1]);
var theta;
if(thetaX >= 0)
theta = (Math.PI / 2) - thetaY;
else
theta = (Math.PI / 2) - thetaX;
function rotate(v){
var xp = (v[0] * Math.cos(theta)) - (v[1] * Math.sin(theta));
var yp = (v[1] * Math.cos(theta)) + (v[0] * Math.sin(theta));
return [xp, yp];
}
v1 = rotate(v1);
v2 = rotate(v2);
v3 = rotate(v3);
return [v1,v2,v3];
}
setup();
draw();
I am trying to create an Mandelbrot set in HTML5 canvas. But it is giving me noise surrounding the set. See the fiddle: https://jsfiddle.net/k1f9phw7/
I tried playing with every setting and tried to map brightness to the iteration count but with no success.
let pixelSize = 1;
let width = 500;
let height = 500;
let maxIterations = 400;
let mandelMin = -2.5;
let mandelMax = 2.5;
let infinity = 15;
function draw() {
let ctx = document.getElementById('canvas').getContext('2d');
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var map = function (num, in_min, in_max, out_min, out_max) {
return (num - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
};
var a = map(x, 0, width, mandelMin , mandelMax);
var b = map(y, 0, width, mandelMin, mandelMax);
var initialA = a;
var initialB = b;
var iterationCount = 0;
while(iterationCount < maxIterations){
//Echt
var aa = (a * a) - (b * b);
//Complex
var bb = (2 * a * b);
//De initiele waarde zijn c
a = aa + initialA;
b = bb + initialB;
var result = Math.abs(a + b);
//Is het oneindig?
if( result >= infinity){
break;
}else{
var brightness = 0;
ctx.fillStyle = 'rgb('+ brightness +', '+ brightness +', '+ brightness +')';
//Teken de pixel
ctx.fillRect( map(a ,mandelMin, mandelMax, 0, width ) * pixelSize, map(b, mandelMin, mandelMax, 0, height) * pixelSize, pixelSize, pixelSize);
}
iterationCount++;
}
}
}
}
draw();
You are testing the function for an escaped value by summing a + b which is incorrect. It is more usual to test for a magnitude of 2.0 after which the function is known to eventually escape. This is simple to test with
a * a + b * b >= 4.0
especially as you need to square a and b anyway.
I'm using nearest neighbor algorithm to zoom the image on canvas. But, when I move the scaling bar higher, the image have white line that create a square array
Original Image
After I move the scale bar
The zoom is work but the problem is only the white lines.
For the source code I will provide in bottom
1.html
<!DOCTYPE HTML>
<html>
<head>
<title>Prototype PC</title>
</head>
<body>
<canvas id='canvas1'></canvas>
<hr>
<button id='read'>READ IMAGE</button>
<hr>
Scale <input type='range' value='1' min='1' max='5' step='0.25' id='scale'>
<br><button id='default2'>Default Scalling</button>
<hr/>
</body>
<style>
body{
background : rgba(255,255,255,1);
}
</style>
<script src='imagine.js'></script>
<script>
var canvas = document.getElementById('canvas1')
var obj = new pc(canvas)
obj.image2canvas("565043_553561101348179_1714194038_a.jpg")
var tes = new Array()
document.getElementById('read').addEventListener('click',function(){
tes = obj.image2read()
})
document.getElementById('scale').addEventListener('change',function(){
var scaleval = this.value
var xpos = 0
var ypos = 0
var xnow = 0
var ynow = 0
var objW = obj.width
var objH = obj.height
tesbackup = new Array()
for(var c=0; c<tes.length; c++){
temp = new Array()
for(var d=0; d<4; d++){
temp.push(255)
}
tesbackup.push(temp)
}
//end of copy
for(var i=0; i<tes.length; i++){
xpos = obj.i2x(i)
ypos = obj.i2y(i)
xnow = Math.round(xpos) * scaleval)
ynow = Math.round(ypos) * scaleval)
if (xnow < objW && ynow < objH) {
for (var j=0; j<scaleval; j++) {
for (var k=0; k<scaleval; k++) {
var idxnow = obj.xy2i(xnow,ynow)
tesbackup[idxnow][0] = tes[i][0]
tesbackup[idxnow][1] = tes[i][1]
tesbackup[idxnow][2] = tes[i][2]
}
}
}
}
obj.array2canvas(tesbackup)
})
</script>
and, for imagine.js
function info(text){
console.info(text)
}
function pc(canvas){
this.canvas = canvas
this.context = this.canvas.getContext('2d')
this.width = 0
this.height = 0
this.imgsrc = ""
this.image2read = function(){
this.originalLakeImageData = this.context.getImageData(0,0, this.width, this.height)
this.resultArr = new Array()
this.tempArr = new Array()
this.tempCount = 0
for(var i=0; i<this.originalLakeImageData.data.length; i++){
this.tempCount++
this.tempArr.push(this.originalLakeImageData.data[i])
if(this.tempCount == 4){
this.resultArr.push(this.tempArr)
this.tempArr = []
this.tempCount = 0
}
}
info('image2read Success ('+this.imgsrc+') : '+this.width+'x'+this.height)
return this.resultArr
}
this.image2canvas = function(imgsrc){
var imageObj = new Image()
var parent = this
imageObj.onload = function() {
parent.canvas.width = imageObj.width
parent.canvas.height = imageObj.height
parent.context.drawImage(imageObj, 0, 0)
parent.width = imageObj.width
parent.height = imageObj.height
info('image2canvas Success ('+imgsrc+')')
}
imageObj.src = imgsrc
this.imgsrc = imgsrc
}
this.array2canvas = function(arr){
this.imageData = this.context.getImageData(0,0, this.width, this.height)
if(this.imageData.data.length != arr.length*4) {
return false
}
for(var i = 0; i < arr.length; i++){
this.imageData.data[(i*4)] = arr[i][0]
this.imageData.data[(i*4)+1] = arr[i][1]
this.imageData.data[(i*4)+2] = arr[i][2]
this.imageData.data[(i*4)+3] = arr[i][3]
}
this.context.clearRect(0, 0, this.width, this.height)
this.context.putImageData(this.imageData, 0, 0)
info('Array2Canvas Success ('+this.imgsrc+')')
}
this.i2x = function(i){
return (i % this.width)
}
this.i2y = function(i){
return ((i - (i % this.width))/ this.width)
}
this.xy2i = function(x,y){
return (y * this.width) + (x)
}
}
Thanks in advance for a solution of this problem
Rounding out pixels
Nearest pixel will result in some zoomed pixels being larger than otheres
It is a problem with the value of scaleval. It has a step of 0.25 and when you calculate each zoomed pixels address you use (and I am guessing as your code has syntax errors) Math.round(xpos * scaleval) but then you draw the pixel using only the fractional size eg 2.75 not the integer size eg 3.0
The size of each pixel is var xSize = Math.round((xpos + 1) * scaleval)-Math.round(xpos * scaleval) same for y. That way when the pixel zoom is not an integer value every so many zoomed pixels will be one pixel wider and higher.
The following is a fix of your code but as you had a number of syntax errors and bugs I have had to guess some of your intentions.
xpos = obj.i2x(i)
ypos = obj.i2y(i)
xnow = Math.round(xpos * scaleval)
ynow = Math.round(ypos * scaleval)
// pixel width and height
var pw = Math.round((xpos + 1) * scaleval) - xnow;
var ph = Math.round((ypos + 1) * scaleval) - ynow;
if (xnow < objW && ynow < objH) {
for (var y = 0; y < ph; y++) {
for (var x =0; x < pw; x++) {
var idxnow = obj.xy2i(xnow + x, ynow + y)
tesbackup[idxnow][0] = tes[i][0]
tesbackup[idxnow][1] = tes[i][1]
tesbackup[idxnow][2] = tes[i][2]
}
}
}
}
But you are not really doing a nearest neighbor algorithm. For that you iterate each of the destination pixels finding the nearest pixel and using its colour. That allows you to easily apply a transform to the zoom but still get every pixel and not skip pixels due to rounding errors.
Nearest neighbor
Example of using nearest neighbor lookup for a scale rotated and translated image
var scaleFac = 2.3; // scale 1> zoom in
var panX = 10; // scaled image pan
var panY = 10;
var ang = 1;
var w = ctx.canvas.width; // source image
var h = ctx.canvas.height;
var wd = ctx1.canvas.width; // destination image
var hd = ctx1.canvas.height;
// use 32bit ints as we are not interested in the channels
var src = ctx.getImageData(0, 0, w, h);
var data = new Uint32Array(src.data.buffer);// source
var dest = ctx1.createImageData(wd, hd);
var zoomData = new Uint32Array(dest.data.buffer);// destination
var xdx = Math.cos(ang) * scaleFac; // xAxis vector x
var xdy = Math.sin(ang) * scaleFac; // xAxis vector y
var ind = 0;
var xx,yy;
for(var y = 0; y < hd; y ++){
for(var x = 0; x < wd; x ++){
// transform point
xx = (x * xdx - y * xdy + panX);
yy = (x * xdy + y * xdx + panY);
// is the lookup pixel in bounds
if(xx >= 0 && xx < w && yy >= 0 && yy < h){
// use the nearest pixel to set the new pixel
zoomData[ind++] = data[(xx | 0) + (yy | 0) * w]; // set the pixel
}else{
zoomData[ind++] = 0; // pixels outside bound are transparent
}
}
}
ctx1.putImageData(dest, 0, 0); // put the pixels onto the destination canvas
Hi I want to make a blur effect particle like this:
Can I use shadowBlur and shadowOffsetX/shadowOffsetY to do this? The actual shine will glow and fade a little bit repeatedly, so if I have to write some kind of animation how can I achieve this?
I have tried this code (jsfiddle example) but it doesn't look like the effect. So I wonder how to blur and glow the particle at the same time?
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ra = window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.oRequestAnimationFrame
|| window.msRequestAnimationFrame
|| function(callback) {
window.setTimeout(callback, 1000 / 60);
};
class Particle {
constructor(options) {
this.ctx = options.context;
this.x = options.x;
this.y = options.y;
this.radius = options.radius;
this.lightSize = this.radius;
this.color = options.color;
this.lightDirection = true;
}
glow() {
const lightSpeed = 0.5;
this.lightSize += this.lightDirection ? lightSpeed : -lightSpeed;
if (this.lightSize > this.radius || this.lightSize < this.radius) {
this.lightDirection = !this.lightDirection;
}
}
render() {
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
this.glow();
this.ctx.globalAlpha = 0.5;
this.ctx.fillStyle = this.color;
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.lightSize,
0, Math.PI * 2
);
this.ctx.fill();
this.ctx.globalAlpha = 0.62;
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius * 0.7, 0, Math.PI * 2);
this.ctx.shadowColor = this.color;
this.ctx.shadowBlur = 6;
this.ctx.shadowOffsetX = 0;
this.ctx.shadowOffsetY = 0;
this.ctx.fill();
}
}
var particle = new Particle({
context: ctx,
x: 60,
y: 80,
radius: 12,
color: '#4d88ff'
});
function run() {
particle.render();
ra(run);
}
run();
<canvas id='canvas'></canvas>
There are several ways to do this. For a particle system my option is to pre render the blur using a blur filter. A common filter is the convolution filter. It uses a small array to determine the amount neighboring pixels contribute to each pixel of the image. You are best to look up convolution functions to understand it.
Wiki Convolution and Wiki Gaussian blur for more info.
I am not much of a fan of the standard Gaussian blur or the convolution filter used so in the demo snippet below you can find my version that I think creates a much better blur. The convolution blur filter is procedurally created and is in the imageTools object.
To use create a filter pass an object with properties size the blur amount in pixels and power is the strength. Lower powers is less spread on the blur.
// image must be loaded or created
var blurFilter = imageTools.createBlurConvolutionArray({size:17,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
// apply the convolution filter on the image. The returned image may be a new
//image if the input image does not have a ctx property pointing to a 2d canvas context
image = imageTools.applyConvolutionFilter(image,blurFilter);
In the demo I create a image, draw a circle on it, copy it and pad it so that there is room for the blur. Then create a blur filter and apply it to the image.
When I render the particles I first draw all the unblurred images, then draw the blurred copies with the ctx.globalCompositeOperation = "screen"; so that they have a shine. To vary the amount of shine I use the ctx.globalAlpha to vary the intensity of the rendered blurred image. To improve the FX I have drawn the blur image twice, once with oscillating scale and next at fixed scale and alpha.
The demo is simple, image tools can be found at the top. Then there is some stuff to setup the canvas and handle resize event. Then there is the code that creates the images, and apply the filters. Then starts the render adds some particles and renders everything.
Look in the function drawParticles for how I draw everything.
imageTools has all the image functions you will need. The imageTools.applyConvolutionFilter will apply any filter (sharpen, outline, and many more) you just need to create the appropriate filter. The apply uses the photon count colour model so gives a very high quality result especially for blurs type effects. (though for sharpen you may want to get in and change the squaring of the RGB values, I personally like it other do not)
The blur filter is not fast so if you apply it to larger images It would be best that you break it up in so you do not block the page execution.
A cheap way to get a blur is to copy the image to blur to a smaller version of itself, eg 1/4 then render it scaled back to normal size, the canvas will apply bilinear filtering on the image give a blur effect. Not the best quality but for most situations it is indistinguishable from the more sophisticated blur that I have presented.
UPDATE
Change the code so that the particles have a bit of a 3dFX to show that the blur can work up to larger scales. The blue particles are 32 by 32 image and the blur is 9 pixels with the blur image being 50by 50 pixels.
var imageTools = (function () {
var tools = {
canvas : function (width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage : function (width, height) {
var image = this.canvas(width, height);
image.ctx = image.getContext("2d");
return image;
},
image2Canvas : function (img) {
var image = this.canvas(img.width, img.height);
image.ctx = image.getContext("2d");
image.drawImage(img, 0, 0);
return image;
},
padImage : function(img,amount){
var image = this.canvas(img.width + amount * 2, img.height + amount * 2);
image.ctx = image.getContext("2d");
image.ctx.drawImage(img, amount, amount);
return image;
},
getImageData : function (image) {
return (image.ctx || (this.image2Canvas(image).ctx)).getImageData(0, 0, image.width, image.height);
},
putImageData : function (image, imgData){
(image.ctx || (this.image2Canvas(image).ctx)).putImageData(imgData,0, 0);
return image;
},
createBlurConvolutionArray : function(options){
var i, j, d; // misc vars
var filterArray = []; // the array to create
var size = options.size === undefined ? 3: options.size; // array size
var center = Math.floor(size / 2); // center of array
// the power ? needs descriptive UI options
var power = options.power === undefined ? 1: options.power;
// dist to corner
var maxDist = Math.sqrt(center * center + center * center);
var dist = 0; // distance sum
var sum = 0; // weight sum
var centerWeight; // center calculated weight
var totalDistance; // calculated total distance from center
// first pass get the total distance
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
dist += d;
}
}
totalDistance = dist; // total distance to all points;
// second pass get the total weight of all but center
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
d = d/totalDistance;
sum += d;
}
}
var scale = 1/sum;
sum = 0; // used to check
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
d = d/totalDistance;
filterArray.push(d*scale);
}
}
return filterArray;
},
applyConvolutionFilter : function(image,filter){
imageData = this.getImageData(image);
imageDataResult = this.getImageData(image);
var w = imageData.width;
var h = imageData.height;
var data = imageData.data;
var data1 = imageDataResult.data;
var side = Math.round(Math.sqrt(filter.length));
var halfSide = Math.floor(side/2);
var r,g,b,a,c;
for(var y = 0; y < h; y++){
for(var x = 0; x < w; x++){
var ind = y*4*w+x*4;
r = 0;
g = 0;
b = 0;
a = 0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = y + cy - halfSide;
var scx = x + cx - halfSide;
if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
var srcOff = (scy*w+scx)*4;
var wt = filter[cy*side+cx];
r += data[srcOff+0] * data[srcOff+0] * wt;
g += data[srcOff+1] * data[srcOff+1] * wt;
b += data[srcOff+2] * data[srcOff+2] * wt;
a += data[srcOff+3] * data[srcOff+3] * wt;
}
}
}
data1[ind+0] = Math.sqrt(Math.max(0,r));
data1[ind+1] = Math.sqrt(Math.max(0,g));
data1[ind+2] = Math.sqrt(Math.max(0,b));
data1[ind+3] = Math.sqrt(Math.max(0,a));
}
}
return this.putImageData(image,imageDataResult);
}
};
return tools;
})();
/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars
var canvas, ctx;
var globalTime = 0;
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
c.id = CANVAS_ELEMENT_ID;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){
cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2;
if(particles && particles.length > 0){
particles.length = 0;
}
}
resizeCanvas(); // create and size canvas
window.addEventListener("resize",resizeCanvas); // add resize event
const IMAGE_SIZE = 32;
const IMAGE_SIZE_HALF = 16;
const GRAV = 2001;
const NUM_PARTICLES = 90;
var background = imageTools.createImage(8,8);
var grad = ctx.createLinearGradient(0,0,0,8);
grad.addColorStop(0,"#000");
grad.addColorStop(1,"#048");
background.ctx.fillStyle = grad;
background.ctx.fillRect(0,0,8,8);
var circle = imageTools.createImage(IMAGE_SIZE,IMAGE_SIZE);
circle.ctx.fillStyle = "#5BF";
circle.ctx.arc(IMAGE_SIZE_HALF, IMAGE_SIZE_HALF, IMAGE_SIZE_HALF -2,0, Math.PI * 2);
circle.ctx.fill();
var blurFilter = imageTools.createBlurConvolutionArray({size:9,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
var blurCircle = imageTools.padImage(circle,9);
blurCircle = imageTools.applyConvolutionFilter(blurCircle,blurFilter)
var sun = imageTools.createImage(64,64);
grad = ctx.createRadialGradient(32,32,0,32,32,32);
grad.addColorStop(0,"#FF0");
grad.addColorStop(1,"#A40");
sun.ctx.fillStyle = grad;
sun.ctx.arc(32,32,32 -2,0, Math.PI * 2);
sun.ctx.fill();
var sunBlur = imageTools.padImage(sun,17);
blurFilter = imageTools.createBlurConvolutionArray({size:17,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
sunBlur = imageTools.applyConvolutionFilter(sunBlur,blurFilter);
var particles = [];
var createParticle = function(x,y,dx,dy){
var dir = Math.atan2(y-ch,x-cw);
var dist = Math.sqrt(Math.pow(y-ch,2)+Math.pow(x-cw,2));
var v = Math.sqrt(GRAV / dist); // get apporox orbital speed
return {
x : x,
y : y,
dx : dx + Math.cos(dir + Math.PI/2) * v, // set orbit speed at tangent
dy : dy + Math.sin(dir + Math.PI/2) * v,
s : (Math.random() + Math.random() + Math.random())/4 + 0.5, // scale
v : (Math.random() + Math.random() + Math.random()) / 3 + 2, // glow vary rate
};
}
var depthSort = function(a,b){
return b.y - a.y;
}
var updateParticles = function(){
var i,p,f,dist,dir;
for(i = 0; i < particles.length; i ++){
p = particles[i];
dist = Math.sqrt(Math.pow(cw-p.x,2)+Math.pow(ch-p.y,2));
dir = Math.atan2(ch-p.y,cw-p.x);
f = GRAV * 1 / (dist * dist);
p.dx += Math.cos(dir) * f;
p.dy += Math.sin(dir) * f;
p.x += p.dx;
p.y += p.dy;
p.rx = ((p.x - cw ) / (p.y + h)) * h + cw;
p.ry = ((p.y - ch ) / (p.y + h)) * h * -0.051+ ch;
//p.ry = ((h-p.y) - ch) * 0.1 + ch;
p.rs = (p.s / (p.y + h)) * h
}
particles.sort(depthSort)
}
var drawParticles = function(){
var i,j,p,f,dist,dir;
// draw behind the sun
for(i = 0; i < particles.length; i ++){
p = particles[i];
if(p.y - ch < 0){
break;
}
ctx.setTransform(p.rs,0,0,p.rs,p.rx,p.ry);
ctx.drawImage(circle,-IMAGE_SIZE_HALF,-IMAGE_SIZE_HALF);
}
// draw glow for behind the sun
ctx.globalCompositeOperation = "screen";
var iw = -blurCircle.width/2;
for(j = 0; j < i; j ++){
p = particles[j];
ctx.globalAlpha = ((Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.4;
var scale = (1-(Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.6;
ctx.setTransform(p.rs * 1.5 * scale,0,0,p.rs * 1.5* scale,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
// second pass to intensify the glow
ctx.globalAlpha = 0.7;
ctx.setTransform(p.rs * 1.1,0,0,p.rs * 1.1,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
}
// draw the sun
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
ctx.setTransform(1,0,0,1,cw,ch);
ctx.drawImage(sun,-sun.width/2,-sun.height/2);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "screen";
ctx.setTransform(1,0,0,1,cw,ch);
ctx.drawImage(sunBlur,-sunBlur.width/2,-sunBlur.height/2);
var scale = Math.sin(globalTime / 100) *0.5 + 1;
ctx.globalAlpha = (Math.cos(globalTime / 100) + 1) * 0.2 + 0.4;;
ctx.setTransform(1 + scale,0,0,1 + scale,cw,ch);
ctx.drawImage(sunBlur,-sunBlur.width/2,-sunBlur.height/2);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
// draw in front the sun
for(j = i; j < particles.length; j ++){
p = particles[j];
if(p.y > -h){ // don't draw past the near view plane
ctx.setTransform(p.rs,0,0,p.rs,p.rx,p.ry);
ctx.drawImage(circle,-IMAGE_SIZE_HALF,-IMAGE_SIZE_HALF);
}
}
ctx.globalCompositeOperation = "screen";
var iw = -blurCircle.width/2;
for(j = i; j < particles.length; j ++){
p = particles[j];
if(p.y > -h){ // don't draw past the near view plane
ctx.globalAlpha = ((Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.4;
var scale = (1-(Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.6;
ctx.setTransform(p.rs * 1.5 * scale,0,0,p.rs * 1.5* scale,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
// second pass to intensify the glow
ctx.globalAlpha = 0.7;
ctx.setTransform(p.rs * 1.1,0,0,p.rs * 1.1,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
}
}
ctx.globalCompositeOperation = "source-over";
}
var addParticles = function(count){
var ww = (h-10)* 2;
var cx = cw - ww/2;
var cy = ch - ww/2;
for(var i = 0; i < count; i ++){
particles.push(createParticle(cx + Math.random() * ww,cy + Math.random() * ww, Math.random() - 0.5, Math.random() - 0.5));
}
}
function display(){ // put code in here
if(particles.length === 0){
addParticles(NUM_PARTICLES);
}
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.drawImage(background,0,0,w,h)
updateParticles();
drawParticles();
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
}
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/
I've tried everything and read every single link I can see on the internet regarding Perlin Noise or Simplex Noise and even dissected a few Javascript examples that I see work fine.
But I still get very random looking images... essentially just TV static.
My code is below. I'm using a random number generator so that I can seed a value, but I've tried with Math.random as well.
As near as I can tell, the different images generated at the different octaves aren't interpolating properly, or maybe the way I'm converting from the Noise function to RGB values is wrong (I've tried to fix both of these issues...).
if (!this.Prng) {
var Prng = function() {
var iMersenne = 2147483647;
var rnd = function(seed) {
if (arguments.length) {
that.seed = arguments[0];
}
that.seed = that.seed*16807%iMersenne;
return that.seed;
};
var that = {
seed: 123,
rnd: rnd,
random: function(seed) {
if (arguments.length) {
that.seed = arguments[0];
}
return rnd()/iMersenne;
}
};
return that;
}();
}
var CSimplexNoise = function(r)
{
this.grad3 = [[1,1,0],[-1,1,0],[1,-1,0],[-1,-1,0],[1,0,1],[-1,0,1],
[1,0,-1],[-1,0,-1],[0,1,1],[0,-1,1],[0,1,-1],[0,-1,-1]];
var p = [];
for(i = 0; i < 256; i++)
p[i] = Math.floor(r.random()*256);
this.perm = new Array();
for(i = 0; i < 512; i++)
{
this.perm[i] = p[i & 255];
}
}
CSimplexNoise.prototype.dot = function(g,x,y)
{
return g[0]*x + g[1]*y;
}
CSimplexNoise.prototype.GenerateSimplexNoise = function(x,y,octaves,persistence)
{
var total = 0;
for(i=0; i < octaves-1; i++)
{
var freq = Math.pow(2,i);
var amp = Math.pow(persistence,i);
total += this.InterpolatedNoise(x*freq,y*freq) * amp;
}
return total;
}
CSimplexNoise.prototype.InterpolatedNoise = function(x,y)
{
var xInt = Math.floor(x);
var xFrac = x - xInt;
var yInt = Math.floor(y);
var yFrac = y - yInt;
var v1 = this.SmoothNoise(xInt,yInt);
var v2 = this.SmoothNoise(xInt + 1,yInt)
var v3 = this.SmoothNoise(xInt,yInt+1)
var v4 = this.SmoothNoise(xInt + 1, yInt + 1);
var i1 = this.LinearInterpolate(v1,v2,xFrac);
var i2 = this.LinearInterpolate(v3,v4,xFrac);
return this.CosineInterpolate(i1,i2,yFrac);
}
CSimplexNoise.prototype.LinearInterpolate = function(a,b,x)
{
return a*(1-x) + b*x;
}
CSimplexNoise.prototype.CosineInterpolate = function(a,b,x)
{
var f = (1 - Math.cos(x*Math.PI)) * 0.5;
return a*(1-f) + b*f;
}
CSimplexNoise.prototype.SmoothNoise = function(x,y)
{
var corners = (this.Noise(x-1,y-1) + this.Noise(x+1,y-1) + this.Noise(x-1,y+1) + this.Noise(x+1,y+1)) / 16;
var sides = (this.Noise(x-1,y) + this.Noise(x+1,y) + this.Noise(x,y-1) + this.Noise(x+1,y+1)) / 8;
var center = this.Noise(x,y) / 4;
return corners + sides + center;
}
CSimplexNoise.prototype.Noise = function(xin, yin)
{
var n0, n1, n2;
var F2 = 0.5*(Math.sqrt(3)-1);
var s = (xin+yin)*F2;
var i = Math.floor(xin+s);
var j = Math.floor(yin+s);
var G2 = (3-Math.sqrt(3))/6;
var t = (i+j)*G2;
var X0 = i-t;
var Y0 = j-t;
var x0 = xin-X0;
var y0 = yin-Y0;
var i1,j1;
if(x0 > y0)
{
i1 = 1;
j1 = 0;
}
else
{
i1 = 0;
j1 = 1;
}
var x1 = x0 - i1 + G2;
var y1 = y0 - j1 + G2;
var x2 = x0 - 1 + 2 * G2;
var y2 = y0 - 1 + 2 * G2;
var ii = i & 255;
var jj = j & 255;
var gi0 = this.perm[ii + this.perm[jj]] % 12;
var gi1 = this.perm[ii + i1 + this.perm[jj + j1]] % 12;
var gi2 = this.perm[ii + 1 + this.perm[jj + 1]] % 12;
var t0 = 0.5 - x0 * x0 - y0 * y0;
if(t0 < 0)
n0 = 0;
else
{
t0 *= t0;
n0 = t0 * t0 * this.dot(this.grad3[gi0],x0,y0)
}
var t1 = 0.5 - x1 * x1 - y1 * y1;
if(t1 < 0)
n1 = 0;
else
{
t1 *= t1;
n1 = t1 * t1 * this.dot(this.grad3[gi1],x1,y1);
}
var t2 = 0.5 - x2 * x2 - y2 * y2;
if(t2 <0 )
n2 = 0;
else
{
t2 *= t2;
n2 = t2 * t2 * this.dot(this.grad3[gi2],x2,y2);
}
return 70 * (n0 + n1 + n2);
}
$(document).ready(function(){
var context = $('#screen')[0].getContext("2d");
var w = 100;
var h = 100;
var data = context.createImageData(w,h);
var simplexNoise = new CSimplexNoise(Prng);
for(y = 0; y < h; y++)
{
for(x = 0; x < w; x++)
{
// var newVal = ((simplexNoise.GenerateSimplexNoise(x,y,5,0.25) - -1) / (1 - -1)) * (255 - 0);
var newVal2 = simplexNoise.GenerateSimplexNoise(x,y,5,0.5)
var newVal = Math.floor(newVal2*256);
newVal = Math.abs(newVal * 2)-0.5;
data.data[((h * y) + x) * 4] = newVal;
data.data[((h * y) + x) * 4+1] = newVal;
data.data[((h * y) + x) * 4+2] = newVal;
data.data[((h * y) + x) * 4+3] = 255;
}
}
context.putImageData(data,0,0);
})
Try sampling simplexNoise.GenerateSimplexNoise(x * 0.05, y * 0.05, 5, 0.5)
The problem may be that your samples are too far apart. (this would result in apparently random behavior, since the simplex noise might go through more than half a wavelength before you sample it)
REVISION: Updated numbers above...
You may actually need to reduce the samples so that there are 20 in a given wavelength of the simplex noise. The average wavelength of most simplex noise is 1, so 0.05 should do the trick. Also, you may want to test with just one octave at first.