Browse Source

Fix issue 246: Add line antialiasing through 'overdraw'.

Points to consider:
- Make softness customizable (`halo' in Graphics::polyline()).
- Add overdraw at beginning and ending edge of the line.
vrld 13 years ago
parent
commit
247f57c10b

+ 134 - 53
src/modules/graphics/opengl/Graphics.cpp

@@ -38,7 +38,7 @@ namespace opengl
 {
 {
 
 
 	Graphics::Graphics()
 	Graphics::Graphics()
-		: currentFont(0), currentImageFilter(), lineWidth(1), matrixLimit(0), userMatrices(0)
+		: currentFont(0), currentImageFilter(), lineStyle(LINE_SMOOTH), lineWidth(1), matrixLimit(0), userMatrices(0)
 	{
 	{
 		currentWindow = love::window::sdl::Window::getSingleton();
 		currentWindow = love::window::sdl::Window::getSingleton();
 	}
 	}
@@ -78,8 +78,7 @@ namespace opengl
 		glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &mode);
 		glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &mode);
 		s.colorMode = (mode == GL_MODULATE) ? Graphics::COLOR_MODULATE : Graphics::COLOR_REPLACE;
 		s.colorMode = (mode == GL_MODULATE) ? Graphics::COLOR_MODULATE : Graphics::COLOR_REPLACE;
 		//get line style
 		//get line style
-		//s.lineStyle = (glIsEnabled(GL_POLYGON_SMOOTH) == GL_TRUE) ? Graphics::LINE_SMOOTH : Graphics::LINE_ROUGH;
-		s.lineStyle = Graphics::LINE_ROUGH;
+		s.lineStyle = lineStyle;
 		//get the point size
 		//get the point size
 		glGetFloatv(GL_POINT_SIZE, &s.pointSize);
 		glGetFloatv(GL_POINT_SIZE, &s.pointSize);
 		//get point style
 		//get point style
@@ -587,18 +586,9 @@ namespace opengl
 		lineWidth = width;
 		lineWidth = width;
 	}
 	}
 
 
-	void Graphics::setLineStyle(Graphics::LineStyle/* style*/ )
+	void Graphics::setLineStyle(Graphics::LineStyle style)
 	{
 	{
-		//// XXX: actually enables antialiasing for _all_ polygons.
-		//// may need investigation if wanted or not
-		//// maybe rename to something else?
-		//if (style == LINE_ROUGH)
-		//	glDisable (GL_POLYGON_SMOOTH);
-		//else // type == LINE_SMOOTH
-		//{
-		//	glEnable (GL_POLYGON_SMOOTH);
-		//	glHint (GL_POLYGON_SMOOTH_HINT, GL_NICEST);
-		//}
+		lineStyle = style;
 	}
 	}
 
 
 	void Graphics::setLine( float width, Graphics::LineStyle style )
 	void Graphics::setLine( float width, Graphics::LineStyle style )
@@ -619,10 +609,7 @@ namespace opengl
 
 
 	Graphics::LineStyle Graphics::getLineStyle()
 	Graphics::LineStyle Graphics::getLineStyle()
 	{
 	{
-		//if (glIsEnabled(GL_POLYGON_SMOOTH) == GL_TRUE)
-		//	return LINE_SMOOTH;
-		//else
-			return LINE_ROUGH;
+		return lineStyle;
 	}
 	}
 
 
 	void Graphics::setPointSize( float size )
 	void Graphics::setPointSize( float size )
@@ -722,75 +709,167 @@ namespace opengl
 		glEnable(GL_TEXTURE_2D);
 		glEnable(GL_TEXTURE_2D);
 	}
 	}
 
 
-	// calculate line boundary intersection vertices for current line
-	// dependent on the current *and next* line segment
-	static void pushIntersectionPoints(Vector *vertices, int pos, float halfwidth, const Vector& p, const Vector& q, const Vector& r)
+	// 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, int pos, float halfwidth, const Vector& p, const Vector& q, const Vector& r,
+			Vector* overdraw_top, Vector* overdraw_bottom, float halo)
 	{
 	{
 		// calculate line directions
 		// calculate line directions
 		Vector s = (q - p);
 		Vector s = (q - p);
 		Vector t = (r - q);
 		Vector t = (r - q);
 
 
 		// calculate vertex displacement vectors
 		// calculate vertex displacement vectors
-		Vector d1 = s.getNormal();
-		Vector d2 = t.getNormal();
-		d1.normalize();
-		d2.normalize();
-		float det_norm = d1 ^ d2;
-		d1 *= halfwidth;
-		d2 *= halfwidth;
+		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 *= halfwidth;
+		n2 *= halfwidth;
 
 
 		// lines parallel -> assume intersection at displacement points
 		// lines parallel -> assume intersection at displacement points
 		if (fabs(det_norm) <= .03)
 		if (fabs(det_norm) <= .03)
 		{
 		{
-			vertices[pos]     = q - d2;
-			vertices[pos + 1] = q + d2;
-			return;
+			vertices[pos]     = q - n2;
+			vertices[pos + 1] = q + n2;
+		}
+		else // real intersection -> calculate boundary intersection points with cramers rule
+		{
+			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
 		}
 		}
 
 
-		// real intersection -> calculate boundary intersection points
-		float det = s ^ t;
-		Vector d = d1 - d2;
-		Vector b = s - d; // s = q - p
-		Vector c = s + d;
-		float lambda = (b ^ t) / det;
-		float mu     = (c ^ t) / det;
+		if (overdraw_top && overdraw_bottom)
+		{
+			overdraw_top[pos]   = vertices[pos];
+			overdraw_top[pos+1] = vertices[pos] * halo + q * (1. - halo);
 
 
-		// ordering for GL_TRIANGLE_STRIP
-		vertices[pos]   = p - d1 + s * mu;
-		vertices[pos+1] = p + d1 + s * lambda;
+			overdraw_bottom[pos]   = vertices[pos+1];
+			overdraw_bottom[pos+1] = vertices[pos+1] * halo + q * (1. - halo);
+		}
 	}
 	}
 
 
-	void Graphics::polyline(const float* coords, size_t count, bool looping)
+	void Graphics::polyline(const float* coords, size_t count)
 	{
 	{
 		Vector *vertices = new Vector[count]; // two vertices for every line end-point
 		Vector *vertices = new Vector[count]; // two vertices for every line end-point
+		Vector *overdraw_top = NULL;
+		Vector *overdraw_bottom = NULL;
+
 		Vector p,q,r;
 		Vector p,q,r;
+		bool looping = (coords[0] == coords[count-2]) && (coords[1] == coords[count-1]);
 
 
+		if (lineStyle == LINE_SMOOTH)
+		{
+			overdraw_top = new Vector[count];
+			overdraw_bottom = new Vector[count];
+		}
+
+		// get line vertex boundaries
+		// if not looping, extend the line at the beginning, else use last point as `p'
+		float halfwidth = lineWidth/2.0f;
+		float halo = 1. + 1./halfwidth; // XXX: customizable?
 		r = Vector(coords[0], coords[1]);
 		r = Vector(coords[0], coords[1]);
-		if (looping) q = Vector(coords[count-4], coords[count-3]);
-		else         q = r * 2 - Vector(coords[2], coords[3]);
+		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)
 		for (size_t i = 0; i+3 < count; i += 2)
 		{
 		{
-			p = q;
-			q = r;
+			p = q; q = r;
 			r = Vector(coords[i+2], coords[i+3]);
 			r = Vector(coords[i+2], coords[i+3]);
-			pushIntersectionPoints(vertices, i, lineWidth/2, p,q,r);
+			pushIntersectionPoints(vertices, i, halfwidth, p,q,r, overdraw_top, overdraw_bottom, halo);
 		}
 		}
 
 
