Commits

vrld committed f348b2d

Add missing Polyline.{h,cpp}

Comments (0)

Files changed (2)

src/modules/graphics/opengl/Polyline.cpp

+/**
+ * Copyright (c) 2006-2013 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#include <iostream>
+
+// LOVE
+#include "Polyline.h"
+
+// OpenGL
+#include "OpenGL.h"
+
+// treat adjacent segments with angles between their normals <7 degree as straight
+static const float LINES_PARALLEL_EPS = 0.15;
+
+namespace love
+{
+namespace graphics
+{
+namespace opengl
+{
+
+void Polyline::render(const float *coords, size_t count, size_t size_hint, float halfwidth, float pixel_size, bool draw_overdraw)
+{
+	static std::vector<Vector> anchors;
+	anchors.clear();
+	anchors.reserve(size_hint);
+
+	static std::vector<Vector> normals;
+	normals.clear();
+	normals.reserve(size_hint);
+
+	// prepare vertex arrays
+	if (draw_overdraw)
+		halfwidth -= pixel_size * .3;
+
+	// compute sleeve
+	bool is_looping = (coords[0] == coords[count - 2]) && (coords[1] == coords[count - 1]);
+	Vector s;
+	if (!is_looping) // virtual starting point at second point mirrored on first point
+		s = Vector(coords[2] - coords[0], coords[3] - coords[1]);
+	else // virtual starting point at last vertex
+		s = Vector(coords[0] - coords[count - 4], coords[1] - coords[count - 3]);
+
+	float len_s = s.getLength();
+	Vector ns = s.getNormal(halfwidth / len_s);
+
+	Vector q, r(coords[0], coords[1]);
+	for (size_t i = 0; i + 3 < count; i += 2)
+	{
+		q = r;
+		r = Vector(coords[i + 2], coords[i + 3]);
+		renderEdge(anchors, normals, s, len_s, ns, q, r, halfwidth);
+	}
+
+	q = r;
+	r = is_looping ? Vector(coords[2], coords[3]) : r + s;
+	renderEdge(anchors, normals, s, len_s, ns, q, r, halfwidth);
+
+	vertex_count = normals.size();
+	vertices = new Vector[vertex_count];
+	for (size_t i = 0; i < vertex_count; ++i)
+		vertices[i] = anchors[i] + normals[i];
+
+	if (draw_overdraw)
+		render_overdraw(normals, pixel_size, is_looping);
+}
+
+void NoneJoinPolyline::renderEdge(std::vector<Vector> &anchors, std::vector<Vector> &normals,
+                                Vector &s, float &len_s, Vector &ns,
+                                const Vector &q, const Vector &r, float hw)
+{
+	anchors.push_back(q);
+	anchors.push_back(q);
+	normals.push_back(ns);
+	normals.push_back(-ns);
+
+	s     = (r - q);
+	len_s = s.getLength();
+	ns    = s.getNormal(hw / len_s);
+
+	anchors.push_back(q);
+	anchors.push_back(q);
+	normals.push_back(-ns);
+	normals.push_back(ns);
+}
+
+
+/** Calculate line boundary points.
+ *
+ * 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 * ns) + lambda * (q - p) = (q + w/2 * nt) + mu * (r - q)   (u1)
+ *    (p - w/2 * ns) + lambda * (q - p) = (q - w/2 * nt) + mu * (r - q)   (u2)
+ *
+ * with nt,nt being the normals on the segments s = p-q and t = q-r,
+ *
+ *    ns = perp(s) / |s|
+ *    nt = perp(t) / |s|.
+ *
+ * Using the linear equation system (similar for u2)
+ *
+ *         p + w/2 * ns + lambda * s - (q + w/2 * nt + mu * t) = 0                 (u1)
+ *    <=> (p-q) + lambda * s - mu * t                          = (nt - ns) * w/2
+ *    <=> (lambda - 1) * s   - mu * t                          = (nt - ns) * w/2
+ *
+ * the intersection points can be efficiently calculated using Cramer's rule.
+ */
+void MiterJoinPolyline::renderEdge(std::vector<Vector> &anchors, std::vector<Vector> &normals,
+                                   Vector &s, float &len_s, Vector &ns,
+                                   const Vector &q, const Vector &r, float hw)
+{
+	Vector t    = (r - q);
+	float len_t = t.getLength();
+	Vector nt   = t.getNormal(hw / len_t);
+
+	anchors.push_back(q);
+	anchors.push_back(q);
+
+	float det = s ^ t;
+	if (fabs(det) / (len_s * len_t) < LINES_PARALLEL_EPS)
+	{
+		// lines parallel, compute as u1 = q + ns * w/2, u2 = q - ns * w/2
+		normals.push_back(ns);
+		normals.push_back(-ns);
+	}
+	else
+	{
+		// cramers rule
+		float lambda = ((nt - ns) ^ t) / det;
+		Vector d = ns + s * lambda;
+		normals.push_back(d);
+		normals.push_back(-d);
+	}
+
+	s     = t;
+	ns    = nt;
+	len_s = len_t;
+}
+
+/** Calculate line boundary points.
+ *
+ * Sketch:
+ *
+ *     uh1___uh2
+ *      .'   '.
+ *    .'   q   '.
+ *  .'   '   '   '.
+ *.'   '  .'.  '   '.
+ *   '  .' ul'.  '
+ * p  .'       '.  r
+ *
+ *
+ * ul can be found as above, uh1 and uh2 are much simpler:
+ *
+ * uh1 = q + ns * w/2, uh2 = q + nt * w/2
+ */
+void BevelJoinPolyline::renderEdge(std::vector<Vector> &anchors, std::vector<Vector> &normals,
+                                   Vector &s, float &len_s, Vector &ns,
+                                   const Vector &q, const Vector &r, float hw)
+{
+	Vector t    = (r - q);
+	float len_t = t.getLength();
+
+	float det = s ^ t;
+	if (fabs(det) / (len_s * len_t) < LINES_PARALLEL_EPS)
+	{
+		// lines parallel, compute as u1 = q + ns * w/2, u2 = q - ns * w/2
+		Vector n = t.getNormal(hw / len_t);
+		anchors.push_back(q);
+		anchors.push_back(q);
+		normals.push_back(n);
+		normals.push_back(-n);
+		s     = t;
+		len_s = len_t;
+		return; // early out
+	}
+
+	// cramers rule
+	Vector nt= t.getNormal(hw / len_t);
+	float lambda = ((nt - ns) ^ t) / det;
+	Vector d = ns + s * lambda;
+
+	anchors.push_back(q);
+	anchors.push_back(q);
+	anchors.push_back(q);
+	anchors.push_back(q);
+	if (det > 0) // 'left' turn -> intersection on the top
+	{
+		normals.push_back(d);
+		normals.push_back(-ns);
+		normals.push_back(d);
+		normals.push_back(-nt);
+	}
+	else
+	{
+		normals.push_back(ns);
+		normals.push_back(-d);
+		normals.push_back(nt);
+		normals.push_back(-d);
+	}
+	s     = t;
+	len_s = len_t;
+	ns    = nt;
+}
+
+void Polyline::render_overdraw(const std::vector<Vector> &normals, float pixel_size, bool is_looping)
+{
+	overdraw_vertex_count = 2 * vertex_count + (is_looping ? 0 : 2);
+	overdraw = new Vector[overdraw_vertex_count];
+	// upper segment
+	for (size_t i = 0; i + 1 < vertex_count; i += 2)
+	{
+		overdraw[i]   = vertices[i];
+		overdraw[i+1] = vertices[i] + normals[i] * (pixel_size / normals[i].getLength());
+	}
+	// lower segment
+	for (size_t i = 0; i + 1 < vertex_count; i += 2)
+	{
+		size_t k = vertex_count - i - 1;
+		overdraw[vertex_count + i]   = vertices[k];
+		overdraw[vertex_count + i+1] = vertices[k] + normals[k] * (pixel_size / normals[i].getLength());
+	}
+
+	// if not looping, the outer overdraw vertices need to be displaced
+	// to cover the line endings, i.e.:
+	// +- - - - //- - +         +- - - - - //- - - +
+	// +-------//-----+         : +-------//-----+ :
+	// | core // line |   -->   : | core // line | :
+	// +-----//-------+         : +-----//-------+ :
+	// +- - //- - - - +         +- - - //- - - - - +
+	if (!is_looping)
+	{
+		// left edge
+		Vector spacer = (overdraw[1] - overdraw[3]);
+		spacer.normalize(pixel_size);
+		overdraw[1] += spacer;
+		overdraw[overdraw_vertex_count - 3] += spacer;
+
+		// right edge
+		spacer = (overdraw[vertex_count-1] - overdraw[vertex_count-3]);
+		spacer.normalize(pixel_size);
+		overdraw[vertex_count-1] += spacer;
+		overdraw[vertex_count+1] += spacer;
+
+		// we need to draw two more triangles to close the
+		// overdraw at the line start.
+		overdraw[overdraw_vertex_count-2] = overdraw[0];
+		overdraw[overdraw_vertex_count-1] = overdraw[1];
+	}
+}
+
+void NoneJoinPolyline::render_overdraw(const std::vector<Vector> &normals, float pixel_size, bool is_looping)
+{
+	(void)is_looping;
+
+	overdraw_vertex_count = 4 * (vertex_count-2); // less than ideal
+	overdraw = new Vector[overdraw_vertex_count];
+	for (size_t i = 2; i + 3 < vertex_count; i += 4)
+	{
+		Vector s = vertices[i] - vertices[i+3];
+		Vector t = vertices[i] - vertices[i+1];
+		s.normalize(pixel_size);
+		t.normalize(pixel_size);
+
+		const size_t k = 4 * (i - 2);
+		overdraw[k  ] = vertices[i];
+		overdraw[k+1] = vertices[i]   + s + t;
+		overdraw[k+2] = vertices[i+1] + s - t;
+		overdraw[k+3] = vertices[i+1];
+
+		overdraw[k+4] = vertices[i+1];
+		overdraw[k+5] = vertices[i+1] + s - t;
+		overdraw[k+6] = vertices[i+2] - s - t;
+		overdraw[k+7] = vertices[i+2];
+
+		overdraw[k+8]  = vertices[i+2];
+		overdraw[k+9]  = vertices[i+2] - s - t;
+		overdraw[k+10] = vertices[i+3] - s + t;
+		overdraw[k+11] = vertices[i+3];
+
+		overdraw[k+12] = vertices[i+3];
+		overdraw[k+13] = vertices[i+3] - s + t;
+		overdraw[k+14] = vertices[i]   + s + t;
+		overdraw[k+15] = vertices[i];
+	}
+}
+
+Polyline::~Polyline()
+{
+	if (vertices)
+		delete[] vertices;
+	if (overdraw)
+		delete[] overdraw;
+}
+
+void Polyline::draw()
+{
+	// draw the core line
+	gl.bindTexture(0);
+	glEnableClientState(GL_VERTEX_ARRAY);
+	glVertexPointer(2, GL_FLOAT, 0, (const GLvoid *)vertices);
+	glDrawArrays(draw_mode, 0, vertex_count);
+
+	if (overdraw)
+	{
+		// 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[overdraw_vertex_count];
+		fill_color_array(colors, c);
+
+		glEnableClientState(GL_COLOR_ARRAY);
+		glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);
+		glVertexPointer(2, GL_FLOAT, 0, (const GLvoid *)overdraw);
+		glDrawArrays(draw_mode, 0, overdraw_vertex_count);
+		glDisableClientState(GL_COLOR_ARRAY);
+
+		delete[] colors;
+
+		gl.setColor(c);
+	}
+
+	glDisableClientState(GL_VERTEX_ARRAY);
+}
+
+void Polyline::fill_color_array(Color *colors, const Color &c)
+{
+	for (size_t i = 0; i < overdraw_vertex_count; ++i)
+	{
+		colors[i] = c;
+		// avoids branching. equiv to if (i%2 == 1) colors[i].a = 0;
+		colors[i].a *= GLubyte((i+1) % 2);
+	}
+}
+
+void NoneJoinPolyline::fill_color_array(Color *colors, const Color &c)
+{
+	for (size_t i = 0; i < overdraw_vertex_count; ++i)
+	{
+		colors[i] = c;
+		// if (i % 4 == 1 || i % 4 == 2) colors[i].a = 0
+		size_t k = i % 4;
+		colors[i].a *= GLubyte(k != 1 && k != 2);
+	}
+}
+
+} // opengl
+} // graphics
+} // love

