Skip to content Skip to sidebar Skip to footer

Having Trouble Calculating Direction For Particle Explosion

I'm trying make a particle explosion system, where each particle explodes directly away from a central point. But I'm getting some really funky behavior. I'm using Math.atan2(y2 -

Solution 1:

Further to a comment above, here's a poorly implemented particle system utilizing 2d vectors. I've hacked together an 'animation', using a for loop - obviously, I should use window.requestAnimationFrame(https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) But that's beyond the scope of this example.

To initialize the position and direction of each particle I do the following.

  1. get two random numbers between 0 and 1
  2. subtract 0.5 from each, so I have 2 numbers in the range [-0.5 .. 0.5]
  3. Use the number gained in 1 as the initial position.
  4. Normalize the vector from the origin to this position (by viewing the position as a vector)
  5. Multiply it by a number between 0 and 10. The particles will have random directions and will have a speed that ranges from [0..10]
  6. get a random number [0..25] and add 25 to it - this will be the max age.

functionbyId(id){returndocument.getElementById(id)}
window.addEventListener('load', onDocLoaded, false);

var partArray = [], maxParticles=500;


functiononDocLoaded(evt)
{
	for (var i=0; i<maxParticles; i++)
	{
		var pos = newvec2_t( Math.random()-0.5, Math.random()-0.5 );		// give them a poition of [-0.5..0.5]var vel = pos.normalize();
		vel.scalarMult( Math.random() * 10 );								// give them a velocity of [0..10]var maxAge = (Math.random() * 25) + 25;								// age is in range [25..50]var newParticle = newpart_t(pos, vel, maxAge);						// create the particle
		partArray.push( newParticle );										// and put it in our array
	}
	
	for (var y=0; y<5; y++)
	{
		drawParticles();
		moveParticles();
	}
}

functionvec2_t(x,y)
{
	this.x = x;
	this.y = y;
	
	this.normalize = function()
	{
		var result = newvec2_t(0,0);
		var lenSq = (this.x*this.x) + (this.y*this.y);
		var len = Math.sqrt(lenSq);
		result.x = this.x / len;
		result.y = this.y / len;
		return result;
	}
	this.scalarMult = function(scalar)
	{
		this.x *= scalar;
		this.y *= scalar;
	}
	
	returnthis;
}

functionpart_t(position, velocity, maxAge)
{
	this.position = position;
	this.velocity = velocity;
	this.maxAge = maxAge;
	this.age = 0;
	returnthis;
}

functionsetPixel(x,y,ctx)
{
	var imgData = ctx.getImageData(x,y,1,1);
	imgData.data[ 0 ] = 255;
	imgData.data[ 1 ] = 0;
	imgData.data[ 2 ] = 0;
	imgData.data[ 3 ] = 255;
	ctx.putImageData(imgData,x,y);
//	console.log(x+','+y);
}

functiondrawParticles()
{
	var can = byId('partSysCanvas');
	var ctx = can.getContext('2d');
	var partNum;
	for (partNum=0; partNum<maxParticles; partNum++)
	{
        // add 256,256 since this is canvas.width/2,canvas.height/2setPixel( 256+partArray[partNum].position.x, 256+partArray[partNum].position.y, ctx);
	}
}
functionmoveParticles()
{
	for (var i=0; i<maxParticles; i++)
	{
		if (partArray[i].age < partArray[i].maxAge)
		{
			partArray[i].age++;
			partArray[i].position.x += partArray[i].velocity.x;
			partArray[i].position.y += partArray[i].velocity.y;
		}
	}
}
<canvaswidth=512height=512id='partSysCanvas'></canvas>

Solution 2:

That was some fun. Again, I've eschewed trigonometry in preference of using vectors. The letters dont explode from the centre - text is drawn and then the letters explode from this point away from the seat of the explosion. I've set a uniform explosion velocity for all of the particles with vel.scalarMult( 10 ); you can randomize this instead with the line above it.

I've also not bothered to scale particle velocity based on it's proximity to the bomb. Everything just explodes away from the seat, where we know the force felt from the explosion reduces as distance increases.

Here's a working demo to play with.

