예제 #1
0
static GeometryCoordinates fromClipperPath(const ClipperLib::Path& path) {
    GeometryCoordinates result;
    result.reserve(path.size() + 1);
    
    result.reserve(path.size());
    for (const auto& p : path) {
        using Coordinate = GeometryCoordinates::coordinate_type;
        assert(p.x >= std::numeric_limits<Coordinate>::min());
        assert(p.x <= std::numeric_limits<Coordinate>::max());
        assert(p.y >= std::numeric_limits<Coordinate>::min());
        assert(p.y <= std::numeric_limits<Coordinate>::max());
        result.emplace_back(Coordinate(p.x), Coordinate(p.y));
    }
    
    // Clipper does not repeat initial point, but our geometry model requires it.
    if (!result.empty()) {
        result.push_back(result.front());
    }
    
    return result;
}
예제 #2
0
void LineBucket::addGeometry(const GeometryCoordinates& vertices) {
    const GLsizei len = [&vertices] {
        GLsizei l = static_cast<GLsizei>(vertices.size());
        // If the line has duplicate vertices at the end, adjust length to remove them.
        while (l > 2 && vertices[l - 1] == vertices[l - 2]) {
            l--;
        }
        return l;
    }();

    if (len < 2) {
        // fprintf(stderr, "a line must have at least two vertices\n");
        return;
    }

    const float miterLimit = layout.join == JoinType::Bevel ? 1.05f : float(layout.miterLimit);

    const double sharpCornerOffset = SHARP_CORNER_OFFSET * (util::EXTENT / (util::tileSize * overscaling));

    const GeometryCoordinate firstVertex = vertices.front();
    const GeometryCoordinate lastVertex = vertices[len - 1];
    const bool closed = firstVertex == lastVertex;

    if (len == 2 && closed) {
        // fprintf(stderr, "a line may not have coincident points\n");
        return;
    }

    const CapType beginCap = layout.cap;
    const CapType endCap = closed ? CapType::Butt : CapType(layout.cap);

    int8_t flip = 1;
    double distance = 0;
    bool startOfLine = true;
    GeometryCoordinate currentVertex = GeometryCoordinate::null(), prevVertex = GeometryCoordinate::null(),
               nextVertex = GeometryCoordinate::null();
    vec2<double> prevNormal = vec2<double>::null(), nextNormal = vec2<double>::null();

    // the last three vertices added
    e1 = e2 = e3 = -1;

    if (closed) {
        currentVertex = vertices[len - 2];
        nextNormal = util::perp(util::unit(vec2<double>(firstVertex - currentVertex)));
    }

    const GLint startVertex = vertexBuffer.index();
    std::vector<TriangleElement> triangleStore;

    for (GLsizei i = 0; i < len; ++i) {
        if (closed && i == len - 1) {
            // if the line is closed, we treat the last vertex like the first
            nextVertex = vertices[1];
        } else if (i + 1 < len) {
            // just the next vertex
            nextVertex = vertices[i + 1];
        } else {
            // there is no next vertex
            nextVertex = GeometryCoordinate::null();
        }

        // if two consecutive vertices exist, skip the current one
        if (nextVertex && vertices[i] == nextVertex) {
            continue;
        }

        if (nextNormal) {
            prevNormal = nextNormal;
        }
        if (currentVertex) {
            prevVertex = currentVertex;
        }

        currentVertex = vertices[i];

        // Calculate the normal towards the next vertex in this line. In case
        // there is no next vertex, pretend that the line is continuing straight,
        // meaning that we are just using the previous normal.
        nextNormal = nextVertex ? util::perp(util::unit(vec2<double>(nextVertex - currentVertex)))
                                : prevNormal;

        // If we still don't have a previous normal, this is the beginning of a
        // non-closed line, so we're doing a straight "join".
        if (!prevNormal) {
            prevNormal = nextNormal;
        }

        // Determine the normal of the join extrusion. It is the angle bisector
        // of the segments between the previous line and the next line.
        vec2<double> joinNormal = util::unit(prevNormal + nextNormal);

        /*  joinNormal     prevNormal
         *             ↖      ↑
         *                .________. prevVertex
         *                |
         * nextNormal  ←  |  currentVertex
         *                |
         *     nextVertex !
         *
         */

        // Calculate the length of the miter (the ratio of the miter to the width).
        // Find the cosine of the angle between the next and join normals
        // using dot product. The inverse of that is the miter length.
        const float cosHalfAngle = joinNormal.x * nextNormal.x + joinNormal.y * nextNormal.y;
        const float miterLength = cosHalfAngle != 0 ? 1 / cosHalfAngle: 1;

        const bool isSharpCorner = cosHalfAngle < COS_HALF_SHARP_CORNER && prevVertex && nextVertex;

        if (isSharpCorner && i > 0) {
            const double prevSegmentLength = util::dist<double>(currentVertex, prevVertex);
            if (prevSegmentLength > 2.0 * sharpCornerOffset) {
                GeometryCoordinate newPrevVertex = currentVertex - (util::round(vec2<double>(currentVertex - prevVertex) * (sharpCornerOffset / prevSegmentLength)));
                distance += util::dist<double>(newPrevVertex, prevVertex);
                addCurrentVertex(newPrevVertex, flip, distance, prevNormal, 0, 0, false, startVertex, triangleStore);
                prevVertex = newPrevVertex;
            }
        }

        // The join if a middle vertex, otherwise the cap
        const bool middleVertex = prevVertex && nextVertex;
        JoinType currentJoin = layout.join;
        const CapType currentCap = nextVertex ? beginCap : endCap;

        if (middleVertex) {
            if (currentJoin == JoinType::Round) {
                if (miterLength < layout.roundLimit) {
                    currentJoin = JoinType::Miter;
                } else if (miterLength <= 2) {
                    currentJoin = JoinType::FakeRound;
                }
            }

            if (currentJoin == JoinType::Miter && miterLength > miterLimit) {
                currentJoin = JoinType::Bevel;
            }

            if (currentJoin == JoinType::Bevel) {
                // The maximum extrude length is 128 / 63 = 2 times the width of the line
                // so if miterLength >= 2 we need to draw a different type of bevel where.
                if (miterLength > 2) {
                    currentJoin = JoinType::FlipBevel;
                }

                // If the miterLength is really small and the line bevel wouldn't be visible,
                // just draw a miter join to save a triangle.
                if (miterLength < miterLimit) {
                    currentJoin = JoinType::Miter;
                }
            }
        }

        // Calculate how far along the line the currentVertex is
        if (prevVertex)
            distance += util::dist<double>(currentVertex, prevVertex);

        if (middleVertex && currentJoin == JoinType::Miter) {
            joinNormal = joinNormal * miterLength;
            addCurrentVertex(currentVertex, flip, distance, joinNormal, 0, 0, false, startVertex,
                             triangleStore);

        } else if (middleVertex && currentJoin == JoinType::FlipBevel) {
            // miter is too big, flip the direction to make a beveled join

            if (miterLength > 100) {
                // Almost parallel lines
                joinNormal = nextNormal;
            } else {
                const float direction = prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x > 0 ? -1 : 1;
                const float bevelLength = miterLength * util::mag(prevNormal + nextNormal) /
                                          util::mag(prevNormal - nextNormal);
                joinNormal = util::perp(joinNormal) * bevelLength * direction;
            }

            addCurrentVertex(currentVertex, flip, distance, joinNormal, 0, 0, false, startVertex,
                             triangleStore);

            addCurrentVertex(currentVertex, -flip, distance, joinNormal, 0, 0, false, startVertex,
                             triangleStore);
        } else if (middleVertex && (currentJoin == JoinType::Bevel || currentJoin == JoinType::FakeRound)) {
            const bool lineTurnsLeft = flip * (prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x) > 0;
            const float offset = -std::sqrt(miterLength * miterLength - 1);
            float offsetA;
            float offsetB;

            if (lineTurnsLeft) {
                offsetB = 0;
                offsetA = offset;
            } else {
                offsetA = 0;
                offsetB = offset;
            }

            // Close previous segement with bevel
            if (!startOfLine) {
                addCurrentVertex(currentVertex, flip, distance, prevNormal, offsetA, offsetB, false,
                                 startVertex, triangleStore);
            }

            if (currentJoin == JoinType::FakeRound) {
                // The join angle is sharp enough that a round join would be visible.
                // Bevel joins fill the gap between segments with a single pie slice triangle.
                // Create a round join by adding multiple pie slices. The join isn't actually round, but
                // it looks like it is at the sizes we render lines at.

                // Add more triangles for sharper angles.
                // This math is just a good enough approximation. It isn't "correct".
                const int n = std::floor((0.5 - (cosHalfAngle - 0.5)) * 8);

                for (int m = 0; m < n; m++) {
                    auto approxFractionalJoinNormal = util::unit(nextNormal * ((m + 1.0f) / (n + 1.0f)) + prevNormal);
                    addPieSliceVertex(currentVertex, flip, distance, approxFractionalJoinNormal, lineTurnsLeft, startVertex, triangleStore);
                }

                addPieSliceVertex(currentVertex, flip, distance, joinNormal, lineTurnsLeft, startVertex, triangleStore);

                for (int k = n - 1; k >= 0; k--) {
                    auto approxFractionalJoinNormal = util::unit(prevNormal * ((k + 1.0f) / (n + 1.0f)) + nextNormal);
                    addPieSliceVertex(currentVertex, flip, distance, approxFractionalJoinNormal, lineTurnsLeft, startVertex, triangleStore);
                }
            }

            // Start next segment
            if (nextVertex) {
                addCurrentVertex(currentVertex, flip, distance, nextNormal, -offsetA, -offsetB,
                                 false, startVertex, triangleStore);
            }

        } else if (!middleVertex && currentCap == CapType::Butt) {
            if (!startOfLine) {
                // Close previous segment with a butt
                addCurrentVertex(currentVertex, flip, distance, prevNormal, 0, 0, false,
                                 startVertex, triangleStore);
            }

            // Start next segment with a butt
            if (nextVertex) {
                addCurrentVertex(currentVertex, flip, distance, nextNormal, 0, 0, false,
                                 startVertex, triangleStore);
            }

        } else if (!middleVertex && currentCap == CapType::Square) {
            if (!startOfLine) {
                // Close previous segment with a square cap
                addCurrentVertex(currentVertex, flip, distance, prevNormal, 1, 1, false,
                                 startVertex, triangleStore);

                // The segment is done. Unset vertices to disconnect segments.
                e1 = e2 = -1;
                flip = 1;
            }

            // Start next segment
            if (nextVertex) {
                addCurrentVertex(currentVertex, flip, distance, nextNormal, -1, -1, false,
                                 startVertex, triangleStore);
            }

        } else if (middleVertex ? currentJoin == JoinType::Round : currentCap == CapType::Round) {
            if (!startOfLine) {
                // Close previous segment with a butt
                addCurrentVertex(currentVertex, flip, distance, prevNormal, 0, 0, false,
                                 startVertex, triangleStore);

                // Add round cap or linejoin at end of segment
                addCurrentVertex(currentVertex, flip, distance, prevNormal, 1, 1, true, startVertex,
                                 triangleStore);

                // The segment is done. Unset vertices to disconnect segments.
                e1 = e2 = -1;
                flip = 1;
            }

            // Start next segment with a butt
            if (nextVertex) {
                // Add round cap before first segment
                addCurrentVertex(currentVertex, flip, distance, nextNormal, -1, -1, true,
                                 startVertex, triangleStore);

                addCurrentVertex(currentVertex, flip, distance, nextNormal, 0, 0, false,
                                 startVertex, triangleStore);
            }
        }

        if (isSharpCorner && i < len - 1) {
            const double nextSegmentLength = util::dist<double>(currentVertex, nextVertex);
            if (nextSegmentLength > 2 * sharpCornerOffset) {
                GeometryCoordinate newCurrentVertex = currentVertex + util::round(vec2<double>(nextVertex - currentVertex) * (sharpCornerOffset / nextSegmentLength));
                distance += util::dist<double>(newCurrentVertex, currentVertex);
                addCurrentVertex(newCurrentVertex, flip, distance, nextNormal, 0, 0, false, startVertex, triangleStore);
                currentVertex = newCurrentVertex;
            }
        }

        startOfLine = false;
    }

    const GLsizei endVertex = vertexBuffer.index();
    const GLsizei vertexCount = endVertex - startVertex;

    // Store the triangle/line groups.
    {
        if (triangleGroups.empty() || (triangleGroups.back()->vertex_length + vertexCount > 65535)) {
            // Move to a new group because the old one can't hold the geometry.
            triangleGroups.emplace_back(std::make_unique<TriangleGroup>());
        }

        assert(triangleGroups.back());
        auto& group = *triangleGroups.back();
        for (const auto& triangle : triangleStore) {
            triangleElementsBuffer.add(group.vertex_length + triangle.a,
                                       group.vertex_length + triangle.b,
                                       group.vertex_length + triangle.c);
        }

        group.vertex_length += vertexCount;
        group.elements_length += triangleStore.size();
    }
}