mirror of
https://github.com/love2d/love.git
synced 2026-08-15 15:51:12 +02:00
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:
+22
-8
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user