I really should be using requestAnimationFrame

"use strict";
functionnewEl(tag){returndocument.createElement(tag)}
functionbyId(id){returndocument.getElementById(id)}
// useful for HtmlCollection, NodeList, String types (array-like objects without the forEach method)functionforEach(array, callback, scope){for (var i=0,n=array.length; i<n; i++)callback.call(scope, array[i], i, array);} // passes back stuff we need///////////////////////////////////////////////////////////////////////////////////////////////////////////////////functionvec2_t(x,y)
{
	this.x=x;
	this.y=y;
	this.equals = function(vec2){this.x = vec2.x; this.y = vec2.y;}
	this.addVec = function(vec2){this.x += vec2.x; this.y += vec2.y;}
	this.scalarMult = function(scalar){this.x *= scalar; this.y *= scalar;}
	this.vecLen = function(){returnMath.sqrt( this.x*this.x + this.y*this.y );}
	this.normalize = function(){ let k = 1.0 / this.vecLen(); this.scalarMult(k); }
	this.vecSub = function(vec2){this.x-=vec2.x;this.y-=vec2.y;}
	this.toString = function(){return"<"+this.x+","+this.y+">"}
	returnthis;
}

functionpart_t(vec2pos, vec2vel, domElem)
{
	this.pos = vec2pos;
	this.vel = vec2vel;
	this.domElem = domElem;
	returnthis;
}



var particleArray, timerId;
let explosionOrigin = newvec2_t(156,110);

window.addEventListener('load', onDocLoaded, false);



functiononDocLoaded(evt)
{
	particleArray = createPartSys('textInput', 'tgtContainer');
	byId('stepBtn').addEventListener('click', onStepBtnClick);
	byId('resetBtn').addEventListener('click', onResetBtnClick);
	byId('animateBtn').addEventListener('click', onAnimateBtnClick);
	
	byId('pauseBtn').addEventListener('click', onPauseBtnClick);
	
	byId('tgtContainer').addEventListener('click', onClick);
}

functiononStepBtnClick(evt)
{
	updatePartSys(particleArray);
}

functiononAnimateBtnClick(evt)
{
	timerId = setInterval(function(){updatePartSys(particleArray);}, 100);
	this.setAttribute('disabled', 'true');
	byId('pauseBtn').removeAttribute('disabled');
}

functiononPauseBtnClick(evt)
{
	clearInterval(timerId);
	this.setAttribute('disabled', 'true');
	byId('animateBtn').removeAttribute('disabled');
}

functiononResetBtnClick(evt)
{
	var bombImg = byId('bomb');
	byId('tgtContainer').innerHTML = '';
	byId('tgtContainer').appendChild(bombImg);
	
	particleArray = createPartSys('textInput', 'tgtContainer');
	
	byId('animateBtn').removeAttribute('disabled');
	clearInterval(timerId);
}

functioncreatePartSys(srcElemId, tgtElemId)
{
	let	elem = byId(srcElemId);
	var str = elem.value, len=str.length;

	let result = [];
	let parent = elem;
	let curX = elem.offsetLeft - parent.offsetLeft;
	let curY = elem.offsetTop - parent.offsetTop;

	let bombImg = byId('bomb');
	bombImg.style = 'position: absolute';
	bombImg.style.left = (explosionOrigin.x - (bombImg.clientWidth/2))+'px';
	bombImg.style.top = (explosionOrigin.y - (bombImg.clientHeight/2))+'px';
	byId(tgtElemId).appendChild(bombImg);
		
	curY = 50;
	curX = 50;
	forEach(str,
			function(letter)
			{
				var span = newEl('span');
				span.className = 'particle';
				if (letter == ' ') letter = '&nbsp;'let h1 = newEl('h1');
				h1.innerHTML = letter;
				span.appendChild(h1);
				span.style.left = curX + 'px';
				span.style.top = curY + 'px';
				byId(tgtElemId).appendChild(span);
				
				var pos = newvec2_t(curX,curY);
				
				curX += span.offsetWidth;

				var vel = newvec2_t(0,0);
				
				let letterOrigin = getCenter(span);
				
				vel.equals(letterOrigin);
				vel.vecSub(explosionOrigin);
				vel.normalize();
			//	vel.scalarMult( (Math.random()*1) + 4 );
				vel.scalarMult( 10 );
				
				var newPart = newpart_t(pos,vel,span);
				result.push(newPart);
			}
		);
	return result;
}

