1. rude
  2. love

Commits

Matthias Richter  committed a9e1ef3

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.

  • Participants
  • Parent commits ac20c9b
  • Branches default

Comments (0)

Files changed (7)

File src/common/Vector.h

View file
  • Ignore whitespace
 
 	/**
 	 * 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;
 
-	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.
 	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;
 }
 
 /**

File src/modules/graphics/Graphics.cpp

View file
  • Ignore whitespace
 	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);
 
 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 },

File src/modules/graphics/Graphics.h

View file
  • Ignore whitespace
 		LINE_MAX_ENUM
 	};
 
+	enum LineJoin
+	{
+		LINE_JOIN_NONE = 1,
+		LINE_JOIN_MITER,
+		LINE_JOIN_BEVEL,
+		LINE_JOIN_MAX_ENUM
+	};
+
 	enum PointStyle
 	{
 		POINT_ROUGH = 1,
 	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);
 
 	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;
 

File src/modules/graphics/opengl/Graphics.cpp

View file
  • Ignore whitespace
 
 #include "Graphics.h"
 #include "window/sdl/Window.h"
+#include "Polyline.h"
 
 #include <vector>
 #include <sstream>
 	lineStyle = style;
 }
 
+void Graphics::setLineJoin(Graphics::LineJoin join)
+{
+	lineJoin = join;
+}
+
 float Graphics::getLineWidth() const
 {
 	return lineWidth;
 	return lineStyle;
 }
 
+Graphics::LineJoin Graphics::getLineJoin() const
+{
+	return lineJoin;
+}
+
 void Graphics::setPointSize(float size)
 {
 	glPointSize((GLfloat)size);
 	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();
 	}
-
-	// 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;
+	else // LINE_JOIN_MITER
+	{
+		MiterJoinPolyline line;
+		line.render(coords, count, lineWidth * .5f, pixel_size_stack.back(), lineStyle == LINE_SMOOTH);
+		line.draw();
+	}
 }
 
 void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h)

File src/modules/graphics/opengl/Graphics.h

View file
  • Ignore whitespace
 
 	// Line.
 	Graphics::LineStyle lineStyle;
+	Graphics::LineJoin lineJoin;
 
 	// Point.
 	float pointSize;
 		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;
 	void setLineStyle(LineStyle style);
 
 	/**
+	 * Sets the line style.
+	 * @param style LINE_ROUGH or LINE_SMOOTH.
+	 **/
+	void setLineJoin(LineJoin style);
+
+	/**
 	 * Gets the line width.
 	 **/
 	float getLineWidth() const;
 	LineStyle getLineStyle() const;
 
 	/**
+	 * Gets the line style.
+	 **/
+	LineJoin getLineJoin() const;
+
+	/**
 	 * Sets the size of points.
 	 **/
 	void setPointSize(float size);
 
 	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;

File src/modules/graphics/opengl/wrap_Graphics.cpp

View file
  • Ignore whitespace
 	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());
 	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);
 	{ "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 },

File src/modules/graphics/opengl/wrap_Graphics.h

View file
  • Ignore whitespace
 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);