问题
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 - y1, x2 - x1) * 180 / Math.PI;
to calculate the direction to the center point, then reversing the direction by subtracting 180 degrees from the angle.
Here's the rest of my code dealing with the particles:
for ( var i = 0; i < le.length; i ++ ) {
// Create new particle element and append it to document.
lObj[i] = new Particle(0, 0, 9, 0, 0, document.createElement('span'));
lObj[i].spanEl.innerHTML = le[i];
lObj[i].spanEl.style.position = 'relative';
p.appendChild(lObj[i].spanEl);
// Find location of this particle.
loc[i] = lObj[i].spanEl.getBoundingClientRect();
// Calculate direction toward center point and reverse away from it.
lObj[i].direction = directionToPoint( loc[i].left, loc[i].top, centX, centY) - 180;
}
var x = 0,
y = 0,
vel,
dir;
function loop() {
for (var i = 0; i < le.length; i ++ ) {
// Update location of each particle
x = lObj[i].relX;
y = lObj[i].relY;
vel = lObj[i].velocity;
dir = lObj[i].direction;
dir = lObj[i].direction;
x += vel * Math.cos( dir * Math.PI / 180 );
y += vel * Math.sin( dir * Math.PI / 180 );
vel = (vel > 0) * ( vel - 0.2 );
lObj[i].relX = x;
lObj[i].relY = y;
lObj[i].velocity = vel;
lObj[i].spanEl.style.left = x + 'px';
lObj[i].spanEl.style.top = y + 'px';
}
}
Image of what's happening
Seriously can not figure this one out. Help from you guys would be GREATLY appreciated!
Edit: Rest of the code
var p = document.getElementsByTagName('p')[0],
le = p.innerHTML.split('');
p.innerHTML = '';
var lObj = [];
function Particle (relX, relY, velocity, direction, keyframe, spanEl) {
this.relX = relX;
this.relY = relY;
this.velocity = velocity;
this.direction = direction;
this.friction = 0.1;
this.keyframe = keyframe;
this.spanEl = spanEl;
}
var loc = [];
var centX = 800, centY = 250;
var marker = document.getElementsByClassName('marker')[0];
marker.style.left = centX + 'px';
marker.style.top = centY + 'px';
回答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.
- get two random numbers between 0 and 1
- subtract 0.5 from each, so I have 2 numbers in the range [-0.5 .. 0.5]
- Use the number gained in 1 as the initial position.
- Normalize the vector from the origin to this position (by viewing the position as a vector)
- 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]
- get a random number [0..25] and add 25 to it - this will be the max age.
function byId(id){return document.getElementById(id)}
window.addEventListener('load', onDocLoaded, false);
var partArray = [], maxParticles=500;
function onDocLoaded(evt)
{
for (var i=0; i<maxParticles; i++)
{
var pos = new vec2_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 = new part_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();
}
}
function vec2_t(x,y)
{
this.x = x;
this.y = y;
this.normalize = function()
{
var result = new vec2_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;
}
return this;
}
function part_t(position, velocity, maxAge)
{
this.position = position;
this.velocity = velocity;
this.maxAge = maxAge;
this.age = 0;
return this;
}
function setPixel(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);
}
function drawParticles()
{
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/2
setPixel( 256+partArray[partNum].position.x, 256+partArray[partNum].position.y, ctx);
}
}
function moveParticles()
{
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;
}
}
}
<canvas width=512 height=512 id='partSysCanvas'></canvas>
回答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";
function newEl(tag){return document.createElement(tag)}
function byId(id){return document.getElementById(id)}
// useful for HtmlCollection, NodeList, String types (array-like objects without the forEach method)
function forEach(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
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function vec2_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(){return Math.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+">"}
return this;
}
function part_t(vec2pos, vec2vel, domElem)
{
this.pos = vec2pos;
this.vel = vec2vel;
this.domElem = domElem;
return this;
}
var particleArray, timerId;
let explosionOrigin = new vec2_t(156,110);
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(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);
}
function onStepBtnClick(evt)
{
updatePartSys(particleArray);
}
function onAnimateBtnClick(evt)
{
timerId = setInterval(function(){updatePartSys(particleArray);}, 100);
this.setAttribute('disabled', 'true');
byId('pauseBtn').removeAttribute('disabled');
}
function onPauseBtnClick(evt)
{
clearInterval(timerId);
this.setAttribute('disabled', 'true');
byId('animateBtn').removeAttribute('disabled');
}
function onResetBtnClick(evt)
{
var bombImg = byId('bomb');
byId('tgtContainer').innerHTML = '';
byId('tgtContainer').appendChild(bombImg);
particleArray = createPartSys('textInput', 'tgtContainer');
byId('animateBtn').removeAttribute('disabled');
clearInterval(timerId);
}
function createPartSys(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 = ' '
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 = new vec2_t(curX,curY);
curX += span.offsetWidth;
var vel = new vec2_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 = new part_t(pos,vel,span);
result.push(newPart);
}
);
return result;
}
function updatePartSys(partSys)
{
forEach(
partSys,
function(part, index, array)
{
part.pos.addVec(part.vel); // position += velocity
var gravity = new vec2_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";
}
);
}
function onClick(evt)
{
let elemRect = byId('tgtContainer').getBoundingClientRect();
let posX = evt.clientX - elemRect.left, posY = evt.clientY-elemRect.top;
explosionOrigin.x = posX;
explosionOrigin.y = posY;
onResetBtnClick();
}
function getCenter(elem)
{
let x = elem.offsetLeft + (elem.offsetWidth/2);
let y = elem.offsetTop + (elem.offsetHeight/2);
let result = new vec2_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;
}
#textContainer textarea
{
width: 100%;
padding: 0;
margin: 1px 0px;
}
<div class='panel'>
<div id='textContainer'><textarea id='textInput'>click to set bomb position</textarea></div>
<hr>
<button id='resetBtn'>Reset</button><button id='stepBtn'>Single Step</button> | <button id='animateBtn'>Animate</button><button id='pauseBtn' disabled>Pause</button>
<hr>
<div id='tgtContainer'>
</div>
</div>
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" id='bomb'>
<g transform="translate(0,-1020.3622)">
<path d="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"/>
<path d="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"/>
<path d="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"/>
<path d="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"/>
<rect height="2.399" width="4.798" y="1026" x="13.31" stroke-width="0" fill="#26201e"/>
<path fill="#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>
来源:https://stackoverflow.com/questions/39673387/having-trouble-calculating-direction-for-particle-explosion