重庆幸运农场中奖金额|重庆幸运农场官网
MyException - 我的異常網
當前位置:我的異常網» 綜合 » 玩玩Leap Motion跟粒子效果

玩玩Leap Motion跟粒子效果

www.h0f1.com  網友分享于:2013-10-09  瀏覽:44次
玩玩Leap Motion和粒子效果

終于拿到期待已久的Leap Motion了,600多軟妹幣,比Kinect for Windows便宜多了!先灑一個圖。



剛拿到就有寫程序的沖動,Leap Motion可以精確定位人的手指,因此打算寫一個用手指控制的粒子效果,下面一步一步來。


一、粒子系統

常有人問,初中高中學的那些物理有什么用?我現在知道了,那些物理知識可以用來寫粒子效果!而且只需要了解基本的牛頓力學即可(牛頓三定律)。

描述一個物體(粒子)的運動和狀態,需要哪些物理量?物體的質量,物體的空間坐標,物體的速度還有物體的加速度。因此我們創建一個粒子類,這個類中必須包含這4個成員變量。

float	m_mass;
ofVec3f m_location;
ofVec3f	m_velocity;
ofVec3f m_acceleration;

粒子在運動過程中勢必會受力,受力怎么表現呢?想必下面這個公式一定無人不知、無人不曉:


也就是說,受力是通過改變物體的加速度來表現的,因此我們寫下如下一組方法:

void ApplyForce(ofVec3f force)
{
	m_acceleration += force/m_mass;
}

void ClearForce()
{
	m_acceleration = ofVec3f::zero();
}

加速度是指速度單位時間內的變化量,因此加速度會影響速度,同理速度是路程單位時間內的變化量,所以速度會影響路程(空間坐標)。我們將Openframeworks中的每一幀看做一個單位時間,因此每一幀都要對速度和空間坐標進行更新,具體實現如下:

void Particle2D::Update()
{
	m_velocity += m_acceleration;
	m_location += m_velocity;

	ClearForce();
}

每一幀調用完以上方法后,我們還要將這個粒子畫出來,在這個粒子類中,我還包含了一個ofImage類型的變量,用于將一副圖片畫在屏幕上,這些都在Draw方法中完成:

void Particle2D::Draw()
{
	float h = m_image.height;
	float w = m_image.width;

	m_image.setAnchorPercent(0.5f, 0.5f);
	m_image.draw(m_location.x, m_location.y, w, h);

}

其中的setAnchorPercent方法的作用,是將圖片的原點設置在圖像中心處,這樣在繪制圖片時,圖片的中心就會在我繪制的坐標處。當然,我們還需要一個方法來設置要繪制的圖片,我將這一部分代碼寫在了粒子類的構造函數中:

Particle2D::Particle2D( ofImage& image, float mass, ofVec2f location, ofVec2f velocity )
	: m_image(image)
{
	m_mass = mass;
	m_location = location;
	m_velocity = velocity;
	m_acceleration = ofVec2f::zero();
}

好了,粒子類就這樣寫完了,是不是非常簡單!接下來,我們需要另外一個類將所有粒子管理起來,負責所有粒子的發射、更新還有繪制,我們取名為ParticlesController類,這個類用一個list容器將所有粒子存儲起來(為什么用list而不用vector,后面會說到),更新和繪制時,就遍歷整個list,并調用對應粒子的Update方法和Draw方法,具體實現如下:

void ParticlesController::Emit( vector<Particle>& particles )
{
	for (int i = 0; i < particles.size(); ++i)
	{
		m_particles.push_back(particles[i]);
	}
}

void ParticlesController::Emit( Particle& particle )
{
	m_particles.push_back(particle);
}

void ParticlesController::Update()
{
	for (list<Particle>::iterator it = m_particles.begin(); it != m_particles.end(); )
	{
		it.Update();
		++it;
	}
}

void ParticlesController::Draw()
{
	for (list<Particle>::iterator it = m_particles.begin(); it != m_particles.end(); ++it)
	{
		it.Draw();
	}
}

好了,我們已經完成了整個粒子系統,使用時只需實例化一個ParticlesController類,并調用Emit方法將你實例化好的粒子扔進去就行了,就像下面這樣:

