题记:写这个程序是因为偶然看到这个链接里的幕布模拟效果,虽然作者公布了代码,但是笔者没有参考,根据标题<基于韦尔莱算法>自己写了下面这个程序,权当练习。
正如题记所言,该程序的意义就是模拟一个可以撕扯的幕布,在显示上使用OpenCV3.0的库来显示,并且响应鼠标消息。顺便提一句,OpenCV3.0相比之前的2.4.x而言,终于把多个lib合并到一个了,现在只要添加一个world就都解决了,真好啊!
读者可以参考维基百科对韦尔莱算法的介绍,就能想清楚这个程序的实现。不过这里最复杂的还是各种参数的调整,否则会出现不少bug。
首先给出我编译后的程序:http://download.csdn.net/detail/delltdk/8691797
下面是主要代码:
CurtainGrain:
CurtainGrain.h
#ifndef _CURTAINGRAIN_H_
#define _CURTAINGRAIN_H_
#include "stdafx.h"
#include
#define MAX(x,y) (x)>(y)?(x):(y)
#define MIN(x,y) (x)<(y)?(x):(y)
#define BreakLink 0
#define Gravity_g 9.8f
enum NeighborType
{
upNeibor = 0x01,
downNeibor,
leftNeibor,
rightNeibor
};
struct Acc{
float ax;
float ay;
float az;
};
struct Power
{
float px;
float py;
float pz;
void Add( const Power f ){
px += f.px;
py += f.py;
pz += f.pz;
}
Acc GetAcc( const float m ){
Acc acc;
acc.ax = px/m;
acc.ay = py/m;
acc.az = pz/m;
}
};
struct Pos
{
float x;
float y;
float z;
};
struct Velocity
{
float vx;
float vy;
float vz;
};
class CurtainGrain
{
public:
CurtainGrain();
CurtainGrain(int x, int y);
~CurtainGrain();
int colId, rowId;
bool isVisited;
void SetFixed();
void SetPos();
void SetGround(float _ground);
void SetPos(const float scale);
void GetPos( Pos &_pos );
bool GetNeiborVisitStatus(NeighborType type);
bool NeiborExist(NeighborType type);
void GetUpPos( Pos &_pos );
void GetDownPos( Pos &_pos );
void GetLeftPos( Pos &_pos );
void GetRightPos( Pos &_pos );
void SetNeighbor( CurtainGrain* pNeibor, NeighborType type );
void RemoveNeighbor( NeighborType type );
void ClearOutsidePower();
void SetOutsidePower( const Power out_f );
void UpdatePosAfterPower(const float deltaT);
private:
void CalcSelfOtherPower( Power &selfPower, Power &otherPower, const Pos otherPos );
float iCalcSelfPower( const float dis );
// These are state properties of Curtain_Grain
bool isFixed;
Pos pos, prevPos;
Velocity vt, prevVt;
Power prevF;
float ground;
CurtainGrain* left;
CurtainGrain* right;
CurtainGrain* up;
CurtainGrain* down;
// These are inner properties of Curtain_Grain
float k; // the factor in equation f = k*x^2 when it's drawed
float l; // the factor in equation f = l*x^2 when it's clustered
float rho; // the density in equation m = rho*Volume
float s; // the length of Curtain_Grain, so Volume = s^2
float m; // the weight of one Curtain_Grain
float wind_fac; // the factor of wind strength
float linkPower; // max power the link can bear
Power f; // the sum power which is measured in N(Newton)
};
inline float redef_abs(float x)
{
return x>0?x:-x;
}
#endif
#include "stdafx.h"
#include "CurtainGrain.h"
CurtainGrain::CurtainGrain()
{
isVisited = false;
k = 2.0f; // N/m^2
l = 2.0f;
rho = 0.25f; // kg/m^3
s = 0.05f; // m
m = rho*s*s; // kg
wind_fac = 0.005f;
linkPower = 0.03f;
f.py = m*Gravity_g;
f.px = f.pz = 0.0f;
prevF = f;
vt.vx = vt.vy = vt.vz = 0.0f;
prevVt = vt;
left = right = up = down = BreakLink;
}
CurtainGrain::CurtainGrain( int x, int y )
{
colId = x;
rowId = y;
isVisited = false;
isFixed = false;
k = 2.0f; // N/m^2
l = 2.0f;
rho = 0.25f; // kg/m^3
s = 0.05f; // m
m = rho*s*s*0.02f; // kg
wind_fac = 0.005f;
linkPower = MAX(0.03f,0.06f-y*0.003f);
f.py = m*Gravity_g;
f.px = f.pz = 0.0f;
prevF = f;
vt.vx = vt.vy = vt.vz = 0.0f;
prevVt = vt;
left = right = up = down = BreakLink;
}
CurtainGrain::~CurtainGrain()
{
}
void CurtainGrain::SetFixed()
{
isFixed = true;
}
void CurtainGrain::SetPos()
{
prevPos.x = pos.x = colId*s;
prevPos.y = pos.y = rowId*s;
prevPos.z = pos.z = 0.0f;
}
void CurtainGrain::SetGround(float _ground)
{
ground = _ground;
}
void CurtainGrain::SetPos(const float scale)
{
prevPos.y *= scale;
pos.y *= scale;
}
void CurtainGrain::GetPos( Pos &_pos )
{
if( rowId==2 && colId== 0 && pos.y > 100 )
int tempInt = 0;
_pos = pos;
}
bool CurtainGrain::GetNeiborVisitStatus(NeighborType type)
{
switch (type)
{
case upNeibor:
return up->isVisited;
case downNeibor:
return down->isVisited;
case leftNeibor:
return left->isVisited;
case rightNeibor:
return right->isVisited;
default:
break;
}
return false;
}
bool CurtainGrain::NeiborExist(NeighborType type)
{
switch(type)
{
case upNeibor:
return up != BreakLink;
case downNeibor:
return down != BreakLink;
case leftNeibor:
return left != BreakLink;
case rightNeibor:
return right != BreakLink;
default:
return false;
}
}
void CurtainGrain::GetUpPos( Pos &_pos )
{
if(up) up->GetPos(_pos);
}
void CurtainGrain::GetDownPos( Pos &_pos )
{
if(down) down->GetPos(_pos);
}
void CurtainGrain::GetLeftPos( Pos &_pos )
{
if(left) left->GetPos(_pos);
}
void CurtainGrain::GetRightPos( Pos &_pos )
{
if(right) right->GetPos(_pos);
}
void CurtainGrain::SetNeighbor( CurtainGrain* pNeibor, NeighborType type )
{
if ( pNeibor )
{
switch (type)
{
case upNeibor:
{
up = pNeibor;
break;
}
case downNeibor:
{
down = pNeibor;
break;
}
case leftNeibor:
{
left = pNeibor;
break;
}
case rightNeibor:
{
right = pNeibor;
break;
}
default:
break;
}
}
}
void CurtainGrain::RemoveNeighbor( NeighborType type )
{
switch (type)
{
case upNeibor:
{
up = BreakLink;
break;
}
case downNeibor:
{
down = BreakLink;
break;
}
case leftNeibor:
{
left = BreakLink;
break;
}
case rightNeibor:
{
right = BreakLink;
break;
}
default:
break;
}
}
void CurtainGrain::ClearOutsidePower()
{
prevF = f;
f.px = f.pz = 0.0f;
f.py = m*Gravity_g;
}
void CurtainGrain::SetOutsidePower( const Power out_f )
{
f.Add(out_f);
Power selfPower,otherPower;
Pos tmpPos;
if (up)
{
if(!up->isVisited)
{
up->GetPos( tmpPos );
CalcSelfOtherPower( selfPower, otherPower, tmpPos );
float p = selfPower.px + selfPower.py + selfPower.pz;
if (p > linkPower)
{
up->down = BreakLink;
up = BreakLink;
}
else
{
f.Add( selfPower );
up->f.Add( otherPower );
}
}
}
if (down)
{
if(!down->isVisited)
{
down->GetPos( tmpPos );
CalcSelfOtherPower( selfPower, otherPower, tmpPos );
float p = selfPower.px + selfPower.py + selfPower.pz;
if (p > linkPower)
{
down->up = BreakLink;
down = BreakLink;
}
else
{
f.Add( selfPower );
down->f.Add( otherPower );
}
}
}
if (left)
{
if(!left->isVisited)
{
left->GetPos( tmpPos );
CalcSelfOtherPower( selfPower, otherPower, tmpPos );
float p = selfPower.px + selfPower.py + selfPower.pz;
if (p > linkPower)
{
left->right = BreakLink;
left = BreakLink;
}
else
{
f.Add( selfPower );
left->f.Add( otherPower );
}
}
}
if (right)
{
if(!right->isVisited)
{
right->GetPos( tmpPos );
CalcSelfOtherPower( selfPower, otherPower, tmpPos );
float p = selfPower.px + selfPower.py + selfPower.pz;
if (p > linkPower)
{
right->left = BreakLink;
right = BreakLink;
}
else
{
f.Add( selfPower );
right->f.Add( otherPower );
}
}
}
// Add the wind power
Power wind;
wind.px = -wind_fac*(pos.x - prevPos.x);
wind.py = -wind_fac*(pos.y - prevPos.y);
wind.pz = -wind_fac*(pos.z - prevPos.z);
f.Add(wind);
isVisited = true;
}
void CurtainGrain::UpdatePosAfterPower( const float deltaT )
{
if(isFixed) return ;
float deltaTInS = deltaT*0.001f;
Pos temp = pos;
Velocity tmpVt = vt;
Power tmpF = f;
tmpF.Add(prevF);
//vt.vx = vt.vx + 0.5*tmpF.px/m*deltaTInS;
//vt.vy = vt.vy + 0.5*tmpF.py/m*deltaTInS;
//vt.vz = vt.vz + 0.5*tmpF.pz/m*deltaTInS;
//pos.x = pos.x + (redef_abs(vt.vx*deltaTInS)<0.05?vt.vx*deltaTInS:(vt.vx<0?-0.05:0.05));
//pos.y = pos.y + (redef_abs(vt.vy*deltaTInS)<0.05?vt.vy*deltaTInS:(vt.vy<0?-0.05:0.05));
//pos.z = pos.z + (redef_abs(vt.vz*deltaTInS)<0.05?vt.vz*deltaTInS:(vt.vz<0?-0.05:0.05));
pos.x = 2*pos.x - prevPos.x + f.px/m*deltaTInS*deltaTInS;
pos.y = 2*pos.y - prevPos.y + f.py/m*deltaTInS*deltaTInS;
pos.z = 2*pos.z - prevPos.z + f.pz/m*deltaTInS*deltaTInS;
// the grain must be above the ground
pos.y = MIN(ground,pos.y);
prevVt = tmpVt;
prevPos = temp;
}
void CurtainGrain::CalcSelfOtherPower(
Power &selfPower, Power &otherPower,
const Pos otherPos )
{
float disx = otherPos.x - pos.x;
float disy = otherPos.y - pos.y;
float disz = otherPos.z - pos.z;
float dis = sqrt(disx*disx + disy*disy + disz*disz);
float strength = iCalcSelfPower( dis );
float selfPx = disx/dis*strength;
float selfPy = disy/dis*strength;
float selfPz = disz/dis*strength;
selfPower.px = selfPx;
selfPower.py = selfPy;
selfPower.pz = selfPz;
otherPower.px = -selfPx;
otherPower.py = -selfPy;
otherPower.pz = -selfPz;
}
float CurtainGrain::iCalcSelfPower( const float dis )
{
if( dis > 0 )
{
float tmp = redef_abs(dis)-s;
if(tmp > 0)
{
return k*tmp*tmp; // positive means "pull" power
}
else
{
return -l*tmp*tmp; // negative means "push" power
}
}
else
{
return 0.0f;
}
}
Curtain:
Curtain.h
#ifndef _CURTAIN_H_
#define _CURTAIN_H_
#include "stdafx.h"
#include "CurtainGrain.h"
#include "cv.h"
#include "highgui.h"
#include
class Curtain
{
public:
Curtain(int _w, int _h);
~Curtain();
void InitAllPos(const float scale);
void ReceivePower( const Pos _pos, const Power _f );
void ReceivePower( const CvPoint _pt, const Power _f );
void ShowThePosImage( const std::string winName );
float fistRange; // the range affected by the fist measured in m
private:
// These are the inner properties
int w, h; // the curtain has w*h grains
CurtainGrain* pFirst; // the first curtain grain in the curtain
// These are the operate params
float deltaT; // the time interval for update, it's measured in ms
IplImage* backImg; // a white image to plaint on
float meterPerPixel; // how long(in m) one pixel distance denotes
CurtainGrain* GetGrainById(int x, int y);
CvPoint MapPosToPoint(const Pos _pos);
Pos MapPointToPos(const CvPoint _pt);
double t;
};
#endif
#include "stdafx.h"
#include "Curtain.h"
Curtain
::Curtain(int _w, int _h)
{
w = _w;
h = _h;
deltaT = 4.0f;
fistRange = 0.2f;
meterPerPixel = 5.0f/7.0f*0.01f;
pFirst = new CurtainGrain[w*h];
for (int i = 0; i < w*h; i++)
{
int x = i%w, y = i/w;
CurtainGrain* curGrain = pFirst+i;
memcpy(curGrain, &CurtainGrain(x,y), sizeof(CurtainGrain));
if(y==0) curGrain->SetFixed();
//set position
curGrain->SetPos();
//set ground
float temp = MapPointToPos(cvPoint(0,610)).y;
curGrain->SetGround(temp);
//setup the link
if(x!=0)
curGrain->SetNeighbor(GetGrainById(x-1,y), leftNeibor);
if(x!=w-1)
curGrain->SetNeighbor(GetGrainById(x+1,y), rightNeibor);
if(y!=0)
curGrain->SetNeighbor(GetGrainById(x,y-1), upNeibor);
if(y!=h-1)
curGrain->SetNeighbor(GetGrainById(x,y+1), downNeibor);
}
backImg = cvCreateImage(cvSize(800,640),IPL_DEPTH_8U, 3);
cvSet(backImg,cvScalarAll(255));
cvLine(backImg,cvPoint(5,610),cvPoint(795,610),CV_RGB(0,0,0),2);
}
Curtain
::~Curtain()
{
delete[] pFirst;
}
CurtainGrain* Curtain::GetGrainById(int x, int y)
{
return (CurtainGrain*)(pFirst + y*w + x);
}
CvPoint Curtain::MapPosToPoint(const Pos _pos)
{
return cvPoint(cvRound(_pos.x/meterPerPixel)+136,
cvRound(_pos.y/meterPerPixel)+41);
}
Pos Curtain::MapPointToPos(const CvPoint _pt)
{
Pos temp;
temp.z = 0.0f;
temp.x = (_pt.x-136)*meterPerPixel;
temp.y = (_pt.y-41)*meterPerPixel;
return temp;
}
void Curtain::InitAllPos(const float scale)
{
for (int x = 0; x < w; x ++)
{
for (int y = 0; y < h; y++)
{
CurtainGrain* curGrain = GetGrainById(x,y);
curGrain->SetPos(scale);
}
}
}
void Curtain::ReceivePower( const Pos _pos, const Power _f )
{
t = (double)cvGetTickCount();
int id = 0;
while (idClearOutsidePower();
id ++;
}
Pos tmpPos;
Power tmpPower;
for( int y = 0; y < h; y ++ )
{
for (int x = 0; x < w; x++)
{
CurtainGrain* curGrain = GetGrainById(x,y);
curGrain->GetPos(tmpPos);
if (abs(tmpPos.x-_pos.x) < fistRange &&
abs(tmpPos.y-_pos.y) < fistRange)
{
float diff = sqrt((tmpPos.x-_pos.x)*(tmpPos.x-_pos.x) +
(tmpPos.y-_pos.y)*(tmpPos.y-_pos.y));
tmpPower.px = _f.px*exp(-diff);
tmpPower.py = _f.py*exp(-diff);
tmpPower.pz = _f.pz*exp(-diff);
curGrain->SetOutsidePower( tmpPower );
curGrain->UpdatePosAfterPower(deltaT);
}
else
{
tmpPower.px = tmpPower.py = tmpPower.pz = 0.0f;
curGrain->SetOutsidePower( tmpPower );
curGrain->UpdatePosAfterPower(deltaT);
}
}
}
}
void Curtain::ReceivePower( const CvPoint _pt, const Power _f )
{
Pos temp = MapPointToPos(_pt);
ReceivePower(temp,_f);
}
void Curtain::ShowThePosImage( const std::string winName )
{
IplImage* showImg = cvCloneImage(backImg);
Pos tmpPos;
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
CurtainGrain* curGrain = GetGrainById(x,y);
curGrain->GetPos(tmpPos);
CvPoint curPt = MapPosToPoint(tmpPos);
if(curGrain->isVisited)
{
if(curGrain->NeiborExist(upNeibor) &&
curGrain->GetNeiborVisitStatus(upNeibor))
{
curGrain->GetUpPos(tmpPos);
cvLine(showImg,curPt,MapPosToPoint(tmpPos),cvScalarAll(0));
}
if(curGrain->NeiborExist(downNeibor) &&
curGrain->GetNeiborVisitStatus(downNeibor))
{
curGrain->GetDownPos(tmpPos);
cvLine(showImg,curPt,MapPosToPoint(tmpPos),cvScalarAll(0));
}
if(curGrain->NeiborExist(leftNeibor) &&
curGrain->GetNeiborVisitStatus(leftNeibor))
{
curGrain->GetLeftPos(tmpPos);
cvLine(showImg,curPt,MapPosToPoint(tmpPos),cvScalarAll(0));
}
if(curGrain->NeiborExist(rightNeibor) &&
curGrain->GetNeiborVisitStatus(rightNeibor))
{
curGrain->GetRightPos(tmpPos);
cvLine(showImg,curPt,MapPosToPoint(tmpPos),cvScalarAll(0));
}
curGrain->isVisited = false;
}
}
}
t = ((double)cvGetTickCount() - t)/(1000.0*cvGetTickFrequency());
cvShowImage( winName.c_str(), showImg );
cvWaitKey(/*MAX(cvRound(deltaT-t),*/1);
cvReleaseImage( &showImg );
}
#include "stdafx.h"
#include "Curtain.h"
#include "cv.h"
#include "highgui.h"
#define EPS_FLOAT 0.000000001f
CvPoint g_cur,g_prev;
Power g_power;
float g_powerBase = 0.001f;
float g_powerScale = 0.0004f;
void OnMouse(int Event,int x,int y,int flags,void* param)
{
switch (Event)
{
case CV_EVENT_LBUTTONDOWN:
{
g_power.pz = -g_powerBase;
}
break;
case CV_EVENT_MOUSEMOVE:
{
if (flags&CV_EVENT_FLAG_LBUTTON)
{
g_power.px = (abs(x-g_prev.x)*g_powerScale + g_powerBase)*
(x>g_prev.x?1.0f:-1.0f);
g_power.py = (abs(y-g_prev.y)*g_powerScale + g_powerBase)*
(y>g_prev.y?1.0f:-1.0f);
g_power.pz = -g_powerBase;
}
else
{
g_power.px = g_power.py = g_power.pz = 0.0f;
}
}
break;
default:
g_power.px = g_power.py = g_power.pz = 0.0f;
}
g_prev.x = g_cur.x = x;
g_prev.y = g_cur.y = y;
}
int _tmain(int argc, _TCHAR* argv[])
{
g_power.px = g_power.py = g_power.pz = 0.0f;
g_prev.x = g_prev.y = g_cur.x = g_cur.y = 0;
std::string winName = "display";
cvNamedWindow(winName.c_str());
cvSetMouseCallback(winName.c_str(),OnMouse,NULL);
Curtain curtain(70,30);
curtain.InitAllPos(1.0);
int tdk = 0;
while (true)
{
//printf("tdk=%d\n",tdk++);
curtain.ReceivePower(g_cur,g_power);
curtain.ShowThePosImage("display");
//char c = cvWaitKey(1);
//if(c==27) break;
}
return 0;
}