终于拿到期待已久的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(); }
void Particle2D::Update() { m_velocity += m_acceleration; m_location += m_velocity; ClearForce(); }
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); }
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(); }
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(); } }
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; }
最后还要修改一下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 时的效果!!
最后还是给一个下载链接吧http://download.csdn.net/detail/aichipmunk/7218957