void testApp::EmitParticles( ofImage& image, int count, ofVec2f location, ofVec2f velocity )
{
	for (int i = 0; i < count ; ++i)
	{
		Particle2D p = Particle2D(
			image,
			ofRandom(0.5f, 2),
			location + ofVec2f(ofRandomf()*5, ofRandomf()*5),
			velocity + ofVec2f(ofRandomf()*5, ofRandomf()*5)
			);

		m_particles_ctrl.Emit(p);
	}
	
}

注意,要實現好的粒子效果,精髓是誤差,所以我在實例化粒子時,對決定粒子特性的參數都使用了隨機數,這都是為了制造粒子的獨特性與隨機性。不過,以上代碼產生的粒子任然比較死板,我們希望粒子的運動更具有不確定性,我們希望粒子在運動過程中大小會發生改變,我們希望粒子具有壽命,會隨著時間的推移而消失。為此,我們要對粒子類進行較大的改動,首先賦予粒子壽命,在類中加入下面三個成員變量:

int	m_lifespan;
int	m_age;
bool	m_is_dead;

并修改Update方法,每一次更新都將m_age加一,如果m_age大于m_lifespan就將m_is_dead設置為true:

void Particle2D::Update()
{
	m_velocity += m_acceleration;
	m_location += m_velocity;

	ClearForce();

        m_age++;
	if (m_age > m_lifespan)
		m_is_dead = true;
}
同時修改ParticlesController類中的Update方法,將已經死亡的粒子從列表中刪除(這就是為什么用list的原因,隨機刪除效率比vector高):

void ParticlesController::Update()
{
	for (list<Particle>::iterator it = m_particles.begin(); it != m_particles.end(); )
	{
		if (it.IsDead())
		{
			it = m_particles.erase(it);
		}
		else
		{
			it.Update();
			++it;
		}
	}
}
接下來,我們要讓粒子的大小逐漸變小,且運動軌跡不可琢磨,為此再次加入兩個成員變量:

float	m_decay;
float	m_scale;

第一個變量是衰減量,用于模擬阻力,第二個量是所放量,用于控制繪制粒子時的大小。同時我們引入柏林噪聲(Perlin Noise)對粒子的運動進行干涉,關于柏林噪聲,大家可以自行谷歌之。修改后的Update方法如下:

void Particle2D::Update()
{
	m_velocity += m_acceleration;

	float noise = ofNoise(m_location.x*0.005f, m_location.y*0.005f, ofGetElapsedTimef()*0.1f);
	float angle = noise*15.0f;
	ofVec2f noise_vec = ofVec2f(cos(angle), sin(angle)) * 0.2f * (1 - m_scale);
	m_velocity += noise_vec;

	m_location += m_velocity;
	m_velocity *= m_decay;

	m_scale = 1 - m_age / (float)m_lifespan;
	m_scale = min(max(m_scale, 0.0f), 1.0f);
	
	ClearForce();

	m_age++;
	if (m_age > m_lifespan)
		m_is_dead = true;
}

可以看出,柏林噪聲會隨著粒子的空間位置和時間變化,之后將噪聲值與三角函數結合,產生最終作用于粒子速度的矢量。其實,那里的三角函數是隨便寫的,也可以是其他數學函數,我們的目的只是為了創造難以琢磨運動軌跡,要注意的是之后的(1 - m_scale),它的作用是使噪聲在早期對粒子的作用很小,而后期作用明顯,因為m_scale是和粒子年齡有關的量,隨著年齡的增長,m_scale會越來越小。代碼中有很多硬編碼,修改那些常量會對粒子的運動效果有影響,可以更具自己的喜好和審美來修改那些量,畢竟這是一種藝術口牙!!!!

最后還要修改一下Draw方法,使m_scale能控制繪圖的大小:

void Particle2D::Draw()
{
	float h = m_image.height * m_scale;
	float w = m_image.width * m_scale;

	m_image.setAnchorPercent(0.5f, 0.5f);
	m_image.draw(m_location.x, m_location.y, w, h);
}

OK!!大功告成,將這些東西放在一起,先暫時用鼠標來測試一下效果,如下圖:



二、加入Leap Motion

