Issue #659: Add line joins.

Added:

love.graphics.setLineJoin(linejoin)
linejoin = love.graphics.getLineJoin()

where linjoin is one of:

- none
- miter
- bevel

Bevel and miter drawing is slightly faster than the 0.8 default (miter), while
the `none` join mode will be slower when overdraw is enabled.
This commit is contained in:
vrld
2013-08-07 21:48:21 +02:00
parent fc3affb9b9
commit be205544b3
7 changed files with 119 additions and 199 deletions
+22 -8
View File
@@ -66,17 +66,26 @@ public:
/**
* Normalizes the Vector.
* @param length Desired length of the vector.
* @return The old length of the Vector.
**/
float normalize();
float normalize(float length = 1.0);
/**
* Gets a normal to the Vector.
* Gets a vector perpendicular to the Vector.
* To get the true (normalized) normal, use v.getNormal(1.0f / v.getLength())
* @return A normal to the Vector.
**/
Vector getNormal() const;
/**
* Gets a vector perpendicular to the Vector.
* To get the true (normalized) normal, use v.getNormal(1.0f / v.getLength())
* @param Scale factor to apply.
* @return A normal to the Vector.
**/
Vector getNormal(float scale) const;
/**
* Adds a Vector to this Vector.
* @param v The Vector we want to add to this Vector.
@@ -186,15 +195,20 @@ inline Vector Vector::getNormal() const
return Vector(-y, x);
}
inline float Vector::normalize()
inline Vector Vector::getNormal(float scale) const
{
return Vector(-y * scale, x * scale);
}
inline float Vector::normalize(float length)
{
float len = getLength();
float length_current = getLength();
if (len > 0)
(*this) /= len;
if (length_current > 0)
(*this) *= length / length_current;
return len;
return length_current;
}
/**
+19
View File
@@ -69,6 +69,16 @@ bool Graphics::getConstant(LineStyle in, const char *&out)
return lineStyles.find(in, out);
}
bool Graphics::getConstant(const char *in, LineJoin &out)
{
return lineJoins.find(in, out);
}
bool Graphics::getConstant(LineJoin in, const char *&out)
{
return lineJoins.find(in, out);
}
bool Graphics::getConstant(const char *in, PointStyle &out)
{
return pointStyles.find(in, out);
@@ -137,6 +147,15 @@ StringMap<Graphics::LineStyle, Graphics::LINE_MAX_ENUM>::Entry Graphics::lineSty
StringMap<Graphics::LineStyle, Graphics::LINE_MAX_ENUM> Graphics::lineStyles(Graphics::lineStyleEntries, sizeof(Graphics::lineStyleEntries));
StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM>::Entry Graphics::lineJoinEntries[] =
{
{ "none", Graphics::LINE_JOIN_NONE },
{ "miter", Graphics::LINE_JOIN_MITER },
{ "bevel", Graphics::LINE_JOIN_BEVEL }
};
StringMap<Graphics::LineJoin, Graphics::LINE_JOIN_MAX_ENUM> Graphics::lineJoins(Graphics::lineJoinEntries, sizeof(Graphics::lineJoinEntries));
StringMap<Graphics::PointStyle, Graphics::POINT_MAX_ENUM>::Entry Graphics::pointStyleEntries[] =
{
{ "smooth", Graphics::POINT_SMOOTH },
+14
View File
@@ -68,6 +68,14 @@ public:
LINE_MAX_ENUM
};
enum LineJoin
{
LINE_JOIN_NONE = 1,
LINE_JOIN_MITER,
LINE_JOIN_BEVEL,
LINE_JOIN_MAX_ENUM
};
enum PointStyle
{
POINT_ROUGH = 1,
@@ -125,6 +133,9 @@ public:
static bool getConstant(const char *in, LineStyle &out);
static bool getConstant(LineStyle in, const char *&out);
static bool getConstant(const char *in, LineJoin &out);
static bool getConstant(LineJoin in, const char *&out);
static bool getConstant(const char *in, PointStyle &out);
static bool getConstant(PointStyle in, const char *&out);
@@ -148,6 +159,9 @@ private:
static StringMap<LineStyle, LINE_MAX_ENUM>::Entry lineStyleEntries[];
static StringMap<LineStyle, LINE_MAX_ENUM> lineStyles;
static StringMap<LineJoin, LINE_JOIN_MAX_ENUM>::Entry lineJoinEntries[];
static StringMap<LineJoin, LINE_JOIN_MAX_ENUM> lineJoins;
static StringMap<PointStyle, POINT_MAX_ENUM>::Entry pointStyleEntries[];
static StringMap<PointStyle, POINT_MAX_ENUM> pointStyles;
+25 -191
View File
@@ -24,6 +24,7 @@
#include "Graphics.h"
#include "window/sdl/Window.h"
#include "Polyline.h"
#include <vector>
#include <sstream>
@@ -647,6 +648,11 @@ void Graphics::setLineStyle(Graphics::LineStyle style)
lineStyle = style;
}
void Graphics::setLineJoin(Graphics::LineJoin join)
{
lineJoin = join;
}
float Graphics::getLineWidth() const
{
return lineWidth;
@@ -657,6 +663,11 @@ Graphics::LineStyle Graphics::getLineStyle() const
return lineStyle;
}
Graphics::LineJoin Graphics::getLineJoin() const
{
return lineJoin;
}
void Graphics::setPointSize(float size)
{
glPointSize((GLfloat)size);
@@ -776,203 +787,26 @@ void Graphics::point(float x, float y)
glEnd();
}
// Calculate line boundary points u1 and u2. Sketch:
// u1
// -------------+---...___
// | ```'''-- ---
// p- - - - - - q- - . _ _ | w/2
// | ` ' ' r +
// -------------+---...___ | w/2
// u2 ```'''-- ---
//
// u1 and u2 depend on four things:
// - the half line width w/2
// - the previous line vertex p
// - the current line vertex q
// - the next line vertex r
//
// u1/u2 are the intersection points of the parallel lines to p-q and q-r,
// i.e. the point where
//
// (p + w/2 * n1) + mu * (q - p) = (q + w/2 * n2) + lambda * (r - q) (u1)
// (p - w/2 * n1) + mu * (q - p) = (q - w/2 * n2) + lambda * (r - q) (u2)
//
// with n1,n2 being the normals on the segments p-q and q-r:
//
// n1 = perp(q - p) / |q - p|
// n2 = perp(r - q) / |r - q|
//
// The intersection points can be calculated using cramers rule.
static void pushIntersectionPoints(Vector *vertices, Vector *overdraw,
int pos, int count, float hw, float overdraw_factor,
const Vector &p, const Vector &q, const Vector &r)
{
// calculate line directions
Vector s = (q - p);
Vector t = (r - q);
// calculate vertex displacement vectors
Vector n1 = s.getNormal();
Vector n2 = t.getNormal();
n1.normalize();
n2.normalize();
float det_norm = n1 ^ n2; // will be close to zero if the angle between the normals is sharp
n1 *= hw;
n2 *= hw;
// lines parallel -> assume intersection at displacement points
if (fabs(det_norm) <= .03)
{
vertices[pos] = q - n2;
vertices[pos+1] = q + n2;
}
// real intersection -> calculate boundary intersection points with cramers rule
else
{
float det = s ^ t;
Vector d = n1 - n2;
Vector b = s - d; // s = q - p
Vector c = s + d;
float lambda = (b ^ t) / det;
float mu = (c ^ t) / det;
// ordering for GL_TRIANGLE_STRIP
vertices[pos] = p + s*mu - n1; // u1
vertices[pos+1] = p + s*lambda + n1; // u2
}
if (overdraw)
{
// displacement of the overdraw vertices
Vector x = (vertices[pos] - q) * overdraw_factor;
overdraw[pos] = vertices[pos];
overdraw[pos+1] = vertices[pos] + x;
overdraw[2*count-pos-2] = vertices[pos+1];
overdraw[2*count-pos-1] = vertices[pos+1] - x;
}
}
// precondition:
// glEnableClientState(GL_VERTEX_ARRAY);
static void draw_overdraw(Vector *overdraw, size_t count, float pixel_size, bool looping)
{
// if not looping, the outer overdraw vertices need to be displaced
// to cover the line endings, i.e.:
// +- - - - //- - + +- - - - - //- - - +
// +-------//-----+ : +-------//-----+ :
// | core // line | --> : | core // line | :
// +-----//-------+ : +-----//-------+ :
// +- - //- - - - + +- - - //- - - - - +
if (!looping)
{
Vector s = overdraw[1] - overdraw[3];
s.normalize();
s *= pixel_size;
overdraw[1] += s;
overdraw[2*count-1] += s;
Vector t = overdraw[count-1] - overdraw[count-3];
t.normalize();
t *= pixel_size;
overdraw[count-1] += t;
overdraw[count+1] += t;
// we need to draw two more triangles to close the
// overdraw at the line start.
overdraw[2*count] = overdraw[0];
overdraw[2*count+1] = overdraw[1];
}
// prepare colors:
// even indices in overdraw* point to inner vertices => alpha = current-alpha,
// odd indices point to outer vertices => alpha = 0.
Color c = gl.getColor();
Color *colors = new Color[2*count+2];
for (size_t i = 0; i < 2*count+2; ++i)
{
colors[i] = c;
// avoids branching. equiv to if (i%2 == 1) colors[i].a = 0;
colors[i].a *= GLubyte(i % 2 == 0);
}
// draw faded out line halos
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);
glVertexPointer(2, GL_FLOAT, 0, (const GLvoid *)overdraw);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 2*count + 2 * int(!looping));
glDisableClientState(GL_COLOR_ARRAY);
// "if GL_COLOR_ARRAY is enabled, the value of the current color is
// undefined after glDrawArrays executes"
gl.setColor(c);
delete[] colors;
}
void Graphics::polyline(const float *coords, size_t count)
{
Vector *vertices = new Vector[count]; // two vertices for every line end-point
Vector *overdraw = NULL;
Vector p,q,r;
bool looping = (coords[0] == coords[count-2]) && (coords[1] == coords[count-1]);
float halfwidth = lineWidth/2.f;
float pixel_size = pixel_size_stack.back();
float overdraw_factor = .0f;
if (lineStyle == LINE_SMOOTH)
if (lineJoin == LINE_JOIN_NONE)
{
overdraw = new Vector[2*count+2];
overdraw_factor = pixel_size / halfwidth;
halfwidth = std::max(.0f, halfwidth - .25f*pixel_size);
NoneJoinPolyline line;
line.render(coords, count, lineWidth * .5f, pixel_size_stack.back(), lineStyle == LINE_SMOOTH);
line.draw();
}
// get line vertex boundaries
// if not looping, extend the line at the beginning, else use last point as `p'
r = Vector(coords[0], coords[1]);
if (!looping)
q = r * 2 - Vector(coords[2], coords[3]);
else
q = Vector(coords[count-4], coords[count-3]);
for (size_t i = 0; i+3 < count; i += 2)
else if (lineJoin == LINE_JOIN_BEVEL)
{
p = q;
q = r;
r = Vector(coords[i+2], coords[i+3]);
pushIntersectionPoints(vertices, overdraw, i, count, halfwidth, overdraw_factor, p,q,r);
BevelJoinPolyline line;
line.render(coords, count, lineWidth * .5f, pixel_size_stack.back(), lineStyle == LINE_SMOOTH);
line.draw();
}
else // LINE_JOIN_MITER
{
MiterJoinPolyline line;
line.render(coords, count, lineWidth * .5f, pixel_size_stack.back(), lineStyle == LINE_SMOOTH);
line.draw();
}
// if not looping, extend the line at the end, else use first point as `r'
p = q;
q = r;
if (!looping)
r += q - p;
else
r = Vector(coords[2], coords[3]);
pushIntersectionPoints(vertices, overdraw, count-2, count, halfwidth, overdraw_factor, p,q,r);
// end get line vertex boundaries
// draw the core line
gl.bindTexture(0);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, (const GLvoid *)vertices);
glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
// draw the line halo (antialiasing)
if (lineStyle == LINE_SMOOTH)
draw_overdraw(overdraw, count, pixel_size, looping);
glDisableClientState(GL_VERTEX_ARRAY);
// cleanup
delete[] vertices;
if (lineStyle == LINE_SMOOTH)
delete[] overdraw;
}
void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h)
+14
View File
@@ -69,6 +69,7 @@ struct DisplayState
// Line.
Graphics::LineStyle lineStyle;
Graphics::LineJoin lineJoin;
// Point.
float pointSize;
@@ -88,6 +89,7 @@ struct DisplayState
backgroundColor.set(0, 0, 0, 255);
blendMode = Graphics::BLEND_ALPHA;
lineStyle = Graphics::LINE_SMOOTH;
lineJoin = Graphics::LINE_JOIN_MITER;
pointSize = 1.0f;
pointStyle = Graphics::POINT_SMOOTH;
scissor = false;
@@ -299,6 +301,12 @@ public:
**/
void setLineStyle(LineStyle style);
/**
* Sets the line style.
* @param style LINE_ROUGH or LINE_SMOOTH.
**/
void setLineJoin(LineJoin style);
/**
* Gets the line width.
**/
@@ -309,6 +317,11 @@ public:
**/
LineStyle getLineStyle() const;
/**
* Gets the line style.
**/
LineJoin getLineJoin() const;
/**
* Sets the size of points.
**/
@@ -455,6 +468,7 @@ private:
std::vector<double> pixel_size_stack; // stores current size of a pixel (needed for line drawing)
LineStyle lineStyle;
LineJoin lineJoin;
float lineWidth;
GLint matrixLimit;
GLint userMatrices;
@@ -843,6 +843,17 @@ int w_setLineStyle(lua_State *L)
return 0;
}
int w_setLineJoin(lua_State *L)
{
Graphics::LineJoin join;
const char *str = luaL_checkstring(L, 1);
if (!Graphics::getConstant(str, join))
return luaL_error(L, "Invalid line join: %s", str);
instance->setLineJoin(join);
return 0;
}
int w_getLineWidth(lua_State *L)
{
lua_pushnumber(L, instance->getLineWidth());
@@ -859,6 +870,16 @@ int w_getLineStyle(lua_State *L)
return 1;
}
int w_getLineJoin(lua_State *L)
{
Graphics::LineJoin join = instance->getLineJoin();
const char *str;
if (!Graphics::getConstant(join, str))
return luaL_error(L, "Unknown line join");
lua_pushstring(L, str);
return 1;
}
int w_setPointSize(lua_State *L)
{
float size = (float)luaL_checknumber(L, 1);
@@ -1453,8 +1474,10 @@ static const luaL_Reg functions[] =
{ "getDefaultMipmapFilter", w_getDefaultMipmapFilter },
{ "setLineWidth", w_setLineWidth },
{ "setLineStyle", w_setLineStyle },
{ "setLineJoin", w_setLineJoin },
{ "getLineWidth", w_getLineWidth },
{ "getLineStyle", w_getLineStyle },
{ "getLineJoin", w_getLineJoin },
{ "setPointSize", w_setPointSize },
{ "setPointStyle", w_setPointStyle },
{ "getPointSize", w_getPointSize },
@@ -74,8 +74,10 @@ int w_setDefaultMipmapFilter(lua_State *L);
int w_getDefaultMipmapFilter(lua_State *L);
int w_setLineWidth(lua_State *L);
int w_setLineStyle(lua_State *L);
int w_setLineJoin(lua_State *L);
int w_getLineWidth(lua_State *L);
int w_getLineStyle(lua_State *L);
int w_getLineJoin(lua_State *L);
int w_setPointSize(lua_State *L);
int w_setPointStyle(lua_State *L);
int w_getPointSize(lua_State *L);