-		p = q;
-		q = r;
-		if (looping) r = Vector(coords[2], coords[3]);
-		else         r += (q-p);
-		pushIntersectionPoints(vertices, count-2, lineWidth/2, p,q,r);
+		// 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, count-2, halfwidth, p,q,r, overdraw_top, overdraw_bottom, halo);
+		// end get line vertex boundaries
 
 
+		// draw the core line
 		glDisable(GL_TEXTURE_2D);
 		glDisable(GL_TEXTURE_2D);
 		glEnableClientState(GL_VERTEX_ARRAY);
 		glEnableClientState(GL_VERTEX_ARRAY);
 		glVertexPointer(2, GL_FLOAT, 0, (const GLvoid*)vertices);
 		glVertexPointer(2, GL_FLOAT, 0, (const GLvoid*)vertices);
 		glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
 		glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
+
+		// draw the line halo (antialiasing)
+		if (lineStyle == LINE_SMOOTH)
+		{
+			// prepare colors:
+			// even indices in overdraw* point to inner vertices => alpha = current-alpha,
+			// odd indices point to outer vertices => alpha = 0.
+			Colorf tmp;
+			glGetFloatv(GL_CURRENT_COLOR, (GLfloat*)(&tmp));
+			Color color(tmp.r * 255.0f, tmp.g * 255.0f, tmp.b * 255.0f, tmp.a * 255.0f);
+
+			Color *colors = new Color[count];
+			for (int i = 0; i < count; ++i)
+			{
+				colors[i] = color;
+				colors[i].a *= int(i%2 == 0); // avoids branching. equiv to colors[i].a *= (i%2==0) ? 1 : 0;
+			}
+
+			// TODO: overdraw at line start and end
+
+			// draw faded out line halos
+			glEnableClientState(GL_COLOR_ARRAY);
+			glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);
+			glVertexPointer(2, GL_FLOAT, 0, (const GLvoid*)overdraw_top);
+			glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
+			glVertexPointer(2, GL_FLOAT, 0, (const GLvoid*)overdraw_bottom);
+			glDrawArrays(GL_TRIANGLE_STRIP, 0, count);
+			glDisableClientState(GL_COLOR_ARRAY);
+
+			delete[] colors;
+		}
+
 		glDisableClientState(GL_VERTEX_ARRAY);
 		glDisableClientState(GL_VERTEX_ARRAY);
 		glEnable(GL_TEXTURE_2D);
 		glEnable(GL_TEXTURE_2D);
 
 
