Related
Edited : Thanks to all for valuable time and effort. Finally I made this )) JSfiddle
I was just playing with canvas and made this. Fiddle link here.
... some code here ...
var cords = [];
for(var i = 50; i <= width; i += 100) {
for(var j = 50; j <= height; j += 100) {
cords.push({ cor: i+','+j});
}
}
console.log(cords);
var offset = 15,
speed = 0.01,
angle = 0.01;
cords.forEach(function(e1) {
e1.base = parseInt(Math.random()*25);
e1.rgb = 'rgb('+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+')';
});
setInterval(function() {
cords.forEach(function(e1) {
e1.base = parseInt(Math.random()*25);
e1.rgb = 'rgb('+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+')';
});
},5000);
function render() {
ctx.clearRect(0,0,width,height);
cords.forEach(function(e1) {
//console.log(e1);
ctx.fillStyle = e1.rgb;
ctx.beginPath();
var r = e1.base + Math.abs(Math.sin(angle)) * offset;
var v = e1.cor.split(',');
ctx.arc(v[0],v[1],r,0,Math.PI * 2, false);
ctx.fill();
});
angle += speed;
requestAnimationFrame(render);
}
render();
Was wondering if -
Coordinates can be made random, now they are fixed as you can see. After 5000 mil, balls will show up in various random cords but even at their fullest they won't touch each other.
Every ball has same speed for changing size, I want that to be different too. Meaning, After 5000 mil, they show up with different animation speeds as well.
Also any suggestion on improving code and making it better/quicker/lighter is much appreciated. Thank you !
TL;DR - See it running here.
Making the coordinates random:
This requires you to add some random displacement to the x and y coordinates. So I added a random value to the coordinates. But then a displacement of less than 1 is not noticeable. So you'd need to magnify that random number by a multiplier. That's where the randomizationFactor comes in. I have set it to 100 since that is the value by which you shift the coordinates in each iteration. So that gives a truly random look to the animation.
Making Speed Random:
This one took me a while to figure out, but the ideal way is to push a value of speed into the array of coordinates. This let's you ensure that for the duration of animation, the speed will remain constant and that gives you a smoother feel. But again multiplying the radius r with a value between 0 and 1 reduces the speed significantly for some of the circles. So I have added a multiplier to 3 to compensate slightly for that.
Ideally I'd put a 2, as the average value of Math.random() is 0.5, so a multiplier of 2 would be adequate to compensate for that. But a little experimentation showed that the multiplier of 3 was much better. You can choose the value as per your preference.
Your logic of generating the coordinates changes as follows:
for(var i = 50; i <= width;i += 100) {
for(var j = 51; j <= height;j += 100) {
var x = i + (Math.random() - 0.5)*randomizationFactor;
var y = j + (Math.random() - 0.5)*randomizationFactor;
cords.push({ cor: x+','+y, speed: Math.random()});
}
}
Your logic of enlarging the circles changes as follows:
function render() {
ctx.clearRect(0,0,width,height);
cords.forEach(function(e1) {
//console.log(e1);
ctx.fillStyle = e1.rgb;
ctx.beginPath();
var r = e1.base + Math.abs(Math.sin(angle)) * offset * e1.speed * 3;
var v = e1.cor.split(',');
ctx.arc(v[0],v[1],r,0,Math.PI * 2, false);
ctx.fill();
});
angle += speed ;
requestAnimationFrame(render);
}
Suggestion: Update the coordinates with color
I'd probably also update the location of circles every 5 seconds along with the colors. It's pretty simple to do as well. Here I've just created a function resetCoordinates that runs every 5 seconds along with the setBaseRgb function.
var cords = [];
function resetCoordinates() {
cords = [];
for(var i = 50; i <= width;i += 100) {
for(var j = 51; j <= height;j += 100) {
var x = i + (Math.random() - 0.5)*randomizationFactor;
var y = j + (Math.random() - 0.5)*randomizationFactor;
cords.push({ cor: x+','+y, speed: Math.random()});
}
}
}
UPDATE I did some fixes in your code that can make your animation more dynamic. Totally rewritten sample.
(sorry for variable name changing, imo now better)
Built in Math.random not really random, and becomes obvious when you meet animations. Try to use this random-js lib.
var randEngine = Random.engines.mt19937().autoSeed();
var rand = function(from, to){
return Random.integer(from, to)(randEngine)
}
Internal base properties to each circle would be better(more dynamic).
var circles = [];
// better to save coords as object neither as string
for(var i = 50; i <= width; i += 100)
for(var j = 50; j <= height; j += 100)
circles.push({
coords: {x:i,y:j}
});
We can adjust animation with new bouncing property.
var offset = 15,
speed = 0.005,
angle = 0.01,
bouncing = 25;
This is how setBaseRgb function may look like
function setBaseRgb(el){
el.base = rand(-bouncing, bouncing);
el.speed = rand(5, 10) * speed;
el.angle = 0;
el.rgb = 'rgb('+rand(0, 255)+','+rand(0, 255)+','+rand(0, 255)+')';
}
All your animations had fixed setInterval timeout. Better with random timeout.
cords.forEach(function(el){
// random timeout for each circle
setInterval(setBaseRgb.bind(null,el), rand(3000, 5000));
})
You forgot to add your base to your circle position
function render() {
ctx.clearRect(0,0,width,height);
circles.forEach(function(el) {
ctx.fillStyle = el.rgb;
ctx.beginPath();
var r = bouncing + el.base + Math.abs(Math.sin(el.angle)) * offset;
var coords = el.coords;
ctx.arc(
coords.x + el.base,
coords.y + el.base,
r, 0, Math.PI * 2, false
);
ctx.fill();
el.angle += el.speed;
});
requestAnimationFrame(render);
}
render();
Effect 1 JSFiddle
Adding this
if(el.angle > 1)
el.angle=0;
Results bubling effect
Effect 2 JSFiddle
Playing with formulas results this
Effect 3 JSFiddle
I am working on some logic for point-to-point spaceship travel across a cartesian map using force, acceleration and mass. The ship will accelerate and burn at 1G towards its destination, flip 180 degrees at the half-way mark, and decelerate at 1G to arrive at a relative stop at its destination.
The problem I am having is determining the (x, y) coordinate using the time traveled while the ship is either under acceleration or deceleration.
Here are the specs on the ship:
ship = {
mass: 135000, // kg
force: 1324350, // Newtons
p: { x: -1, y: -5 } // (x,y) coordinates
}
dest: {
p: { x: 15, y: 30 } // (x,y) coordinates
}
For the first part of the problem I calculate the time to destination:
var timeToDestination = function(ship, dest) {
// calculate acceleration (F = ma)
var acceleration = ship.force / ship.mass; // ~9.81 (1G)
// calculate distance between 2 points (Math.sqrt((a - x)^2+(b - y)^2))
var totalDistance = Math.sqrt(
Math.pow(dest.p.x - ship.p.x, 2) +
Math.pow(dest.p.y - ship.p.y, 2)
); // 38.48376280978771
// multiply grid system to galactic scale
var actualDistance = totalDistance * 1e9; // 1 = 1Mkm (38,483,763km) Earth -> Venus
// determine the mid-point where ship should flip and burn to decelerate
var midPoint = actualDistance / 2;
// calculate acceleration + deceleration time by solving t for each: (Math.sqrt(2d/a))
return Math.sqrt( 2 * midPoint / acceleration ) * 2; // 125,266s or 34h or 1d 10h
}
The second part is a little trickier, getting the (x, y) coordinate after delta time. This is where I get stuck, but here is what I have so far:
var plotCurrentTimeDistance = function(ship, dest, time) {
// recalculate acceleration (F = ma)
var acc = ship.force / ship.mass; //~9.81m/s^2
// recalculate total distance
var distance = Math.sqrt(
Math.pow(dest.p.x - ship.p.x, 2) +
Math.pow(dest.p.y - ship.p.y, 2)
) * 1e9; // 38,483,762,810m
// get distance traveled (d = (1/2) at^2)
var traveled = (acc * Math.pow(time, 2)) / 2;
// get ratio of distance traveled to actualDistance
var ratio = traveled / distance;
// midpoint formula to test # 50% time ((x+a)/2,(y+b)/2)
console.log({ x: (ship.p.x+dest.p.x)/2, y: (ship.p.y+dest.p.y)/2})
// get the point using this formula (((1−t)a+tx),((1−t)b+ty))
return {
x: (( 1 - ratio ) * ship.p.x) + (ratio * dest.p.x),
y: (( 1 - ratio ) * ship.p.y) + (ratio * dest.p.y)
};
}
# 50% time, 62,633s point returns as (~7, ~12.5) which matches the midpoint formula which returns as (~7, ~12.5). However, any other distance/time you input will be wildly wrong. My guess is that acceleration is messing up the calculations but I can't figure out how to change the formula to make it work. Thank you for your time.
First, you say that distance is the total distance, but it really is the distance left from the ship to the destination. I don't fully understand your plan on how to do the calculations, so I will suggest something different below.
Lets call the start position start, and it has a x, and y coordinate: start.x and start.y. Similarly with end.
Now, for a simulation like this to work we need velocity as a property on the ship as well, so
ship = {
...
v : {x : 0, y : 0}
}
It will start from rest, and it should reach rest. Ideally it should have acceleration a for a general movement, but we can skip that right now. We will have two different behaviours
The ship starts from rest, accelerates with 9.8 m/s^2 towards the goal until the half point is reached.
The ship starts at speed v at the midpoint, decelerates with -9.8 m/s^2 towards the goal until the speed is 0. Now we should have reached the goal.
To get velocity from accelerations we use v = v_start + a*time, and to get position from velocity we use x = x_start + v*time. The current position of the ship is then for the two cases
// Case 1
current = start + a*time*time
// the above is due to the fact that
// current = start + v*time
// and the fact that v = v_start + a*time as I wrote previously,
// with v_start.x = v_start.y = 0
//Case 2
current = midpoint + (v_at_midpoint - a*time_since_midpoint)*time_since_midpoint
Note here that start, current and a here are vectors, so they will have a x and y (and potentially z) coordinate each.
The acceleration you get by the following algorithm
a = (start - end)/totalDistance * 9.81 // 9.81 is the desired acceleration -- change it to whatever you want
If you want to understand what the above actually means, it calculates what is called a unit vector, which tells us what direction the force points at.
What you will need to do now is as follows:
Calculate the total distance and the acceleration
Determine if you're in case 1 or 2 given the input time in the function.
Once you've reached the midpoint, store the velocity and how long it took to get there and use it to determine the motion in case 2.
Stop once you've reached the destination, or you will go back to the start eventually!
Good luck!
P.S I should also note here that this does not take into account special relativity, so if your distances are too far apart you will get non-physical speeds. It gets a lot messier if you want to take this into account, however.
So thanks to #pingul I was able to get the answer using insights from his suggestions.
var ship = {
mass: 135000,
force: 1324350,
accel: 0,
nav: {
startPos: { x: 0, y: 0 },
endPos: { x: 0, y: 0 },
distanceToDest: 0,
distanceTraveled: 0,
departTime: 0,
timeToDest: 0,
arriveTime: 0,
startDeceleration: 0
}
};
var log = [];
var start = { x: -1, y: -5 };
var end = { x: 15, y: 30 };
var updateLog = function() {
document.getElementById('ship').textContent = JSON.stringify(ship, null, 2);
document.getElementById('pos').textContent = JSON.stringify(log, null, 2);
}
var plotCourse = function(ship, start, end) {
// calculate acceleration (F = ma)
var acceleration = ship.force / ship.mass; // ~9.81 (1G)
// calculate distance between 2 points (Math.sqrt((a - x)^2+(b - y)^2))
var totalDistance = Math.sqrt(
Math.pow(end.x - start.x, 2) +
Math.pow(end.y - start.y, 2)
); // 38.48376280978771
// multiply grid system to galactic scale
var actualDistance = totalDistance * 1e9; // 1 = 1Mkm (38,483,763km) Earth -> Venus
// determine the mid-point where ship should flip and burn to decelerate
var midpoint = actualDistance / 2;
// calculate acceleration + deceleration time by solving t for each: (Math.sqrt(2d/a))
var time = Math.sqrt( 2 * midpoint / acceleration ) * 2; // 125,266s or 34h or 1d 10h
// load data into ship nav
ship.nav = {
startPos: start,
endPos: end,
distanceToDest: actualDistance,
timeToDest: time,
startDeceleration: time / 2
}
ship.accel = acceleration
//log it
updateLog();
};
var goUnderway = function(ship) {
var arrivalEl = document.getElementById('arrivalTime');
// set depart and arrive times
var timeToDest = ship.nav.timeToDest * 1000; // convert to ms
ship.nav['departTime'] = moment().valueOf(); // returns now as unix ms
ship.nav['arriveTime'] = moment(ship.nav.departTime).add(timeToDest).valueOf();
//log it
arrivalEl.textContent = 'Your ship will arrive ' + moment(ship.nav.arriveTime).calendar();
updateLog();
};
var getPosition = function(ship, date) {
var currentTime = date ? moment(date).valueOf() : moment().valueOf() // unix ms
var elapsedTime = (currentTime - ship.nav.departTime) / 1000; // convert to s
var remainingTime = (ship.nav.arriveTime - currentTime) / 1000; // convert to s
var distanceAtMidpoint = 0;
var timeSinceMidpoint = 0;
var pos = { x: 0, y: 0 };
// calculate velocity from elapsed time
if (elapsedTime < ship.nav.startDeceleration) {
// if ship is accelerating use this function
ship.nav.distanceTraveled = 0 + ship.accel * Math.pow(elapsedTime, 2) / 2;
} else if (elapsedTime > ship.nav.startDeceleration) {
// else if ship is decelerating use this function
distanceAtMidpoint = 0 + ship.accel * Math.pow(ship.nav.startDeceleration, 2) / 2; // distance at midpoint
timeSinceMidpoint = elapsedTime - ship.nav.startDeceleration;
ship.nav.distanceTraveled = distanceAtMidpoint + ship.accel * Math.pow(timeSinceMidpoint, 2) / 2;
}
if (remainingTime <= 0) {
ship.force = ship.vel = ship.accel = 0;
pos = ship.nav.endPos;
} else {
// get ratio of distance traveled to actualDistance
var ratio = ship.nav.distanceTraveled / ship.nav.distanceToDest;
// get the point using this formula (((1−t)a+tx),((1−t)b+ty))
pos = {
x: (( 1 - ratio ) * start.x) + (ratio * end.x),
y: (( 1 - ratio ) * start.y) + (ratio * end.y)
};
}
log.push({
timestamp: moment(currentTime),
pos: pos
})
//log it
updateLog();
};
plotCourse(ship, start, end);
goUnderway(ship);
for (var i = 0; i < 35; i++) {
getPosition(ship, moment().add(i, 'hour'));
}
pre {
outline: 1px solid #ccc;
padding: 5px;
margin: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.15.1/moment.min.js"></script>
Ship: <span id="arrivalTime"></span>
<pre id="ship"></pre> Last Known Positions:
<pre id="pos"></pre>
I have been wrestling with rendering an animation that fires a projectile accurately from an "enemy" node to a "player" node in a 2D 11:11 grid (0:0 = top-left) in JS/Canvas. After a lot of reading up I've managed to get the shots close, but not quite bang on. I think my velocity function is a little out but I really don't know why. This is the trigonometric function:
this.getVelocityComponents = function(speed){
// loc (location of enemy actor) = array(2) [X_coord, Y_coord]
// des (destination (ie. player in this instance)) = array(2) [X_coord, Y_coord]
var i, sum, hyp, output = [], dis = [];
var higher = false;
for (i in loc) {
sum = 0;
if (loc[i] > des[i])
sum = loc[i] - des[i];
if (loc[i] < des[i])
sum = des[i] - loc[i];
dis.push(sum);
}
hyp = Math.sqrt(Math.pow(dis[X], 2) + Math.pow(dis[Y], 2));
if (dis[X] > dis[Y]) {
output[X] = (speed * Math.cos(dis[X]/hyp))
output[Y] = (speed * Math.sin(dis[Y]/hyp))
} else if (dis[X] < dis[Y]) {
output[X] = (speed * Math.cos(dis[Y]/hyp))
output[Y] = (speed * Math.sin(dis[X]/hyp))
}
return output;
}
and this is the instruction that tells the X and the Y of the projectile frame to advance:
var distance = [];
for (i in loc) {
var sum = 0;
if (loc[i] > des[i])
sum = loc[i] - des[i];
if (loc[i] < des[i])
sum = des[i] - loc[i];
distance.push(sum);
}
if (distance[X] > distance[Y]) {
frm[X] += (loc[X] < des[X]) ? v[X] : -v[X];
frm[Y] += (loc[Y] < des[Y]) ? v[Y] : -v[Y];
} else {
frm[Y] += (loc[Y] < des[Y]) ? v[X] : -v[X];
frm[X] += (loc[X] < des[X]) ? v[Y] : -v[Y];
}
Below is a screenshot. Blue is player, pink enemy and the yellow circles are projectiles
as you can see, it's almost on the mark.
Have I done something wrong? what do I need to do?
To calculate the direction from enemy to player you can simplify the calculations a little.
Find direction angle
var diffX = Player.x - Enemy.x, // difference in position
diffY = Player.y - Enemy.y,
angle = Math.atan2(diffY, diffX); // atan2 will give the angle in radians
Notice also difference for Y comes first for atan2 as canvas is oriented 0° pointing right.
Velocity vector
Then calculate the velocity vector using angle and speed:
// calculate velocity vector
var speed = 8,
vx = Math.cos(angle) * speed, // angle x speed
vy = Math.sin(angle) * speed;
You might want to consider using time as a factor if that is important. You can see my answer from a while back here for an example on this.
Demo
Using these calculations you will be able to always "hit" the player with the projectile (reload demo to change enemy position to random y):
var ctx = document.querySelector("canvas").getContext("2d"),
Player = {
x: 470,
y: 75
},
Enemy = {
x: 100,
y: Math.random() * 150 // reload demo to change y-position
};
// calculate angle
var diffX = Player.x - Enemy.x,
diffY = Player.y - Enemy.y,
angle = Math.atan2(diffY, diffX);
// calculate velocity vector
var speed = 8,
vx = Math.cos(angle) * speed, // angle x speed
vy = Math.sin(angle) * speed,
x = Enemy.x, // projectil start
y = Enemy.y + 50;
// render
(function loop() {
ctx.clearRect(0, 0, 500, 300);
ctx.fillRect(Player.x, Player.y, 30, 100);
ctx.fillRect(Enemy.x, Enemy.y, 30, 100);
ctx.fillRect(x - 3, y -3, 6, 6);
x += vx;
y += vy;
if (x < 500) requestAnimationFrame(loop);
})();
<canvas width=500 height=300></canvas>
The solution is much simpler than that.
What should you do ?
1) compute the vector that leads from you enemy to the player. That will be the shooting direction.
2) normalize the vector : meaning you build a vector that has a length of 1, with the same direction.
3) multiply that vector by your speed : now you have a correct speed vector, with the right norm, aimed at the player.
Below some code to help you understand :
function spawnBullet(enemy, player) {
var shootVector = [];
shootVector[0] = player[0] - enemy[0];
shootVector[1] = player[1] - enemy[1];
var shootVectorLength = Math.sqrt(Math.pow(shootVector[0], 2) + Math.pow(shootVector[1],2));
shootVector[0]/=shootVectorLength;
shootVector[1]/=shootVectorLength;
shootVector[0]*=bulletSpeed;
shootVector[1]*=bulletSpeed;
// ... here return an object that has the enemy's coordinate
// and shootVector as speed
}
Then, since you don't use time in your computations (!! wrooong !! ;-) ) you will make the bullet move with the straightforward :
bullet[0] += bullet.speed[0];
bullet[1] += bullet.speed[1];
Now the issue with fixed-step is that your game will run, say, twice slower on a 30fps device than on a 60fps device. The solution is to compute how much time elapsed since the last refresh, let's call this time 'dt'. Using that time will lead you to an update like :
bullet[0] += dt * bullet.speed[0];
bullet[1] += dt * bullet.speed[1];
and now you'll be framerate-agnostic, your game will feel the same on any device.
I currently have an issue with my code (written in Javascript); I have arrays objects that keep filling as the time goes. An example of an object:
monster.push({
range: 200,
attackSpeed: 500,
lastFire: 100,
id: 'ogre',
speed : 50,
pos:[canvas.width*Math.random(), canvas.height*Math.random()],
sprite: new Sprite('images/sheet_characters.png',[508,224],64,64],6,[0])
and
hero={
attackSpeed: 200,
lastGetHit: Date.now(),
lastFire: Date.now(),
health : 100,
speed: 256, //pixel/second
pos:[canvas.width/2,canvas.height/2],
sprite: new Sprite('images/sheet_characters.png',[256,0],[32,32],8,[0]) };
The position field of the objects change quite often and I want to add a function that determines the slope between the monster and the hero (we want the monster to fire at the hero) and then the attack should follow a linear movement.
What I currently have
for(var i=0; i<monster.length; i++){
var mob = monster[i];
mob.sprite.update(delta); //animatie
var newPos = moveTowards(mob, hero, delta);
mob.pos[0] = newPos[0]
mob.pos[1] = newPos[1]
if(checkBounds(mob.pos,mob.sprite.size)){
monster.splice(i,1);
}
mobAttacks(mob);
var attack = enemyAttacks[i]; //atacks updaten
attack.sprite.update(delta);
attack.pos[0] = attack.speed * Math.cos(attack.direction)));
attack.pos[1] = attack.speed * Math.sin(attack.direction)));
if(checkBounds(attack.pos,attack.sprite.sieze)){
enemyAttacks.splice(i,1);
}
}
In this for-loop I can access the position of the monster that fires and also the hero position as it is a global variable. Now the function to attack is :
function mobAttacks(object)
{
var distance = Math.sqrt(Math.pow((hero.pos[0]-object.pos[0]),2) + Math.pow((hero.pos[1]-object.pos[1]),2));
if( Date.now() - object.lastFire > object.attackSpeed && object.range >= distance)
{
deltaY = hero.pos[1] - object.pos[1];
deltaX = hero.pos[0] - object.pos[0];
var direction = Math.atan(deltaY/deltaX);
enemyAttacks.push({
pos:[(object.pos[0]+object.sprite.size[0]/2), (object.pos[1]+object.sprite.size[1]/2)],
direction: direction,
speed: 128, //pixel/s
sprite: new Sprite('images/sheet_objects.png', [231,3],[24,24],6,[0])
});
object.lastFire = Date.now();
}
}
The angle between both objects is calculated and I make a new object (the attack) with the start position of the monster.
The result is quite odd:
The slope is off, so is the Y position of the boulder. Also when the hero is on the left side of the monster, there is no boulder to be spotted.
After some hours of tinkering with the code I came to the conclusion that I couldn't solve my current problem.
EDIT:
attack.pos[0] += attack.speed * Math.cos(attack.direction)*delta;
attack.pos[1] += attack.speed * Math.sin(attack.direction)*delta;
Solved the issue that the boulders are no longer cast from a random position.
Now the angle is a not going negative when I'm in the 2nd or 3rd kwadrant (position left when viewed from the monster perspective)
Get all the trig out of your code, it's unnecessary. Let
deltaX = hero.pos[0] - object.pos[0];
deltaY = hero.pos[1] - object.pos[1];
then
distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
deltaX /= distance;
deltaY /= distance;
will make <deltaX,deltaY> a normalized vector (one with a length of 1).
Then you can update the position of the attack for delta time using simply:
attack.pos[0] += attack.speed * attack.deltaX * delta;
attack.pos[1] += attack.speed * attack.deltaY * delta;
If you don't have any use for the speed and direction separately, you can also pre-multiply speed into deltaX and deltaY when you initialize the attack, meaning that the update becomes only
attack.pos[0] += attack.deltaX * delta;
attack.pos[1] += attack.deltaY * delta;
which is nice and simple.
How can I recreate this type movement with jquery for images: http://www.istockphoto.com/stock-video-12805249-moving-particles-loop-soft-green-hd-1080.php
I'm planning to use it as a web page background. If it is not possible with jquery I'll go with flash as3. But I prefer jquery.
Edit: Raphael is definitely better suited for this, since it supports IE. The problem with jQuery is that the rounded corners are a pain to do in IE due to CSS constraints... in Raphael cross browser circles are no sweat.
jsFiddle with Raphael - all browsers:
(though it might look nicer speeded up in IE)
(function() {
var paper, circs, i, nowX, nowY, timer, props = {}, toggler = 0, elie, dx, dy, rad, cur, opa;
// Returns a random integer between min and max
// Using Math.round() will give you a non-uniform distribution!
function ran(min, max)
{
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function moveIt()
{
for(i = 0; i < circs.length; ++i)
{
// Reset when time is at zero
if (! circs[i].time)
{
circs[i].time = ran(30, 100);
circs[i].deg = ran(-179, 180);
circs[i].vel = ran(1, 5);
circs[i].curve = ran(0, 1);
circs[i].fade = ran(0, 1);
circs[i].grow = ran(-2, 2);
}
// Get position
nowX = circs[i].attr("cx");
nowY = circs[i].attr("cy");
// Calc movement
dx = circs[i].vel * Math.cos(circs[i].deg * Math.PI/180);
dy = circs[i].vel * Math.sin(circs[i].deg * Math.PI/180);
// Calc new position
nowX += dx;
nowY += dy;
// Calc wrap around
if (nowX < 0) nowX = 490 + nowX;
else nowX = nowX % 490;
if (nowY < 0) nowY = 490 + nowY;
else nowY = nowY % 490;
// Render moved particle
circs[i].attr({cx: nowX, cy: nowY});
// Calc growth
rad = circs[i].attr("r");
if (circs[i].grow > 0) circs[i].attr("r", Math.min(30, rad + .1));
else circs[i].attr("r", Math.max(10, rad - .1));
// Calc curve
if (circs[i].curve > 0) circs[i].deg = circs[i].deg + 2;
else circs[i].deg = circs[i].deg - 2;
// Calc opacity
opa = circs[i].attr("fill-opacity");
if (circs[i].fade > 0) {
circs[i].attr("fill-opacity", Math.max(.3, opa - .01));
circs[i].attr("stroke-opacity", Math.max(.3, opa - .01)); }
else {
circs[i].attr("fill-opacity", Math.min(1, opa + .01));
circs[i].attr("stroke-opacity", Math.min(1, opa + .01)); }
// Progress timer for particle
circs[i].time = circs[i].time - 1;
// Calc damping
if (circs[i].vel < 1) circs[i].time = 0;
else circs[i].vel = circs[i].vel - .05;
}
timer = setTimeout(moveIt, 60);
}
window.onload = function () {
paper = Raphael("canvas", 500, 500);
circs = paper.set();
for (i = 0; i < 30; ++i)
{
opa = ran(3,10)/10;
circs.push(paper.circle(ran(0,500), ran(0,500), ran(10,30)).attr({"fill-opacity": opa,
"stroke-opacity": opa}));
}
circs.attr({fill: "#00DDAA", stroke: "#00DDAA"});
moveIt();
elie = document.getElementById("toggle");
elie.onclick = function() {
(toggler++ % 2) ? (function(){
moveIt();
elie.value = " Stop ";
}()) : (function(){
clearTimeout(timer);
elie.value = " Start ";
}());
}
};
}());
The first attempt jQuery solution is below:
This jQuery attempt pretty much failes in IE and is slow in FF. Chrome and Safari do well:
jsFiddle example for all browsers (IE is not that good)
(I didn't implement the fade in IE, and IE doesn't have rounded corners... also the JS is slower, so it looks pretty bad overall)
jsFiddle example for Chrome and Safari only (4x more particles)
(function() {
var x, y, $elie, pos, nowX, nowY, i, $that, vel, deg, fade, curve, ko, mo, oo, grow, len;
// Returns a random integer between min and max
// Using Math.round() will give you a non-uniform distribution!
function ran(min, max)
{
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function moveIt()
{
$("div.spec").each(function(i, v) {
$elie = $(v);
if (! $elie.data("time"))
{
$elie.data("time", ran(30, 100));
$elie.data("deg", ran(-179, 180));
$elie.data("vel", ran(3, 10));
$elie.data("curve", ran(0, 1));
$elie.data("fade", ran(0, 1));
$elie.data("grow", ran(-2, 2));
}
vel = $elie.data("vel");
deg = $elie.data("deg");
fade = $elie.data("fade");
curve = $elie.data("curve");
grow = $elie.data("grow");
len = $elie.width();
if (grow > 0)
len = Math.min(len + grow, 50);
else
len = Math.max(len + grow, 20);
$elie.css("-moz-border-radius", len/2);
$elie.css("border-radius", len/2);
$elie.css("width", len);
$elie.css("height", len);
pos = $elie.position();
$elie.data("time", $elie.data("time") - 1);
if (curve)
$elie.data("deg", (deg + 5) % 180);
else
$elie.data("deg", (deg - 5) % 180);
ko = $elie.css("-khtml-opacity");
mo = $elie.css("-moz-opacity");
oo = $elie.css("opacity");
if (fade)
{
$elie.css("-khtml-opacity", Math.max(ko - .1, .5));
$elie.css("-moz-opacity", Math.max(mo - .1, .5));
$elie.css("opacity", Math.max(oo - .1, .5));
} else
{
$elie.css("-khtml-opacity", Math.min(ko - -.1, 1));
$elie.css("-moz-opacity", Math.min(mo - -.1, 1));
$elie.css("opacity", Math.min(oo - -.1, 1));
}
if (vel < 3)
$elie.data("time", 0);
else
$elie.data("vel", vel - .2);
nowX = pos.left;
nowY = pos.top;
x = vel * Math.cos(deg * Math.PI/180);
y = vel * Math.sin(deg * Math.PI/180);
nowX = nowX + x;
nowY = nowY + y;
if (nowX < 0)
nowX = 490 + nowX;
else
nowX = nowX % 490;
if (nowY < 0)
nowY = 490 + nowY;
else
nowY = nowY % 490;
$elie.css("left", nowX);
$elie.css("top", nowY);
});
}
$(function() {
$(document.createElement('div')).appendTo('body').attr('id', 'box');
$elie = $("<div/>").attr("class","spec");
// Note that math random is inclussive for 0 and exclussive for Max
for (i = 0; i < 100; ++i)
{
$that = $elie.clone();
$that.css("top", ran(0, 495));
$that.css("left", ran(0, 495));
$("#box").append($that);
}
timer = setInterval(moveIt, 60);
$("input").toggle(function() {
clearInterval(timer);
this.value = " Start ";
}, function() {
timer = setInterval(moveIt, 60);
this.value = " Stop ";
});
});
}());
[Partial answer, just for the physics.]
[I just saw the previous answer, mine is somewhat along the same lines.]
You may try to simulate some sort of Brownian motion, i.e. a movement deriving
from the combination of a random force and a viscous damping. Pseudocode:
initialize:
x = random_position();
v_x = random_velocity(); // v_x = velocity along x
// and same for y
for (each time step) {
x += v_x;
v_x += random_force() - time_step / damping_time * v_x;
// and same for y
}
Keep the damping time long (~ 1 second) and the amplitude of the random force small. Otherwise the movement may be too jerky.
For an easy to implement Gaussian random number generator, look up Box-Muller in Wikipedia.
For the mathematics of it, you give every object a starting position and velocity. The "random walk" is achieved by computing a random angle that is constrained by some amount (experiment). Then change the angle of the velocity vector by this angle. You can also compute a random speed delta and change the magnitude of the vector by that amount. Because you're working with velocity, the movements will be somewhat smooth. A slightly more advanced approach to to work with acceleration directly and compute velocity and position based off that.
For your random steering value, a binomial distribution is preferable to a uniform one. Binomial distributions are concentrated around 0 instead of uniformly spread out. You can just do random() - random() (psuedocode)
Vector math is extensively documented but if you run into a snag, leave a comment.
very late answer from my side, but I thought I might give an approach...
I personally would use an svg vector image.
Create a jquery plugin which accepts opacity, size. and makes them move in a random direction.
Then do a javascript loop in creating a set of those particles (where opacity and size are random, plus the start location is random)
Then make the jquery plugin to initiate a new instance of itself when the particle is unloaded.
(If you look at the little movie you will see that they move in 1 direction and fade out, then another fades in.)
The opacity effect will give the depth perspective.
Not sure if my answer helps, but I would go in that direction.