LeapMotion的開發實在是太簡單了,比Kinect的開發還要簡單。SDK的細節大家可以去官網上看,我就說一下SDK可以拿到所有手指的位置,指尖的朝向,手指的運動速度,還有很多其他信息,不過對我們的這個小程序而言,這些就已經足夠了。

更具官網上的教程,我們首先得定義一個類并繼承于Leap::Listener類,同時實現這個類中的幾個虛函數:

#include "ofMain.h"
#include <Leap.h>

typedef struct _FingerMotionInfo
{
	ofVec3f location;
	ofVec3f delta_vel;
	int id;

}FingerMotionInfo;

class LeapListener : public Leap::Listener
{
public:
	virtual void onInit(const Leap::Controller&);
	virtual void onConnect(const Leap::Controller&);
	virtual void onDisconnect(const Leap::Controller&);
	virtual void onExit(const Leap::Controller&);
	virtual void onFrame(const Leap::Controller&);

	std::vector<FingerMotionInfo> GetFingerInfos();
	ofMutex& GetMutex();

private:
	std::vector<FingerMotionInfo> m_finger_infos;
	ofMutex m_mutex;
};
那些虛函數中,對我們有用的只有onFrame方法,其他幾個方法在我們的程序中用不到,所以就重點說說onFrame的實現:

void LeapListener::onFrame( const Controller& controller)
{
	const Frame frame = controller.frame();
	
	FingerList fingers = frame.fingers();

	//onFrame is executed on another thread. So we need a mutex to synchronize.
	m_mutex.lock();

	m_finger_infos.clear();
	if (fingers.count() > 0)
	{
		for (int i = 0; i < fingers.count(); ++i)
		{
			Finger& finger = fingers[i];
			if (finger.isValid())
			{
				/*Finger last_finger = last_frame.finger(finger.id());
				if (!last_finger.isValid()) continue;*/

				Vector tip = finger.stabilizedTipPosition();
				Vector vel = finger.tipVelocity()/*tip - last_finger.stabilizedTipPosition()*/;
				
				FingerMotionInfo finger_info;
				finger_info.location = ofVec3f(tip.x, tip.y, tip.z);
				finger_info.delta_vel = ofVec3f(vel.x, vel.y, vel.z);
				finger_info.id = finger.id();

				m_finger_infos.push_back(finger_info);
			}
		}
	}

	m_mutex.unlock();
}
首先注意,onFrame方法是運行在另一個線程上的,并不是創建窗口的主線程,因此為了避免訪問數據時的沖突問題,需要有同步措施,我使用了Openframeworks中的ofMutex,即互斥鎖。其次,我將Leap返回的所有手指信息(主要是位置和速度)都存入了一個數組中,我希望每一個手指都可以控制一個粒子源。為了在主程序中使用同一個互斥鎖,并方便的得到所有手指的信息,我又實現了下面兩個方法:

std::vector<FingerMotionInfo> LeapListener::GetFingerInfos()
{
	return m_finger_infos;
}

ofMutex& LeapListener::GetMutex()
{
	return m_mutex;
}
這樣,在主程序中,我們就可以先用GetMutex得到互斥鎖并鎖住,然后通過GetFingerInfos拿到在onFrame方法中保存的信息。最后更具得到的信息,完成粒子的發射,這一部分代碼可以全部寫在主程序的Update方法中,如下:

void testApp::update(){
	m_particles_ctrl.Update();

	int height = ofGetWindowHeight();
	int width = ofGetWindowWidth();
	int image_index = 0;

	m_leap_listener.GetMutex().lock();

	vector<FingerMotionInfo>& fingers = m_leap_listener.GetFingerInfos();

	m_leap_listener.GetMutex().unlock();

	for (int i = 0; i < fingers.size(); ++i)
	{
		FingerMotionInfo& finger_info = fingers[i];

		finger_info.location.x = finger_info.location.x*LEAP_SCALE + width/2.0f;
		finger_info.location.y = height - finger_info.location.y*LEAP_SCALE;

		finger_info.delta_vel.y *= -1;
		finger_info.delta_vel /= FRAME_RATE;
				
		EmitParticles(m_images[image_index], 15, finger_info.location, finger_info.delta_vel);

		++image_index;
		if (image_index >= m_images.size())
			image_index = 0;
	}

}
由于LeapMotion的坐標系和OF是不一樣的,所以代碼中用了一些最簡單的方法使粒子在正確的位置發射,而且,為了使效果更好,不同手指的粒子我用了不同的圖片(顏色不同),這些圖片都保存在m_images中,發射粒子時會使用其中一個。


