【问题标题】:Having trouble calculating direction for particle explosion无法计算粒子爆炸的方向
【发布时间】:2016-09-24 06:43:50
【问题描述】:

我正在尝试制作一个粒子爆炸系统,其中每个粒子 直接从中心点爆炸。但我得到了一些非常时髦的行为。

我使用Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI; 计算到中心点的方向,然后通过从角度减去 180 度来反转方向。

这是我处理粒子的其余代码:

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

真的无法弄清楚这一点。非常感谢你们的帮助!

编辑:其余代码



    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';

【问题讨论】:

  • 与其将速度的标量 (1d) 值与方向航向结合使用,不如保持 2​​d 向量更容易。如果完成,您不必关心回到原点的角度是多少 - 这样可以节省一些三角函数。也就是说 - 你为什么要增加 180° 来反转方向? Point-Origin = 从原点到点的向量。而 Origin-Point = 从点到原点的向量。使用 2d 向量使基本粒子系统变得微不足道。
  • 请同时提供Particle 的代码以及leloc 的示例数据。
  • 嘿,谢谢!添加了其余的代码。你能把你刚才说的话改一下,让我更容易理解吗?
  • @PeterLewis - 没问题,我已经尝试使用第二个代码示例来做到这一点。那是一堆乐趣-希望它有用。请问有什么不清楚的。 :)

标签: javascript html particle-system particles atan2


【解决方案1】:

除了上面的评论,这是一个使用 2d 向量的粒子系统实现得很差。我已经使用 for 循环组合了一个“动画”——显然,我应该使用 window.requestAnimationFrame(https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) 但这超出了本示例的范围。

要初始化每个粒子的位置和方向,我执行以下操作。

  1. 获取 0 到 1 之间的两个随机数
  2. 从每个减去 0.5,所以我在 [-0.5 .. 范围内有 2 个数字 0.5]
  3. 使用1中获得的数字作为初始位置。
  4. 将向量从原点归一化到该位置(通过查看 作为向量的位置)
  5. 将其乘以 0 到 10 之间的数字。粒子将具有 随机方向,速度范围为 [0..10]
  6. 获取一个随机数 [0..25] 并将其加 25 - 这将是最大值 年龄。

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;
		}
	}
}
&lt;canvas width=512 height=512 id='partSysCanvas'&gt;&lt;/canvas&gt;

【讨论】:

  • 谢谢 :) 我实际上不只是试图创建一个爆炸粒子系统,而是爆炸文本。我想要的效果是让它们全部从中心爆炸,而不是在所有随机方向。
【解决方案2】:

这很有趣。同样,我避免使用三角函数而不是使用向量。字母不会从中心爆炸 - 绘制文本,然后字母从该点爆炸,远离爆炸位置。我用vel.scalarMult( 10 );为所有粒子设置了一个统一的爆炸速度,你可以用它上面的线来随机化它。

我也没有费心根据它与炸弹的接近程度来衡量粒子速度。一切都在远离座位的地方爆炸,我们知道爆炸产生的力会随着距离的增加而减小。

这是一个可以玩的工作演示。

我真的应该使用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 = '&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 = 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>

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-07
    • 1970-01-01
    • 1970-01-01
    • 2012-06-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多