多边形轮廓上的边缘并不总是正确的
我使用下面的算法来生成四边形,然后渲染成这样的轮廓
<块引用>I'm using the algorithm below to generate quads which are then rendered to make an outline like this
http://img810.imageshack.us/img810/8530/uhohz.png
The problem as seen on the image is that sometimes the lines are too thin when they should always be the same width. My algorithm finds the 4
vertices for the first one then the top 2
vertices of the next ones are the bottom 2
of the previous. This creates connected lines, but it seems to not always work. How could I fix this?
This is my algorithm:
void OGLENGINEFUNCTIONS::GenerateLinePoly(const std::vector<std::vector<GLdouble>> &input,
std::vector<GLfloat> &output, int width)
{
output.clear();
if(input.size() < 2)
{
return;
}
int temp;
float dirlen;
float perplen;
POINTFLOAT start;
POINTFLOAT end;
POINTFLOAT dir;
POINTFLOAT ndir;
POINTFLOAT perp;
POINTFLOAT nperp;
POINTFLOAT perpoffset;
POINTFLOAT diroffset;
POINTFLOAT p0, p1, p2, p3;
for(unsigned int i = 0; i < input.size() - 1; ++i)
{
start.x = static_cast<float>(input[i][0]);
start.y = static_cast<float>(input[i][1]);
end.x = static_cast<float>(input[i + 1][0]);
end.y = static_cast<float>(input[i + 1][1]);
dir.x = end.x - start.x;
dir.y = end.y - start.y;
dirlen = sqrt((dir.x * dir.x) + (dir.y * dir.y));
ndir.x = static_cast<float>(dir.x * 1.0 / dirlen);
ndir.y = static_cast<float>(dir.y * 1.0 / dirlen);
perp.x = dir.y;
perp.y = -dir.x;
perplen = sqrt((perp.x * perp.x) + (perp.y * perp.y));
nperp.x = static_cast<float>(perp.x * 1.0 / perplen);
nperp.y = static_cast<float>(perp.y * 1.0 / perplen);
perpoffset.x = static_cast<float>(nperp.x * width * 0.5);
perpoffset.y = static_cast<float>(nperp.y * width * 0.5);
diroffset.x = static_cast<float>(ndir.x * 0 * 0.5);
diroffset.y = static_cast<float>(ndir.y * 0 * 0.5);
// p0 = start + perpoffset - diroffset
// p1 = start - perpoffset - diroffset
// p2 = end + perpoffset + diroffset
// p3 = end - perpoffset + diroffset
p0.x = start.x + perpoffset.x - diroffset.x;
p0.y = start.y + perpoffset.y - diroffset.y;
p1.x = start.x - perpoffset.x - diroffset.x;
p1.y = start.y - perpoffset.y - diroffset.y;
if(i > 0)
{
temp = (8 * (i - 1));
p2.x = output[temp + 2];
p2.y = output[temp + 3];
p3.x = output[temp + 4];
p3.y = output[temp + 5];
}
else
{
p2.x = end.x + perpoffset.x + diroffset.x;
p2.y = end.y + perpoffset.y + diroffset.y;
p3.x = end.x - perpoffset.x + diroffset.x;
p3.y = end.y - perpoffset.y + diroffset.y;
}
output.push_back(p2.x);
output.push_back(p2.y);
output.push_back(p0.x);
output.push_back(p0.y);
output.push_back(p1.x);
output.push_back(p1.y);
output.push_back(p3.x);
output.push_back(p3.y);
}
}
Edit:
POINTFLOAT multiply(const POINTFLOAT &a, float b)
{
POINTFLOAT result;
result.x = a.x * b;
result.y = a.y * b;
return result;
}
POINTFLOAT normalize(const POINTFLOAT &a)
{
return multiply(a, 1.0f / sqrt(a.x * a.x + a.y * a.y));
}
POINTFLOAT slerp2d( const POINTFLOAT v0,
const POINTFLOAT v1, float t )
{
float dot = (v0.x * v1.x + v1.y * v1.y);
if( dot < -1.0f ) dot = -1.0f;
if( dot > 1.0f ) dot = 1.0f;
float theta_0 = acos( dot );
float theta = theta_0 * t;
POINTFLOAT v2;
v2.x = -v0.y;
v2.y = v0.x;
POINTFLOAT result;
result.x = v0.x * cos(theta) + v2.x * sin(theta);
result.y = v0.y * cos(theta) + v2.y * sin(theta);
return result;
}
void OGLENGINEFUNCTIONS::GenerateLinePoly(const std::vector<std::vector<GLdouble> > &input,
std::vector<GLfloat> &output, int width)
{
output.clear();
if(input.size() < 2)
{
return;
}
float w = width / 2.0f;
//glBegin(GL_TRIANGLES);
for( size_t i = 0; i < input.size()-1; ++i )
{
POINTFLOAT cur;
cur.x = input[i][0];
cur.y = input[i][1];
POINTFLOAT nxt;
nxt.x = input[i+1][0];
nxt.y = input[i+1][1];
POINTFLOAT b;
b.x = nxt.x - cur.x;
b.y = nxt.y - cur.y;
b = normalize(b);
POINTFLOAT b_perp;
b_perp.x = -b.y;
b_perp.y = b.x;
POINTFLOAT p0;
POINTFLOAT p1;
POINTFLOAT p2;
POINTFLOAT p3;
p0.x = cur.x + b_perp.x * w;
p0.y = cur.y + b_perp.y * w;
p1.x = cur.x - b_perp.x * w;
p1.y = cur.y - b_perp.y * w;
p2.x = nxt.x + b_perp.x * w;
p2.y = nxt.y + b_perp.y * w;
p3.x = nxt.x - b_perp.x * w;
p3.y = nxt.y - b_perp.y * w;
output.push_back(p0.x);
output.push_back(p0.y);
output.push_back(p1.x);
output.push_back(p1.y);
output.push_back(p2.x);
output.push_back(p2.y);
output.push_back(p2.x);
output.push_back(p2.y);
output.push_back(p1.x);
output.push_back(p1.y);
output.push_back(p3.x);
output.push_back(p3.y);
// only do joins when we have a prv
if( i == 0 ) continue;
POINTFLOAT prv;
prv.x = input[i-1][0];
prv.y = input[i-1][1];
POINTFLOAT a;
a.x = prv.x - cur.x;
a.y = prv.y - cur.y;
a = normalize(a);
POINTFLOAT a_perp;
a_perp.x = a.y;
a_perp.y = -a.x;
float det = a.x * b.y - b.x * a.y;
if( det > 0 )
{
a_perp.x = -a_perp.x;
a_perp.y = -a_perp.y;
b_perp.x = -b_perp.x;
b_perp.y = -b_perp.y;
}
// TODO: do inner miter calculation
// flip around normals and calculate round join points
a_perp.x = -a_perp.x;
a_perp.y = -a_perp.y;
b_perp.x = -b_perp.x;
b_perp.y = -b_perp.y;
size_t num_pts = 4;
std::vector< POINTFLOAT> round( 1 + num_pts + 1 );
POINTFLOAT nc;
nc.x = cur.x + (a_perp.x * w);
nc.y = cur.y + (a_perp.y * w);
round.front() = nc;
nc.x = cur.x + (b_perp.x * w);
nc.y = cur.y + (b_perp.y * w);
round.back() = nc;
for( size_t j = 1; j < num_pts+1; ++j )
{
float t = (float)j / (float)(num_pts + 1);
if( det > 0 )
{
POINTFLOAT nin;
nin = slerp2d( b_perp, a_perp, 1.0f-t );
nin.x *= w;
nin.y *= w;
nin.x += cur.x;
nin.y += cur.y;
round[j] = nin;
}
else
{
POINTFLOAT nin;
nin = slerp2d( a_perp, b_perp, t );
nin.x *= w;
nin.y *= w;
nin.x += cur.x;
nin.y += cur.y;
round[j] = nin;
}
}
for( size_t j = 0; j < round.size()-1; ++j )
{
output.push_back(cur.x);
output.push_back(cur.y);
if( det > 0 )
{
output.push_back(round[j + 1].x);
output.push_back(round[j + 1].y);
output.push_back(round[j].x);
output.push_back(round[j].y);
}
else
{
output.push_back(round[j].x);
output.push_back(round[j].y);
output.push_back(round[j + 1].x);
output.push_back(round[j + 1].y);
}
}
}
}
解决方案
Requires Eigen as written, but the core operations should map easily to whatever vector class you're using.
// v0 and v1 are normalized
// t can vary between 0 and 1
// http://number-none.com/product/Understanding%20Slerp,%20Then%20Not%20Using%20It/
Vector2f slerp2d( const Vector2f& v0, const Vector2f& v1, float t )
{
float dot = v0.dot(v1);
if( dot < -1.0f ) dot = -1.0f;
if( dot > 1.0f ) dot = 1.0f;
float theta_0 = acos( dot );
float theta = theta_0 * t;
Vector2f v2( -v0.y(), v0.x() );
return ( v0*cos(theta) + v2*sin(theta) );
}
void glPolyline( const vector<Vector2f>& polyline, float width )
{
if( polyline.size() < 2 ) return;
float w = width / 2.0f;
glBegin(GL_TRIANGLES);
for( size_t i = 0; i < polyline.size()-1; ++i )
{
const Vector2f& cur = polyline[ i ];
const Vector2f& nxt = polyline[i+1];
Vector2f b = (nxt - cur).normalized();
Vector2f b_perp( -b.y(), b.x() );
Vector2f p0( cur + b_perp*w );
Vector2f p1( cur - b_perp*w );
Vector2f p2( nxt + b_perp*w );
Vector2f p3( nxt - b_perp*w );
// first triangle
glVertex2fv( p0.data() );
glVertex2fv( p1.data() );
glVertex2fv( p2.data() );
// second triangle
glVertex2fv( p2.data() );
glVertex2fv( p1.data() );
glVertex2fv( p3.data() );
// only do joins when we have a prv
if( i == 0 ) continue;
const Vector2f& prv = polyline[i-1];
Vector2f a = (prv - cur).normalized();
Vector2f a_perp( a.y(), -a.x() );
float det = a.x()*b.y() - b.x()*a.y();
if( det > 0 )
{
a_perp = -a_perp;
b_perp = -b_perp;
}
// TODO: do inner miter calculation
// flip around normals and calculate round join points
a_perp = -a_perp;
b_perp = -b_perp;
size_t num_pts = 4;
vector< Vector2f > round( 1 + num_pts + 1 );
for( size_t j = 0; j <= num_pts+1; ++j )
{
float t = (float)j/(float)(num_pts+1);
if( det > 0 )
round[j] = cur + (slerp2d( b_perp, a_perp, 1.0f-t ) * w);
else
round[j] = cur + (slerp2d( a_perp, b_perp, t ) * w);
}
for( size_t j = 0; j < round.size()-1; ++j )
{
glVertex2fv( cur.data() );
if( det > 0 )
{
glVertex2fv( round[j+1].data() );
glVertex2fv( round[j+0].data() );
}
else
{
glVertex2fv( round[j+0].data() );
glVertex2fv( round[j+1].data() );
}
}
}
glEnd();
}
EDIT: Screenshots:
相关文章