src/modules/graphics/opengl/Polyline.h

+/**
+ * Copyright (c) 2006-2013 LOVE Development Team
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty.  In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ *    claim that you wrote the original software. If you use this software
+ *    in a product, an acknowledgment in the product documentation would be
+ *    appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ *    misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ **/
+
+#ifndef LOVE_GRAPHICS_OPENGL_POLYLINE_H
+#define LOVE_GRAPHICS_OPENGL_POLYLINE_H
+
+#include <vector>
+
+// LOVE
+#include "common/Vector.h"
+
+// OpenGL
+#include "OpenGL.h"
+
+namespace love
+{
+namespace graphics
+{
+namespace opengl
+{
+
+/**
+ * Abstract base class for a chain of segments.
+ * @author Matthias Richter
+ **/
+class Polyline
+{
+public:
+	Polyline(GLenum mode = GL_TRIANGLE_STRIP)
+		: vertices(NULL)
+		, overdraw(NULL)
+		, vertex_count(0)
+		, overdraw_vertex_count(0)
+		, draw_mode(mode)
+	{}
+	virtual ~Polyline();
+
+	/**
+	 * @param vertices      Vertices defining the core line segments
+	 * @param count         Number of coordinates (= size of the array vertices)
+	 * @param size_hint     Expected number of vertices of the rendering sleeve around the core line.
+	 * @param halfwidth     linewidth / 2.
+	 * @param pixel_size    Dimension of one pixel on the screen in world coordinates.
+	 * @param draw_overdraw Fake antialias the line.
+	 */
+	void render(const float *vertices, size_t count, size_t size_hint, float halfwidth, float pixel_size, bool draw_overdraw);
+
+	/** Draws the line on the screen
+	 */
+	void draw();
+
+protected:
+	virtual void render_overdraw(const std::vector<Vector> &normals, float pixel_size, bool is_looping);
+	virtual void fill_color_array(Color *colors, const Color &c);
+
+	/** Calculate line boundary points.
+	 *
+	 * @param[out]    anchors Anchor points defining the core line.
+	 * @param[out]    normals Normals defining the edge of the sleeve.
+	 * @param[in,out] s       Direction of segment pq (updated to the segment qr).
+	 * @param[in,out] len_s   Length of segment pq (updated to the segment qr).
+	 * @param[in,out] ns      Normal on the segment pq (updated to the segment qr).
+	 * @param[in]     q       Current point on the line.
+	 * @param[in]     r       Next point on the line.
+	 * @param[in]     hw      Half line width (see Polyline.render()).
+	 */
+	virtual void renderEdge(std::vector<Vector> &anchors, std::vector<Vector> &normals,
+	                        Vector &s, float &len_s, Vector &ns,
+	                        const Vector &q, const Vector &r, float hw) = 0;
+
+	Vector *vertices;
+	Vector *overdraw;
+	size_t vertex_count;
+	size_t overdraw_vertex_count;
+	GLenum draw_mode;
+
+}; // Polyline
+
+
+/**
+ * A Polyline whose segments are not connected.
+ * @author Matthias Richter
+ */
+class NoneJoinPolyline : public Polyline
+{
+public:
+	NoneJoinPolyline()
+		: Polyline(GL_QUADS)
+	{}
+
+	void render(const float *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
+	{
+		Polyline::render(vertices, count, 2 * count - 4, halfwidth, pixel_size, draw_overdraw);
+		// discard the first and last two vertices. (these are redundant)
+		for (size_t i = 0; i < vertex_count - 2; ++i)
+			this->vertices[i] = this->vertices[i+2];
+		vertex_count -= 2;
+	}
+
+protected:
+	virtual void render_overdraw(const std::vector<Vector> &normals, float pixel_size, bool is_looping);
+	virtual void fill_color_array(Color *colors, const Color &c);
+	virtual void renderEdge(std::vector<Vector> &anchors, std::vector<Vector> &normals,
+	                        Vector &s, float &len_s, Vector &ns,
+	                        const Vector &q, const Vector &r, float hw);
+};
+
+
+/**
+ * A Polyline whose segments are connected by a sharp edge.
+ * @author Matthias Richter
+ */
+class MiterJoinPolyline : public Polyline
+{
+public:
+	void render(const float *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
+	{
+		Polyline::render(vertices, count, count, halfwidth, pixel_size, draw_overdraw);
+	}
+
+protected:
+	virtual void renderEdge(std::vector<Vector> &anchors, std::vector<Vector> &normals,
+	                        Vector &s, float &len_s, Vector &ns,
+	                        const Vector &q, const Vector &r, float hw);
+};
+
+
+/**
+ * A Polyline whose segments are connected by a flat edge.
+ * @author Matthias Richter
+ */
+class BevelJoinPolyline : public Polyline
+{
+public:
+	void render(const float *vertices, size_t count, float halfwidth, float pixel_size, bool draw_overdraw)
+	{
+		Polyline::render(vertices, count, 2 * count - 4, halfwidth, pixel_size, draw_overdraw);
+	}
+
+protected:
+	virtual void renderEdge(std::vector<Vector> &anchors, std::vector<Vector> &normals,
+	                        Vector &s, float &len_s, Vector &ns,
+	                        const Vector &q, const Vector &r, float hw);
+};
+
+} // opengl
+} // graphics
+} // love
+
+#endif // LOVE_GRAPHICS_OPENGL_POLYLINE_H