OK!是時候看看最終效果了。同時伸出三根手指,這就是 When Code Meets Art 時的效果!!


文章評論

程序員都該閱讀的書
程序員都該閱讀的書
十大編程算法助程序員走上高手之路
十大編程算法助程序員走上高手之路
程序員和編碼員之間的區別
程序員和編碼員之間的區別
初級 vs 高級開發者 哪個性價比更高?
初級 vs 高級開發者 哪個性價比更高?
5款最佳正則表達式編輯調試器
5款最佳正則表達式編輯調試器
什么才是優秀的用戶界面設計
什么才是優秀的用戶界面設計
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
60個開發者不容錯過的免費資源庫
60個開發者不容錯過的免費資源庫
我的丈夫是個程序員
我的丈夫是個程序員
 程序員的樣子
程序員的樣子
“懶”出效率是程序員的美德
“懶”出效率是程序員的美德
Web開發人員為什么越來越懶了?
Web開發人員為什么越來越懶了?
要嫁就嫁程序猿—錢多話少死的早
要嫁就嫁程序猿—錢多話少死的早
Java程序員必看電影
Java程序員必看電影
一個程序員的時間管理
一個程序員的時間管理
老程序員的下場
老程序員的下場
不懂技術不要對懂技術的人說這很容易實現
不懂技術不要對懂技術的人說這很容易實現
10個調試和排錯的小建議
10個調試和排錯的小建議
程序員必看的十大電影
程序員必看的十大電影
我是如何打敗拖延癥的
我是如何打敗拖延癥的
Web開發者需具備的8個好習慣
Web開發者需具備的8個好習慣
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
總結2014中國互聯網十大段子
總結2014中國互聯網十大段子
為啥Android手機總會越用越慢?
為啥Android手機總會越用越慢?
編程語言是女人
編程語言是女人
當下全球最炙手可熱的八位少年創業者
當下全球最炙手可熱的八位少年創業者
程序員應該關注的一些事兒
程序員應該關注的一些事兒
做程序猿的老婆應該注意的一些事情
做程序猿的老婆應該注意的一些事情
漫畫:程序員的工作
漫畫:程序員的工作
程序員眼里IE瀏覽器是什么樣的
程序員眼里IE瀏覽器是什么樣的
為什么程序員都是夜貓子
為什么程序員都是夜貓子
10個幫程序員減壓放松的網站
10個幫程序員減壓放松的網站
程序員周末都喜歡做什么?
程序員周末都喜歡做什么?
程序員的一天:一寸光陰一寸金
程序員的一天:一寸光陰一寸金
中美印日四國程序員比較
中美印日四國程序員比較
代碼女神橫空出世
代碼女神橫空出世
程序員最害怕的5件事 你中招了嗎?
程序員最害怕的5件事 你中招了嗎?
聊聊HTTPS和SSL/TLS協議
聊聊HTTPS和SSL/TLS協議
那些爭議最大的編程觀點
那些爭議最大的編程觀點
程序員的鄙視鏈
程序員的鄙視鏈
Java 與 .NET 的平臺發展之爭
Java 與 .NET 的平臺發展之爭
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
如何區分一個程序員是“老手“還是“新手“?
如何區分一個程序員是“老手“還是“新手“?
每天工作4小時的程序員
每天工作4小時的程序員
如何成為一名黑客
如何成為一名黑客
軟件開發程序錯誤異常ExceptionCopyright © 2009-2015 MyException 版權所有
重庆幸运农场中奖金额 捕鱼游戏平台排行榜 小区里开个汗蒸房赚钱吗 betoo7足球即时比分 重庆时时历史开奖结果记录查询 卖指纹锁赚钱吗 海南飞鱼app pk10走势图杀码技巧 贪玩蓝月真的花2亿吗 广西福彩中心邮编 香港高频彩 暗网黑客接中国的单吗 福彩幸运农场走势图表 海南环岛赛车 必中双色球6十1 500彩票软件 福彩3d跨度走势图带线