123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- /**
- * Copyright (c) 2006-2023 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.
- **/
- // LOVE
- #include "Polyline.h"
- #include "graphics/Graphics.h"
- // C++
- #include <algorithm>
- // treat adjacent segments with angles between their directions <5 degree as straight
- static const float LINES_PARALLEL_EPS = 0.05f;
- namespace love
- {
- namespace graphics
- {
- void Polyline::render(const Vector2 *coords, size_t count, size_t size_hint, float halfwidth, float pixel_size, bool draw_overdraw)
- {
- static std::vector<Vector2> anchors;
- anchors.clear();
- anchors.reserve(size_hint);
- static std::vector<Vector2> normals;
- normals.clear();
- normals.reserve(size_hint);
- // prepare vertex arrays
- if (draw_overdraw)
- halfwidth -= pixel_size * 0.3f;
- // compute sleeve
- bool is_looping = (coords[0] == coords[count - 1]);
- Vector2 segment;
- if (!is_looping) // virtual starting point at second point mirrored on first point
- segment = coords[1] - coords[0];
- else // virtual starting point at last vertex
- segment = coords[0] - coords[count - 2];
- float segmentLength = segment.getLength();
- Vector2 segmentNormal = segment.getNormal(halfwidth / segmentLength);
- Vector2 pointA, pointB(coords[0]);
- for (size_t i = 0; i + 1 < count; i++)
- {
- pointA = pointB;
- pointB = coords[i + 1];
- renderEdge(anchors, normals, segment, segmentLength, segmentNormal, pointA, pointB, halfwidth);
- }
- pointA = pointB;
- pointB = is_looping ? coords[1] : pointB + segment;
- renderEdge(anchors, normals, segment, segmentLength, segmentNormal, pointA, pointB, halfwidth);
- vertex_count = normals.size();
- size_t extra_vertices = 0;
- if (draw_overdraw)
- {
- calc_overdraw_vertex_count(is_looping);
- // When drawing overdraw lines using triangle strips, we want to add an
- // extra degenerate triangle in between the core line and the overdraw
- // line in order to break up the strip into two. This will let us draw
- // everything in one draw call.
- if (triangle_mode == vertex::TriangleIndexMode::STRIP)
- extra_vertices = 2;
- }
- // Use a single linear array for both the regular and overdraw vertices.
- vertices = new Vector2[vertex_count + extra_vertices + overdraw_vertex_count];
- for (size_t i = 0; i < vertex_count; ++i)
- vertices[i] = anchors[i] + normals[i];
- if (draw_overdraw)
- {
- overdraw = vertices + vertex_count + extra_vertices;
- overdraw_vertex_start = vertex_count + extra_vertices;
- render_overdraw(normals, pixel_size, is_looping);
- }
- // Add the degenerate triangle strip.
- if (extra_vertices)
- {
- vertices[vertex_count + 0] = vertices[vertex_count - 1];
- vertices[vertex_count + 1] = vertices[overdraw_vertex_start];
- }
- }
- void NoneJoinPolyline::renderEdge(std::vector<Vector2> &anchors, std::vector<Vector2> &normals,
- Vector2 &segment, float &segmentLength, Vector2 &segmentNormal,
- const Vector2 &pointA, const Vector2 &pointB, float halfWidth)
- {
- // ns1------ns2
- // | |
- // q ------ r
- // | |
- // (-ns1)----(-ns2)
- anchors.push_back(pointA);
- anchors.push_back(pointA);
- normals.push_back(segmentNormal);
- normals.push_back(-segmentNormal);
- segment = (pointB - pointA);
- segmentLength = segment.getLength();
- segmentNormal = segment.getNormal(halfWidth / segmentLength);
- anchors.push_back(pointA);
- anchors.push_back(pointA);
- normals.push_back(segmentNormal);
- normals.push_back(-segmentNormal);
- }
- /** 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
- *
- * (q + w/2 * ns) + lambda * (q - p) = (q + w/2 * nt) + mu * (r - q) (u1)
- * (q - 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) / |t|.
- *
- * Using the linear equation system (similar for u2)
- *
- * q + w/2 * ns + lambda * s - (q + w/2 * nt + mu * t) = 0 (u1)
- * <=> q-q + lambda * s - mu * t = (nt - ns) * w/2
- * <=> lambda * s - mu * t = (nt - ns) * w/2
- *
- * the intersection points can be efficiently calculated using Cramer's rule.
- */
- void MiterJoinPolyline::renderEdge(std::vector<Vector2> &anchors, std::vector<Vector2> &normals,
- Vector2 &segment, float &segmentLength, Vector2 &segmentNormal,
- const Vector2 &pointA, const Vector2 &pointB, float halfwidth)
- {
- Vector2 newSegment = (pointB - pointA);
- float newSegmentLength = newSegment.getLength();
- if (newSegmentLength == 0.0f)
- {
- // degenerate segment, skip it
- return;
- }
- Vector2 newSegmentNormal = newSegment.getNormal(halfwidth / newSegmentLength);
- anchors.push_back(pointA);
- anchors.push_back(pointA);
- float det = Vector2::cross(segment, newSegment);
- if (fabs(det) / (segmentLength * newSegmentLength) < LINES_PARALLEL_EPS)
- {
- // lines parallel, compute as u1 = q + ns * w/2, u2 = q - ns * w/2
- normals.push_back(segmentNormal);
- normals.push_back(-segmentNormal);
- if (Vector2::dot(segment, newSegment) < 0)
- {
- // line reverses direction; because the normal flips, the
- // triangle strip would twist here, so insert a zero-size
- // quad to contain the twist
- // ____.___.____
- // | |\ /| |
- // p q X q r
- // |____|/ \|____|
- anchors.push_back(pointA);
- anchors.push_back(pointA);
- normals.push_back(-segmentNormal);
- normals.push_back(segmentNormal);
- }
- }
- else
- {
- // cramers rule
- float lambda = Vector2::cross((newSegmentNormal - segmentNormal), newSegment) / det;
- Vector2 d = segmentNormal + segment * lambda;
- normals.push_back(d);
- normals.push_back(-d);
- }
- segment = newSegment;
- segmentNormal = newSegmentNormal;
- segmentLength = newSegmentLength;
- }
- /** 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<Vector2> &anchors, std::vector<Vector2> &normals,
- Vector2 &segment, float &segmentLength, Vector2 &segmentNormal,
- const Vector2 &pointA, const Vector2 &pointB, float halfWidth)
- {
- Vector2 newSegment = (pointB - pointA);
- float newSegmentLength = newSegment.getLength();
- float det = Vector2::cross(segment, newSegment);
- if (fabs(det) / (segmentLength * newSegmentLength) < LINES_PARALLEL_EPS)
- {
- // lines parallel, compute as u1 = q + ns * w/2, u2 = q - ns * w/2
- Vector2 newSegmentNormal = newSegment.getNormal(halfWidth / newSegmentLength);
- anchors.push_back(pointA);
- anchors.push_back(pointA);
- normals.push_back(segmentNormal);
- normals.push_back(-segmentNormal);
- if (Vector2::dot(segment, newSegment) < 0)
- {
- // line reverses direction; same as for miter
- anchors.push_back(pointA);
- anchors.push_back(pointA);
- normals.push_back(-segmentNormal);
- normals.push_back(segmentNormal);
- }
- segment = newSegment;
- segmentLength = newSegmentLength;
- segmentNormal = newSegmentNormal;
- return; // early out
- }
- // cramers rule
- Vector2 newSegmentNormal = newSegment.getNormal(halfWidth / newSegmentLength);
- float lambda = Vector2::cross((newSegmentNormal - segmentNormal), newSegment) / det;
- Vector2 d = segmentNormal + segment * lambda;
- anchors.push_back(pointA);
- anchors.push_back(pointA);
- anchors.push_back(pointA);
- anchors.push_back(pointA);
- if (det > 0) // 'left' turn -> intersection on the top
- {
- normals.push_back(d);
- normals.push_back(-segmentNormal);
- normals.push_back(d);
- normals.push_back(-newSegmentNormal);
- }
- else
- {
- normals.push_back(segmentNormal);
- normals.push_back(-d);
- normals.push_back(newSegmentNormal);
- normals.push_back(-d);
- }
- segment = newSegment;
- segmentLength = newSegmentLength;
- segmentNormal = newSegmentNormal;
- }
- void Polyline::calc_overdraw_vertex_count(bool is_looping)
- {
- overdraw_vertex_count = 2 * vertex_count + (is_looping ? 0 : 2);
- }
- void Polyline::render_overdraw(const std::vector<Vector2> &normals, float pixel_size, bool is_looping)
- {
- // 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[k].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
- Vector2 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::calc_overdraw_vertex_count(bool /*is_looping*/)
- {
- overdraw_vertex_count = 4 * (vertex_count-2); // less than ideal
- }
- void NoneJoinPolyline::render_overdraw(const std::vector<Vector2> &/*normals*/, float pixel_size, bool /*is_looping*/)
- {
- for (size_t i = 2; i + 3 < vertex_count; i += 4)
- {
- // v0-v2
- // | / | <- main quad line
- // v1-v3
- Vector2 s = vertices[i+0] - vertices[i+2];
- Vector2 t = vertices[i+0] - vertices[i+1];
- s.normalize(pixel_size);
- t.normalize(pixel_size);
- const size_t k = 4 * (i - 2);
- overdraw[k+0] = vertices[i+0];
- overdraw[k+1] = vertices[i+1];
- overdraw[k+2] = vertices[i+0] + s + t;
- overdraw[k+3] = vertices[i+1] + s - t;
- overdraw[k+4] = vertices[i+1];
- overdraw[k+5] = vertices[i+3];
- overdraw[k+6] = vertices[i+1] + s - t;
- overdraw[k+7] = vertices[i+3] - s - t;
- overdraw[k+ 8] = vertices[i+3];
- overdraw[k+ 9] = vertices[i+2];
- overdraw[k+10] = vertices[i+3] - s - t;
- overdraw[k+11] = vertices[i+2] - s + t;
- overdraw[k+12] = vertices[i+2];
- overdraw[k+13] = vertices[i+0];
- overdraw[k+14] = vertices[i+2] - s + t;
- overdraw[k+15] = vertices[i+0] + s + t;
- }
- }
- Polyline::~Polyline()
- {
- if (vertices)
- delete[] vertices;
- }
- void Polyline::draw(love::graphics::Graphics *gfx)
- {
- const Matrix4 &t = gfx->getTransform();
- bool is2D = t.isAffine2DTransform();
- Color32 curcolor = toColor32(gfx->getColor());
- int overdraw_start = (int) overdraw_vertex_start;
- int overdraw_count = (int) overdraw_vertex_count;
- int total_vertex_count = (int) vertex_count;
- if (overdraw)
- total_vertex_count = overdraw_start + overdraw_count;
- // love's automatic batching can only deal with < 65k vertices per draw.
- // uint16_max - 3 is evenly divisible by 6 (needed for quads mode).
- int maxvertices = LOVE_UINT16_MAX - 3;
- int advance = maxvertices;
- if (triangle_mode == vertex::TriangleIndexMode::STRIP)
- advance -= 2;
- for (int vertex_start = 0; vertex_start < total_vertex_count; vertex_start += advance)
- {
- const Vector2 *verts = vertices + vertex_start;
- Graphics::StreamDrawCommand cmd;
- cmd.formats[0] = vertex::getSinglePositionFormat(is2D);
- cmd.formats[1] = vertex::CommonFormat::RGBAub;
- cmd.indexMode = triangle_mode;
- cmd.vertexCount = std::min(maxvertices, total_vertex_count - vertex_start);
- Graphics::StreamVertexData data = gfx->requestStreamDraw(cmd);
- if (is2D)
- t.transformXY((Vector2 *) data.stream[0], verts, cmd.vertexCount);
- else
- t.transformXY0((Vector3 *) data.stream[0], verts, cmd.vertexCount);
- Color32 *colordata = (Color32 *) data.stream[1];
- int draw_rough_count = std::min(cmd.vertexCount, (int) vertex_count - vertex_start);
- // Constant vertex color up to the overdraw vertices.
- for (int i = 0; i < draw_rough_count; i++)
- colordata[i] = curcolor;
- if (overdraw)
- {
- int draw_remaining_count = cmd.vertexCount - draw_rough_count;
- int draw_overdraw_begin = overdraw_start - vertex_start;
- int draw_overdraw_end = draw_overdraw_begin + overdraw_count;
- draw_overdraw_begin = std::max(0, draw_overdraw_begin);
- int draw_overdraw_count = std::min(draw_remaining_count, draw_overdraw_end - draw_overdraw_begin);
- if (draw_overdraw_count > 0)
- {
- Color32 *colors = colordata + draw_overdraw_begin;
- fill_color_array(curcolor, colors, draw_overdraw_count);
- }
- }
- }
- }
- void Polyline::fill_color_array(Color32 constant_color, Color32 *colors, int count)
- {
- for (int i = 0; i < count; ++i)
- {
- Color32 c = constant_color;
- c.a *= (i+1) % 2; // avoids branching. equiv to if (i%2 == 1) c.a = 0;
- colors[i] = c;
- }
- }
- void NoneJoinPolyline::fill_color_array(Color32 constant_color, Color32 *colors, int count)
- {
- for (int i = 0; i < count; ++i)
- {
- Color32 c = constant_color;
- c.a *= (i & 3) < 2; // if (i % 4 == 2 || i % 4 == 3) c.a = 0
- colors[i] = c;
- }
- }
- } // graphics
- } // love
|