functionupdatePartSys(partSys)
{
	forEach(
			partSys,
			function(part, index, array)
			{
				part.pos.addVec(part.vel);						// position += velocityvar gravity = newvec2_t(0,0.98/3);				// arbitrary value chosen
				part.vel.scalarMult(0.95);						// velocity *= 0.95	- needs to be quite high. it simulates wind resistance
				part.vel.addVec(gravity);						// velocity += gravity
				part.domElem.style.left = part.pos.x + "px";
				part.domElem.style.top = part.pos.y + "px";
			}
		);
}

functiononClick(evt)
{
	let elemRect = byId('tgtContainer').getBoundingClientRect();
	let posX = evt.clientX - elemRect.left, posY = evt.clientY-elemRect.top;
	explosionOrigin.x = posX;
	explosionOrigin.y = posY;
	onResetBtnClick();
}

functiongetCenter(elem)
{
	let x = elem.offsetLeft + (elem.offsetWidth/2);
	let y = elem.offsetTop + (elem.offsetHeight/2);
	let result = newvec2_t(x,y);
	return result;
}
#tgtContainer
{
	position: relative;
	height: 256px;
	width: 512px;
	border: solid 1px black;
	overflow: hidden;
	background-color: white;
}
.particle
{
	display: inline-block;
	position: absolute;
}
.panel
{
	display: inline-block;
	border: solid 1px#113;
	border-radius: 8px;
	margin: 8px;
	padding: 8px;
	background-image: url(https://www.gravatar.com/avatar/97c2d181ef6bbb9eee0c4033561c3891?s=48&d=identicon&r=PG);
	background-size: 100%100%;
}

#textContainer
{
	display: block;
}

#textContainertextarea
{ 
	width: 100%; 
	padding: 0;
	margin: 1px0px;
}
<divclass='panel'><divid='textContainer'><textareaid='textInput'>click to set bomb position</textarea></div><hr><buttonid='resetBtn'>Reset</button><buttonid='stepBtn'>Single Step</button> | <buttonid='animateBtn'>Animate</button><buttonid='pauseBtn'disabled>Pause</button><hr><divid='tgtContainer'></div></div><svgxmlns="http://www.w3.org/2000/svg"height="32"width="32"id='bomb'><gtransform="translate(0,-1020.3622)"><pathd="m23.23,15.84a10.55,10.55,0,1,1,-21.11,0,10.55,10.55,0,1,1,21.11,0z"transform="matrix(1.1875635,0,0,1.1875635,0.68612298,1020.367)"fill="#26201e"/><pathd="m23.23,15.84a10.55,10.55,0,1,1,-21.11,0,10.55,10.55,0,1,1,21.11,0z"transform="matrix(0.86603158,0,0,0.86603158,2.4299747,1024.1874)"fill="#333"/><pathd="m-13.04,19.32a1.964,1.964,0,1,1,-3.929,0,1.964,1.964,0,1,1,3.929,0z"transform="matrix(1.924285,1.1058108,-1.1908732,2.0723069,62.314757,1012.6494)"fill="#CCC"/><pathd="m15.69,1026c0.02518-5.037,7.647-7.396,8.907-2.969,0.7936,2.761,1.349,5.666,4.877,6.786"stroke="#888"stroke-width="1.5px"fill="none"/><rectheight="2.399"width="4.798"y="1026"x="13.31"stroke-width="0"fill="#26201e"/><pathfill="#F00"transform="translate(2.0203051,1022.13)"d="M29.8,10.53,27.1,9.62,24.82,11.32,24.86,8.477,22.54,6.833,25.25,5.989,26.1,3.271,27.74,5.595,30.59,5.558,28.89,7.839z"/></g></svg>

Post a Comment for "Having Trouble Calculating Direction For Particle Explosion"