Add missing Polyline.{h,cpp}

This commit is contained in:
vrld
2013-08-08 08:11:18 +02:00
parent be205544b3
commit 0dea115abe
2 changed files with 551 additions and 0 deletions
+384
View File
@@ -0,0 +1,384 @@
/**
* 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
+167
View File
@@ -0,0 +1,167 @@
/**
* 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