+		// cleanup
 		delete[] vertices;
 		delete[] vertices;
+		if (lineStyle == LINE_SMOOTH)
+		{
+			delete[] overdraw_top;
+			delete[] overdraw_bottom;
+		}
 	}
 	}
 
 
 	void Graphics::triangle(DrawMode mode, float x1, float y1, float x2, float y2, float x3, float y3 )
 	void Graphics::triangle(DrawMode mode, float x1, float y1, float x2, float y2, float x3, float y3 )
@@ -864,7 +943,9 @@ namespace opengl
 
 
 		// GL_POLYGON can only fill-draw convex polygons, so we need to do stuff manually here
 		// GL_POLYGON can only fill-draw convex polygons, so we need to do stuff manually here
 		if (mode == DRAW_LINE)
 		if (mode == DRAW_LINE)
+		{
 			polyline(coords, num_coords); // Artifacts at sharp angles if set to looping.
 			polyline(coords, num_coords); // Artifacts at sharp angles if set to looping.
+		}
 		else
 		else
 		{
 		{
 			glDisable(GL_TEXTURE_2D);
 			glDisable(GL_TEXTURE_2D);
@@ -887,7 +968,7 @@ namespace opengl
 		// coords[count-2] = coords[0], coords[count-1] = coords[1]
 		// coords[count-2] = coords[0], coords[count-1] = coords[1]
 		if (mode == DRAW_LINE)
 		if (mode == DRAW_LINE)
 		{
 		{
-			polyline(coords, count, true);
+			polyline(coords, count);
 		}
 		}
 		else
 		else
 		{
 		{

+ 4 - 4
src/modules/graphics/opengl/Graphics.h

@@ -108,6 +108,7 @@ namespace opengl
 		Image::Filter currentImageFilter;
 		Image::Filter currentImageFilter;
 		love::window::Window *currentWindow;
 		love::window::Window *currentWindow;
 
 
+		LineStyle lineStyle;
 		float lineWidth;
 		float lineWidth;
 		GLint matrixLimit;
 		GLint matrixLimit;
 		GLint userMatrices;
 		GLint userMatrices;
@@ -440,11 +441,10 @@ namespace opengl
 
 
 		/**
 		/**
 		* Draws a series of lines connecting the given vertices.
 		* Draws a series of lines connecting the given vertices.
-		* @param coords Vertex components (x1, y1, x2, y2, etc.)
-		* @param count Number of coordinates in the array.
-		* @param looping Whether the line joins up with itself.
+		* @param coords Vertex components (x1, y1, ..., xn, yn). If x1,y1 == xn,yn the line will be drawn closed.
+		* @param count Number of items in the array, i.e. count = 2 * n
 		**/
 		**/
-		void polyline(const float* coords, size_t count, bool looping = false);
+		void polyline(const float* coords, size_t count);
 
 
 		/**
 		/**
 		* Draws a triangle using the three coordinates passed.
 		* Draws a triangle using the three coordinates passed.

+ 0 - 2
src/modules/graphics/opengl/wrap_Graphics.h

@@ -78,10 +78,8 @@ namespace opengl
 	int w_setLineWidth(lua_State * L);
 	int w_setLineWidth(lua_State * L);
 	int w_setLineStyle(lua_State * L);
 	int w_setLineStyle(lua_State * L);
 	int w_setLine(lua_State * L);
 	int w_setLine(lua_State * L);
-	int w_setLineStipple(lua_State * L);
 	int w_getLineWidth(lua_State * L);
 	int w_getLineWidth(lua_State * L);
 	int w_getLineStyle(lua_State * L);
 	int w_getLineStyle(lua_State * L);
-	int w_getLineStipple(lua_State * L);
 	int w_setPointSize(lua_State * L);
 	int w_setPointSize(lua_State * L);
 	int w_setPointStyle(lua_State * L);
 	int w_setPointStyle(lua_State * L);
 	int w_setPoint(lua_State * L);
 	int w_setPoint(lua_State * L);