| Index: core/cross/gpu2d/path_processor.cc
|
| ===================================================================
|
| --- core/cross/gpu2d/path_processor.cc (revision 0)
|
| +++ core/cross/gpu2d/path_processor.cc (revision 0)
|
| @@ -0,0 +1,1251 @@
|
| +/*
|
| + * Copyright 2010, Google Inc.
|
| + * All rights reserved.
|
| + *
|
| + * Redistribution and use in source and binary forms, with or without
|
| + * modification, are permitted provided that the following conditions are
|
| + * met:
|
| + *
|
| + * * Redistributions of source code must retain the above copyright
|
| + * notice, this list of conditions and the following disclaimer.
|
| + * * Redistributions in binary form must reproduce the above
|
| + * copyright notice, this list of conditions and the following disclaimer
|
| + * in the documentation and/or other materials provided with the
|
| + * distribution.
|
| + * * Neither the name of Google Inc. nor the names of its
|
| + * contributors may be used to endorse or promote products derived from
|
| + * this software without specific prior written permission.
|
| + *
|
| + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
| + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
| + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
| + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
| + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
| + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
| + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
| + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
| + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| + */
|
| +
|
| +#include "core/cross/gpu2d/path_processor.h"
|
| +
|
| +#include <algorithm>
|
| +
|
| +#include "base/logging.h"
|
| +#include "core/cross/gpu2d/arena.h"
|
| +#include "core/cross/gpu2d/cubic_classifier.h"
|
| +#include "core/cross/gpu2d/cubic_math_utils.h"
|
| +#include "core/cross/gpu2d/cubic_texture_coords.h"
|
| +#include "core/cross/gpu2d/interval_tree.h"
|
| +#include "core/cross/gpu2d/local_triangulator.h"
|
| +#include "core/cross/gpu2d/path_cache.h"
|
| +#include "third_party/skia/include/core/SkGeometry.h"
|
| +#include "third_party/skia/include/core/SkPath.h"
|
| +#include "third_party/skia/include/core/SkScalar.h"
|
| +#include "third_party/glu/internal_glu.h"
|
| +
|
| +namespace o3d {
|
| +namespace gpu2d {
|
| +
|
| +class Contour;
|
| +
|
| +//----------------------------------------------------------------------
|
| +// min / max helpers
|
| +//
|
| +
|
| +template <typename T>
|
| +T min2(const T& v1, const T& v2) {
|
| + return std::min(v1, v2);
|
| +}
|
| +
|
| +template <typename T>
|
| +T max2(const T& v1, const T& v2) {
|
| + return std::max(v1, v2);
|
| +}
|
| +
|
| +template <typename T>
|
| +T min3(const T& v1, const T& v2, const T& v3) {
|
| + return min2(min2(v1, v2), v3);
|
| +}
|
| +
|
| +template <typename T>
|
| +T max3(const T& v1, const T& v2, const T& v3) {
|
| + return max2(max2(v1, v2), v3);
|
| +}
|
| +
|
| +template <typename T>
|
| +T min4(const T& v1, const T& v2, const T& v3, const T& v4) {
|
| + return min2(min2(v1, v2), min2(v3, v4));
|
| +}
|
| +
|
| +template <typename T>
|
| +T max4(const T& v1, const T& v2, const T& v3, const T& v4) {
|
| + return max2(max2(v1, v2), max2(v3, v4));
|
| +}
|
| +
|
| +//----------------------------------------------------------------------
|
| +// BBox
|
| +//
|
| +
|
| +// Extremely simple bounding box class for Segments.
|
| +class BBox {
|
| + public:
|
| + BBox()
|
| + : min_x_(0),
|
| + min_y_(0),
|
| + max_x_(0),
|
| + max_y_(0) {
|
| + }
|
| +
|
| + // Initializes the parameters of the bounding box.
|
| + void Setup(float min_x,
|
| + float min_y,
|
| + float max_x,
|
| + float max_y) {
|
| + min_x_ = min_x;
|
| + min_y_ = min_y;
|
| + max_x_ = max_x;
|
| + max_y_ = max_y;
|
| + }
|
| +
|
| + // Initializes the bounding box to surround the given triangle.
|
| + void Setup(LocalTriangulator::Triangle* triangle) {
|
| + Setup(min3(triangle->get_vertex(0)->x(),
|
| + triangle->get_vertex(1)->x(),
|
| + triangle->get_vertex(2)->x()),
|
| + min3(triangle->get_vertex(0)->y(),
|
| + triangle->get_vertex(1)->y(),
|
| + triangle->get_vertex(2)->y()),
|
| + max3(triangle->get_vertex(0)->x(),
|
| + triangle->get_vertex(1)->x(),
|
| + triangle->get_vertex(2)->x()),
|
| + max3(triangle->get_vertex(0)->y(),
|
| + triangle->get_vertex(1)->y(),
|
| + triangle->get_vertex(2)->y()));
|
| + }
|
| +
|
| + float min_x() const { return min_x_; }
|
| + float min_y() const { return min_y_; }
|
| + float max_x() const { return max_x_; }
|
| + float max_y() const { return max_y_; }
|
| +
|
| + private:
|
| + float min_x_;
|
| + float min_y_;
|
| + float max_x_;
|
| + float max_y_;
|
| + DISALLOW_COPY_AND_ASSIGN(BBox);
|
| +};
|
| +
|
| +//----------------------------------------------------------------------
|
| +// Segment
|
| +//
|
| +
|
| +// Describes a segment of the path: either a cubic or a line segment.
|
| +// These are stored in a doubly linked list to speed up curve
|
| +// subdivision, which occurs due to either rendering artifacts in the
|
| +// loop case or due to overlapping triangles.
|
| +class Segment {
|
| + public:
|
| + // The kind of segment: cubic or line.
|
| + enum Kind {
|
| + kCubic,
|
| + kLine
|
| + };
|
| +
|
| + // No-argument constructor allows construction by the Arena class.
|
| + Segment()
|
| + : arena_(NULL),
|
| + kind_(kCubic),
|
| + prev_(NULL),
|
| + next_(NULL),
|
| + contour_(NULL),
|
| + triangulator_(NULL),
|
| + marked_for_subdivision_(false) {
|
| + }
|
| +
|
| + // Initializer for cubic curve segments.
|
| + void Setup(Arena* arena,
|
| + Contour* contour,
|
| + SkPoint cp0,
|
| + SkPoint cp1,
|
| + SkPoint cp2,
|
| + SkPoint cp3) {
|
| + arena_ = arena;
|
| + contour_ = contour;
|
| + kind_ = kCubic;
|
| + points_[0] = cp0;
|
| + points_[1] = cp1;
|
| + points_[2] = cp2;
|
| + points_[3] = cp3;
|
| + ComputeBoundingBox();
|
| + }
|
| +
|
| + // Initializer for line segments.
|
| + void Setup(Arena* arena,
|
| + Contour* contour,
|
| + SkPoint p0,
|
| + SkPoint p1) {
|
| + arena_ = arena;
|
| + contour_ = contour;
|
| + kind_ = kLine;
|
| + points_[0] = p0;
|
| + points_[1] = p1;
|
| + ComputeBoundingBox();
|
| + }
|
| +
|
| + // Returns the kind of segment.
|
| + Kind kind() const {
|
| + return kind_;
|
| + }
|
| +
|
| + // Returns the i'th control point, 0 <= i < 4.
|
| + const SkPoint& get_point(int i) {
|
| + DCHECK(i >= 0 && i < 4);
|
| + return points_[i];
|
| + }
|
| +
|
| + // Returns the next segment in the contour.
|
| + Segment* next() const { return next_; }
|
| +
|
| + // Returns the previous segment in the contour.
|
| + Segment* prev() const { return prev_; }
|
| +
|
| + // Sets the next segment in the contour.
|
| + void set_next(Segment* next) { next_ = next; }
|
| +
|
| + // Sets the previous segment in the contour.
|
| + void set_prev(Segment* prev) { prev_ = prev; }
|
| +
|
| + // The contour this segment belongs to.
|
| + Contour* contour() const { return contour_; }
|
| +
|
| + // Subdivides the current segment at the given parameter value (0 <=
|
| + // t <= 1) and replaces it with the two newly created Segments in
|
| + // the linked list, if possible. Returns a pointer to the leftmost
|
| + // Segment.
|
| + Segment* Subdivide(float param) {
|
| + SkPoint dst[7];
|
| + SkChopCubicAt(points_, dst, param);
|
| + Segment* left = arena_->Alloc<Segment>();
|
| + Segment* right = arena_->Alloc<Segment>();
|
| + left->Setup(arena_, contour_, dst[0], dst[1], dst[2], dst[3]);
|
| + right->Setup(arena_, contour_, dst[3], dst[4], dst[5], dst[6]);
|
| + left->set_next(right);
|
| + right->set_prev(left);
|
| + // Try to set up a link between "this->prev()" and "left".
|
| + if (prev() != NULL) {
|
| + left->set_prev(prev());
|
| + prev()->set_next(left);
|
| + }
|
| + // Try to set up a link between "this->next()" and "right".
|
| + Segment* n = next();
|
| + if (n != NULL) {
|
| + right->set_next(n);
|
| + n->set_prev(right);
|
| + }
|
| + // Set up a link between "this" and "left"; this is only to
|
| + // provide a certain amount of continuity during forward iteration.
|
| + set_next(left);
|
| + return left;
|
| + }
|
| +
|
| + // Subdivides the current segment at the halfway point and replaces
|
| + // it with the two newly created Segments in the linked list, if
|
| + // possible. Returns a pointer to the leftmost Segment.
|
| + Segment* Subdivide() {
|
| + return Subdivide(0.5f);
|
| + }
|
| +
|
| + // Returns the bounding box of this segment.
|
| + const BBox& bbox() const {
|
| + return bbox_;
|
| + }
|
| +
|
| + // Computes the number of times a query line starting at the given
|
| + // point and extending to x=+infinity crosses this segment.
|
| + int NumCrossingsForXRay(SkPoint pt) const {
|
| + if (kind_ == kCubic) {
|
| + // Should consider caching the monotonic cubics.
|
| + return SkNumXRayCrossingsForCubic(pt, points_);
|
| + } else {
|
| + return SkXRayCrossesLine(pt, points_) ? 1 : 0;
|
| + }
|
| + }
|
| +
|
| + // Performs a local triangulation of the control points in this
|
| + // segment. This operation only makes sense for cubic type segments.
|
| + void Triangulate(bool compute_inside_edges,
|
| + CubicTextureCoords::Result* tex_coords);
|
| +
|
| + // Returns the number of control point triangles associated with
|
| + // this segment.
|
| + int num_triangles() const {
|
| + if (!triangulator_)
|
| + return 0;
|
| + return triangulator_->num_triangles();
|
| + }
|
| +
|
| + // Fetches the given control point triangle for this segment.
|
| + LocalTriangulator::Triangle* get_triangle(int index) {
|
| + DCHECK(triangulator_);
|
| + return triangulator_->get_triangle(index);
|
| + }
|
| +
|
| + // Number of vertices along the inside edge of this segment. This
|
| + // can be called either for line or cubic type segments.
|
| + int num_interior_vertices() const {
|
| + if (kind_ == kCubic) {
|
| + if (triangulator_) {
|
| + return triangulator_->num_interior_vertices();
|
| + } else {
|
| + return 0;
|
| + }
|
| + }
|
| +
|
| + return 2;
|
| + }
|
| +
|
| + // Returns the given interior vertex, 0 <= index < num_interior_vertices().
|
| + SkPoint get_interior_vertex(int index) const {
|
| + DCHECK(index >= 0 && index < num_interior_vertices());
|
| + if (kind_ == kCubic) {
|
| + SkPoint res;
|
| + if (triangulator_) {
|
| + LocalTriangulator::Vertex* vertex =
|
| + triangulator_->get_interior_vertex(index);
|
| + if (vertex)
|
| + res.set(SkFloatToScalar(vertex->x()),
|
| + SkFloatToScalar(vertex->y()));
|
| + }
|
| + return res;
|
| + }
|
| +
|
| + return points_[index];
|
| + }
|
| +
|
| + // State to assist with curve subdivision.
|
| + bool marked_for_subdivision() {
|
| + return marked_for_subdivision_;
|
| + }
|
| +
|
| + // State to assist with curve subdivision.
|
| + void set_marked_for_subdivision(bool marked_for_subdivision) {
|
| + marked_for_subdivision_ = marked_for_subdivision;
|
| + }
|
| +
|
| + private:
|
| + // Computes the bounding box of this Segment.
|
| + void ComputeBoundingBox() {
|
| + switch (kind_) {
|
| + case kCubic:
|
| + bbox_.Setup(SkScalarToFloat(min4(points_[0].fX,
|
| + points_[1].fX,
|
| + points_[2].fX,
|
| + points_[3].fX)),
|
| + SkScalarToFloat(min4(points_[0].fY,
|
| + points_[1].fY,
|
| + points_[2].fY,
|
| + points_[3].fY)),
|
| + SkScalarToFloat(max4(points_[0].fX,
|
| + points_[1].fX,
|
| + points_[2].fX,
|
| + points_[3].fX)),
|
| + SkScalarToFloat(max4(points_[0].fY,
|
| + points_[1].fY,
|
| + points_[2].fY,
|
| + points_[3].fY)));
|
| + break;
|
| +
|
| + case kLine:
|
| + bbox_.Setup(SkScalarToFloat(min2(points_[0].fX,
|
| + points_[1].fX)),
|
| + SkScalarToFloat(min2(points_[0].fY,
|
| + points_[1].fY)),
|
| + SkScalarToFloat(max2(points_[0].fX,
|
| + points_[1].fX)),
|
| + SkScalarToFloat(max2(points_[0].fY,
|
| + points_[1].fY)));
|
| + break;
|
| +
|
| + default:
|
| + NOTREACHED();
|
| + break;
|
| + }
|
| + }
|
| +
|
| + Arena* arena_;
|
| + Kind kind_;
|
| + SkPoint points_[4];
|
| + Segment* prev_;
|
| + Segment* next_;
|
| + Contour* contour_;
|
| + BBox bbox_;
|
| + LocalTriangulator* triangulator_;
|
| + bool marked_for_subdivision_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(Segment);
|
| +};
|
| +
|
| +//----------------------------------------------------------------------
|
| +// Contour
|
| +//
|
| +
|
| +// Describes a closed contour of the path.
|
| +class Contour {
|
| + public:
|
| + Contour() {
|
| + first_ = &sentinel_;
|
| + first_->set_next(first_);
|
| + first_->set_prev(first_);
|
| + ccw_ = true;
|
| + fill_right_side_ = true;
|
| + }
|
| +
|
| + // Adds a segment to this contour.
|
| + void Add(Segment* segment) {
|
| + if (first_ == &sentinel_) {
|
| + // First element is the sentinel. Replace it with the incoming
|
| + // segment.
|
| + segment->set_next(first_);
|
| + segment->set_prev(first_);
|
| + first_->set_next(segment);
|
| + first_->set_prev(segment);
|
| + first_ = segment;
|
| + } else {
|
| + // first_->prev() is the sentinel.
|
| + Segment* sentinel = first_->prev();
|
| + Segment* last = sentinel->prev();
|
| + last->set_next(segment);
|
| + segment->set_prev(last);
|
| + segment->set_next(sentinel);
|
| + sentinel->set_prev(segment);
|
| + }
|
| + }
|
| +
|
| + // Subdivides the given segment at the given parametric value.
|
| + // Returns a pointer to the first of the two portions of the
|
| + // subdivided segment.
|
| + Segment* Subdivide(Segment* segment, float param) {
|
| + Segment* left = segment->Subdivide(param);
|
| + if (first_ == segment)
|
| + first_ = left;
|
| + return left;
|
| + }
|
| +
|
| + // Subdivides the given segment at the halfway point. Returns a
|
| + // pointer to the first of the two portions of the subdivided
|
| + // segment.
|
| + Segment* Subdivide(Segment* segment) {
|
| + Segment* left = segment->Subdivide();
|
| + if (first_ == segment)
|
| + first_ = left;
|
| + return left;
|
| + }
|
| +
|
| + // Returns the first segment in the contour for iteration.
|
| + Segment* begin() const {
|
| + return first_;
|
| + }
|
| +
|
| + // Returns the last segment in the contour for iteration. Callers
|
| + // should not iterate over this segment. In other words:
|
| + // for (Segment* cur = contour->begin();
|
| + // cur != contour->end();
|
| + // cur = cur->next()) {
|
| + // // .. process cur ...
|
| + // }
|
| + Segment* end() const {
|
| + return first_->prev();
|
| + }
|
| +
|
| + // Returns whether this contour is oriented counterclockwise.
|
| + bool ccw() const {
|
| + return ccw_;
|
| + }
|
| +
|
| + void set_ccw(bool ccw) {
|
| + ccw_ = ccw;
|
| + }
|
| +
|
| + // Returns whether the right side of this contour is filled.
|
| + bool fill_right_side() const { return fill_right_side_; }
|
| +
|
| + void set_fill_right_side(bool fill_right_side) {
|
| + fill_right_side_ = fill_right_side;
|
| + }
|
| +
|
| + private:
|
| + // The start of the segment chain. The segments are kept in a
|
| + // circular doubly linked list for rapid access to the beginning and
|
| + // end.
|
| + Segment* first_;
|
| +
|
| + // The sentinel element at the end of the chain, needed for
|
| + // reasonable iteration semantics.
|
| + Segment sentinel_;
|
| +
|
| + // Whether this contour is oriented counterclockwise.
|
| + bool ccw_;
|
| +
|
| + // Whether we should fill the right (or left) side of this contour.
|
| + bool fill_right_side_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(Contour);
|
| +};
|
| +
|
| +//----------------------------------------------------------------------
|
| +// Segment
|
| +//
|
| +
|
| +// Definition of Segment::Triangulate(), which must come after
|
| +// declaration of Contour.
|
| +void Segment::Triangulate(bool compute_inside_edges,
|
| + CubicTextureCoords::Result* tex_coords) {
|
| + DCHECK(kind_ == kCubic);
|
| + if (triangulator_ == NULL) {
|
| + triangulator_ = arena_->Alloc<LocalTriangulator>();
|
| + }
|
| + triangulator_->Reset();
|
| + for (int i = 0; i < 4; i++) {
|
| + LocalTriangulator::Vertex* vertex = triangulator_->get_vertex(i);
|
| + if (tex_coords) {
|
| + vertex->Set(get_point(i).fX,
|
| + get_point(i).fY,
|
| + tex_coords->coords[i].getX(),
|
| + tex_coords->coords[i].getY(),
|
| + tex_coords->coords[i].getZ());
|
| + } else {
|
| + vertex->Set(get_point(i).fX,
|
| + get_point(i).fY,
|
| + // No texture coordinates yet
|
| + 0, 0, 0);
|
| + }
|
| + }
|
| + triangulator_->Triangulate(compute_inside_edges,
|
| + contour()->fill_right_side());
|
| +}
|
| +
|
| +//----------------------------------------------------------------------
|
| +// PathProcessor
|
| +//
|
| +
|
| +PathProcessor::PathProcessor()
|
| + : arena_(new Arena()),
|
| + should_delete_arena_(true),
|
| + verbose_logging_(false) {
|
| +}
|
| +
|
| +PathProcessor::PathProcessor(Arena* arena)
|
| + : arena_(arena),
|
| + should_delete_arena_(false),
|
| + verbose_logging_(false) {
|
| +}
|
| +
|
| +PathProcessor::~PathProcessor() {
|
| + if (should_delete_arena_) {
|
| + delete arena_;
|
| + }
|
| +}
|
| +
|
| +void PathProcessor::Process(const SkPath& path, PathCache* cache) {
|
| + BuildContours(path);
|
| +
|
| + // Run plane-sweep algorithm to determine overlaps of control point
|
| + // curves and subdivide curves appropriately.
|
| + SubdivideCurves();
|
| +
|
| + // Determine orientations of countours. Based on orientation and the
|
| + // number of curve crossings at a random point on the contour,
|
| + // determine whether to fill the left or right side of the contour.
|
| + DetermineSidesToFill();
|
| +
|
| + // Classify curves, compute texture coordinates and subdivide as
|
| + // necessary to eliminate rendering artifacts. Do the final
|
| + // triangulation of the curve segments, determining the path along
|
| + // the interior of the shape.
|
| + for (std::vector<Contour*>::iterator iter = contours_.begin();
|
| + iter != contours_.end();
|
| + iter++) {
|
| + Contour* cur = *iter;
|
| + for (Segment* seg = cur->begin(); seg != cur->end(); seg = seg->next()) {
|
| + if (seg->kind() == Segment::kCubic) {
|
| + CubicClassifier::Result classification =
|
| + CubicClassifier::Classify(seg->get_point(0).fX,
|
| + seg->get_point(0).fY,
|
| + seg->get_point(1).fX,
|
| + seg->get_point(1).fY,
|
| + seg->get_point(2).fX,
|
| + seg->get_point(2).fY,
|
| + seg->get_point(3).fX,
|
| + seg->get_point(3).fY);
|
| + DLOG_IF(INFO, verbose_logging_) << "Classification:"
|
| + << classification.curve_type();
|
| + CubicTextureCoords::Result tex_coords;
|
| + CubicTextureCoords::Compute(classification,
|
| + cur->fill_right_side(),
|
| + &tex_coords);
|
| + if (tex_coords.has_rendering_artifact) {
|
| + // TODO(kbr): split at the subdivision parameter value
|
| + cur->Subdivide(seg);
|
| + // Next iteration will handle the newly subdivided halves
|
| + } else {
|
| + if (!tex_coords.is_line_or_point) {
|
| + seg->Triangulate(true, &tex_coords);
|
| + for (int i = 0; i < seg->num_triangles(); i++) {
|
| + LocalTriangulator::Triangle* triangle =
|
| + seg->get_triangle(i);
|
| + for (int j = 0; j < 3; j++) {
|
| + LocalTriangulator::Vertex* vert =
|
| + triangle->get_vertex(j);
|
| + cache->AddVertex(vert->x(),
|
| + vert->y(),
|
| + vert->k(),
|
| + vert->l(),
|
| + vert->m());
|
| + }
|
| + }
|
| +#ifdef O3D_CORE_CROSS_GPU2D_PATH_CACHE_DEBUG_INTERIOR_EDGES
|
| + // Show the end user the interior edges as well
|
| + for (int i = 1; i < seg->num_interior_vertices(); i++) {
|
| + SkPoint vert = seg->get_interior_vertex(i);
|
| + // Duplicate previous vertex to be able to draw GL_LINES
|
| + SkPoint prev = seg->get_interior_vertex(i - 1);
|
| + cache->AddInteriorEdgeVertex(prev.fX, prev.fY);
|
| + cache->AddInteriorEdgeVertex(vert.fX, vert.fY);
|
| + }
|
| +#endif // O3D_CORE_CROSS_GPU2D_PATH_CACHE_DEBUG_INTERIOR_EDGES
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Run the interior paths through a tessellation algorithm
|
| + // supporting multiple contours.
|
| + TessellateInterior(cache);
|
| +}
|
| +
|
| +void PathProcessor::BuildContours(const SkPath& path) {
|
| + // Clear out the contours
|
| + contours_.clear();
|
| + SkPath::Iter iter(path, false);
|
| + SkPoint pts[4];
|
| + SkPath::Verb verb;
|
| + Contour* contour = NULL;
|
| + SkPoint cur_pt;
|
| + SkPoint move_to_pt;
|
| + do {
|
| + verb = iter.next(pts);
|
| + if (verb != SkPath::kMove_Verb) {
|
| + if (contour == NULL) {
|
| + contour = arena_->Alloc<Contour>();
|
| + contours_.push_back(contour);
|
| + }
|
| + }
|
| + switch (verb) {
|
| + case SkPath::kMove_Verb: {
|
| + contour = arena_->Alloc<Contour>();
|
| + contours_.push_back(contour);
|
| + cur_pt = pts[0];
|
| + move_to_pt = pts[0];
|
| + DLOG_IF(INFO, verbose_logging_) << "MoveTo (" << pts[0].fX
|
| + << ", " << pts[0].fY << ")";
|
| + break;
|
| + }
|
| + case SkPath::kLine_Verb: {
|
| + Segment* segment = arena_->Alloc<Segment>();
|
| + if (iter.isCloseLine()) {
|
| + segment->Setup(arena_, contour, cur_pt, pts[1]);
|
| + DLOG_IF(INFO, verbose_logging_) << "CloseLineTo (" << cur_pt.fX
|
| + << ", " << cur_pt.fY
|
| + << "), (" << pts[1].fX
|
| + << ", " << pts[1].fY << ")";
|
| + contour->Add(segment);
|
| + contour = NULL;
|
| + } else {
|
| + segment->Setup(arena_, contour, pts[0], pts[1]);
|
| + DLOG_IF(INFO, verbose_logging_) << "LineTo (" << pts[0].fX
|
| + << ", " << pts[0].fY
|
| + << "), (" << pts[1].fX
|
| + << ", " << pts[1].fY << ")";
|
| + contour->Add(segment);
|
| + cur_pt = pts[1];
|
| + }
|
| + break;
|
| + }
|
| + case SkPath::kQuad_Verb: {
|
| + // Need to degree elevate the quadratic into a cubic
|
| + SkPoint cubic[4];
|
| + SkConvertQuadToCubic(pts, cubic);
|
| + Segment* segment = arena_->Alloc<Segment>();
|
| + segment->Setup(arena_, contour,
|
| + cubic[0], cubic[1], cubic[2], cubic[3]);
|
| + DLOG_IF(INFO, verbose_logging_) << "Quad->CubicTo (" << cubic[0].fX
|
| + << ", " << cubic[0].fY
|
| + << "), (" << cubic[1].fX
|
| + << ", " << cubic[1].fY
|
| + << "), (" << cubic[2].fX
|
| + << ", " << cubic[2].fY
|
| + << "), (" << cubic[3].fX
|
| + << ", " << cubic[3].fY << ")";
|
| + contour->Add(segment);
|
| + cur_pt = cubic[3];
|
| + break;
|
| + }
|
| + case SkPath::kCubic_Verb: {
|
| + Segment* segment = arena_->Alloc<Segment>();
|
| + segment->Setup(arena_, contour, pts[0], pts[1], pts[2], pts[3]);
|
| + DLOG_IF(INFO, verbose_logging_) << "CubicTo (" << pts[0].fX
|
| + << ", " << pts[0].fY
|
| + << "), (" << pts[1].fX
|
| + << ", " << pts[1].fY
|
| + << "), (" << pts[2].fX
|
| + << ", " << pts[2].fY
|
| + << "), (" << pts[3].fX
|
| + << ", " << pts[3].fY << ")";
|
| + contour->Add(segment);
|
| + cur_pt = pts[3];
|
| + break;
|
| + }
|
| + case SkPath::kClose_Verb: {
|
| + Segment* segment = arena_->Alloc<Segment>();
|
| + segment->Setup(arena_, contour, cur_pt, move_to_pt);
|
| + DLOG_IF(INFO, verbose_logging_) << "Close (" << cur_pt.fX
|
| + << ", " << cur_pt.fY
|
| + << ") -> (" << move_to_pt.fX
|
| + << ", " << move_to_pt.fY << ")";
|
| + contour->Add(segment);
|
| + contour = NULL;
|
| + }
|
| + default:
|
| + break;
|
| + }
|
| + } while (verb != SkPath::kDone_Verb);
|
| +}
|
| +
|
| +std::vector<Segment*> PathProcessor::AllSegmentsOverlappingY(float y) {
|
| + std::vector<Segment*> res;
|
| + for (std::vector<Contour*>::iterator iter = contours_.begin();
|
| + iter != contours_.end();
|
| + iter++) {
|
| + Contour* cur = *iter;
|
| + for (Segment* seg = cur->begin(); seg != cur->end(); seg = seg->next()) {
|
| + const BBox& bbox = seg->bbox();
|
| + if (bbox.min_y() <= y && y <= bbox.max_y()) {
|
| + res.push_back(seg);
|
| + }
|
| + }
|
| + }
|
| + return res;
|
| +}
|
| +
|
| +// Uncomment this to debug the orientation computation
|
| +// #define O3D_CORE_CROSS_GPU2D_PATH_PROCESSOR_DEBUG_ORIENTATION
|
| +
|
| +void PathProcessor::DetermineSidesToFill() {
|
| + // Loop and Blinn's algorithm can only easily emulate the even/odd
|
| + // fill rule, and only for non-intersecting curves. We can determine
|
| + // which side of each curve segment to fill based on its
|
| + // clockwise/counterclockwise orientation and how many other
|
| + // contours surround it.
|
| +
|
| + // To optimize the query of all curve segments intersecting a
|
| + // horizontal line going to x=+infinity, we build up an interval
|
| + // tree whose keys are the y extents of the segments.
|
| + IntervalTree<float, Segment*> tree(arena_);
|
| + typedef IntervalTree<float, Segment*>::IntervalType IntervalType;
|
| +
|
| + for (std::vector<Contour*>::iterator iter = contours_.begin();
|
| + iter != contours_.end();
|
| + iter++) {
|
| + Contour* cur = *iter;
|
| + DetermineOrientation(cur);
|
| + for (Segment* seg = cur->begin(); seg != cur->end(); seg = seg->next()) {
|
| + const BBox& bbox = seg->bbox();
|
| + tree.Insert(tree.MakeInterval(bbox.min_y(), bbox.max_y(), seg));
|
| + }
|
| + }
|
| +
|
| + // Now iterate through the contours and pick a random segment (in
|
| + // this case we use the first) and a random point on that segment.
|
| + // Find all segments from other contours which intersect this one
|
| + // and count the number of crossings a horizontal line to
|
| + // x=+infinity makes with those contours. This combined with the
|
| + // orientation of the curve tells us which side to fill -- again,
|
| + // assuming an even/odd fill rule, which is all we can easily
|
| + // handle.
|
| + for (std::vector<Contour*>::iterator iter = contours_.begin();
|
| + iter != contours_.end();
|
| + iter++) {
|
| + Contour* cur = *iter;
|
| + Segment* seg = cur->begin();
|
| +
|
| + int num_crossings = 0;
|
| +
|
| + // We use a zero-sized vertical interval for the query
|
| + std::vector<IntervalType> overlaps =
|
| + tree.AllOverlaps(IntervalType(SkScalarToFloat(seg->get_point(0).fY),
|
| + SkScalarToFloat(seg->get_point(0).fY),
|
| + NULL));
|
| +#if !defined(O3D_CORE_CROSS_GPU2D_PATH_PROCESSOR_DEBUG_ORIENTATION)
|
| + for (std::vector<IntervalType>::iterator iter = overlaps.begin();
|
| + iter != overlaps.end();
|
| + iter++) {
|
| + const IntervalType& interval = *iter;
|
| + Segment* query_seg = interval.data();
|
| + // Ignore segments coming from the same contour
|
| + if (query_seg->contour() != cur) {
|
| + num_crossings += query_seg->NumCrossingsForXRay(seg->get_point(0));
|
| + }
|
| + }
|
| +#endif // !defined(O3D_CORE_CROSS_GPU2D_PATH_PROCESSOR_DEBUG_ORIENTATION)
|
| +
|
| +#ifdef O3D_CORE_CROSS_GPU2D_PATH_PROCESSOR_DEBUG_ORIENTATION
|
| + // For debugging
|
| + std::vector<Segment*> slow_overlaps =
|
| + AllSegmentsOverlappingY(seg->get_point(0).fY);
|
| + DCHECK(overlaps.size() == slow_overlaps.size());
|
| + for (std::vector<Segment*>::iterator iter = slow_overlaps.begin();
|
| + iter != slow_overlaps.end();
|
| + iter++) {
|
| + Segment* query_seg = *iter;
|
| + // Ignore segments coming from the same contour
|
| + if (query_seg->contour() != cur) {
|
| + num_crossings += query_seg->NumCrossingsForXRay(seg->get_point(0));
|
| + }
|
| + }
|
| +#endif // O3D_CORE_CROSS_GPU2D_PATH_PROCESSOR_DEBUG_ORIENTATION
|
| +
|
| + if (cur->ccw()) {
|
| + if (num_crossings & 1) {
|
| + cur->set_fill_right_side(true);
|
| + } else {
|
| + cur->set_fill_right_side(false);
|
| + }
|
| + } else {
|
| + if (num_crossings & 1) {
|
| + cur->set_fill_right_side(false);
|
| + } else {
|
| + cur->set_fill_right_side(true);
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +void PathProcessor::DetermineOrientation(Contour* contour) {
|
| + // Determine signed area of the polygon represented by the points
|
| + // along the segments. Consider this an approximation to the true
|
| + // orientation of the polygon; it probably won't handle
|
| + // self-intersecting curves correctly.
|
| + //
|
| + // There is also a pretty basic assumption here that the contour is
|
| + // closed.
|
| + float signed_area = 0;
|
| + for (Segment* seg = contour->begin();
|
| + seg != contour->end();
|
| + seg = seg->next()) {
|
| + int limit = (seg->kind() == Segment::kCubic) ? 4 : 2;
|
| + for (int i = 1; i < limit; i++) {
|
| + const SkPoint& prev_point = seg->get_point(i - 1);
|
| + const SkPoint& point = seg->get_point(i);
|
| + float cur_area = prev_point.fX * point.fY - prev_point.fY * point.fX;
|
| + DLOG_IF(INFO, verbose_logging_) << "Adding to signed area ("
|
| + << prev_point.fX << ", "
|
| + << prev_point.fY << ") -> ("
|
| + << point.fX << ", "
|
| + << point.fY << ") = "
|
| + << cur_area;
|
| + signed_area += cur_area;
|
| + }
|
| + }
|
| +
|
| + if (signed_area > 0)
|
| + contour->set_ccw(true);
|
| + else
|
| + contour->set_ccw(false);
|
| +}
|
| +
|
| +//----------------------------------------------------------------------
|
| +// Classes and typedefs needed for curve subdivision.
|
| +// Unfortunately it appears we can't scope these within the
|
| +// SubdivideCurves() method itself, because templates then fail to
|
| +// instantiate.
|
| +
|
| +// The user data which is placed in the IntervalTree.
|
| +struct SweepData {
|
| + SweepData()
|
| + : triangle(NULL),
|
| + segment(NULL) {
|
| + }
|
| +
|
| + // The triangle this interval is associated with
|
| + LocalTriangulator::Triangle* triangle;
|
| + // The segment the triangle is associated with
|
| + Segment* segment;
|
| +};
|
| +
|
| +typedef IntervalTree<float, SweepData*> SweepTree;
|
| +typedef SweepTree::IntervalType SweepInterval;
|
| +
|
| +// The entry / exit events which occur at the minimum and maximum x
|
| +// coordinates of the control point triangles' bounding boxes.
|
| +//
|
| +// Note that this class requires its copy constructor and assignment
|
| +// operator since it needs to be stored in a std::vector.
|
| +class SweepEvent {
|
| + public:
|
| + SweepEvent()
|
| + : x_(0),
|
| + entry_(false),
|
| + interval_(0, 0, NULL) {
|
| + }
|
| +
|
| + // Initializes the SweepEvent.
|
| + void Setup(float x, bool entry, SweepInterval interval) {
|
| + x_ = x;
|
| + entry_ = entry;
|
| + interval_ = interval;
|
| + }
|
| +
|
| + float x() const { return x_; }
|
| + bool entry() const { return entry_; }
|
| + const SweepInterval& interval() const { return interval_; }
|
| +
|
| + bool operator<(const SweepEvent& other) const {
|
| + return x_ < other.x_;
|
| + }
|
| +
|
| + private:
|
| + float x_;
|
| + bool entry_;
|
| + SweepInterval interval_;
|
| +};
|
| +
|
| +namespace {
|
| +
|
| +bool TrianglesOverlap(LocalTriangulator::Triangle* t0,
|
| + LocalTriangulator::Triangle* t1) {
|
| + LocalTriangulator::Vertex* t0v0 = t0->get_vertex(0);
|
| + LocalTriangulator::Vertex* t0v1 = t0->get_vertex(1);
|
| + LocalTriangulator::Vertex* t0v2 = t0->get_vertex(2);
|
| + LocalTriangulator::Vertex* t1v0 = t1->get_vertex(0);
|
| + LocalTriangulator::Vertex* t1v1 = t1->get_vertex(1);
|
| + LocalTriangulator::Vertex* t1v2 = t1->get_vertex(2);
|
| + return cubic::TrianglesOverlap(t0v0->x(), t0v0->y(),
|
| + t0v1->x(), t0v1->y(),
|
| + t0v2->x(), t0v2->y(),
|
| + t1v0->x(), t1v0->y(),
|
| + t1v1->x(), t1v1->y(),
|
| + t1v2->x(), t1v2->y());
|
| +}
|
| +
|
| +} // anonymous namespace
|
| +
|
| +void PathProcessor::SubdivideCurves() {
|
| + // We need to determine all overlaps of all control point triangles
|
| + // (from different segments, not the same segment) and, if any
|
| + // exist, subdivide the associated curves.
|
| + //
|
| + // The plane-sweep algorithm determines all overlaps of a set of
|
| + // rectangles in the 2D plane. Our problem maps very well to this
|
| + // algorithm and significantly reduces the complexity compared to a
|
| + // naive implementation.
|
| + //
|
| + // Each bounding box of a control point triangle is converted into
|
| + // an "entry" event at its smallest X coordinate and an "exit" event
|
| + // at its largest X coordinate. Each event has an associated
|
| + // one-dimensional interval representing the Y span of the bounding
|
| + // box. We sort these events by increasing X coordinate. We then
|
| + // iterate through them. For each entry event we add the interval to
|
| + // a side interval tree, and query this tree for overlapping
|
| + // intervals. Any overlapping interval corresponds to an overlapping
|
| + // bounding box. For each exit event we remove the associated
|
| + // interval from the interval tree.
|
| +
|
| + std::vector<Segment*> cur_segments;
|
| + std::vector<Segment*> next_segments;
|
| +
|
| + // Start things off by considering all of the segments
|
| + for (std::vector<Contour*>::iterator iter = contours_.begin();
|
| + iter != contours_.end();
|
| + iter++) {
|
| + Contour* cur = *iter;
|
| + for (Segment* seg = cur->begin(); seg != cur->end(); seg = seg->next()) {
|
| + if (seg->kind() == Segment::kCubic) {
|
| + seg->Triangulate(false, NULL);
|
| + cur_segments.push_back(seg);
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Subdivide curves at most this many times
|
| + const int kMaxIter = 5;
|
| + std::vector<SweepInterval> overlaps;
|
| +
|
| + for (int cur_iter = 0; cur_iter < kMaxIter; ++cur_iter) {
|
| + if (cur_segments.size() == 0) {
|
| + // Done
|
| + break;
|
| + }
|
| +
|
| + std::vector<SweepEvent> events;
|
| + SweepTree tree(arena_);
|
| + for (std::vector<Segment*>::iterator iter = cur_segments.begin();
|
| + iter != cur_segments.end();
|
| + iter++) {
|
| + Segment* seg = *iter;
|
| + if (seg->kind() == Segment::kCubic) {
|
| + for (int i = 0; i < seg->num_triangles(); i++) {
|
| + LocalTriangulator::Triangle* triangle = seg->get_triangle(i);
|
| + BBox bbox;
|
| + bbox.Setup(triangle);
|
| + // Ignore zero-width triangles to avoid issues with
|
| + // coincident entry and exit events for the same triangle
|
| + if (bbox.max_x() > bbox.min_x()) {
|
| + SweepData* data = arena_->Alloc<SweepData>();
|
| + data->triangle = triangle;
|
| + data->segment = seg;
|
| + SweepInterval interval =
|
| + tree.MakeInterval(bbox.min_y(), bbox.max_y(), data);
|
| + // Add entry and exit events
|
| + SweepEvent event;
|
| + event.Setup(bbox.min_x(), true, interval);
|
| + events.push_back(event);
|
| + event.Setup(bbox.max_x(), false, interval);
|
| + events.push_back(event);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Sort events by increasing X coordinate
|
| + std::sort(events.begin(), events.end());
|
| +
|
| + // Now iterate through the events
|
| + for (std::vector<SweepEvent>::iterator iter = events.begin();
|
| + iter != events.end();
|
| + iter++) {
|
| + SweepEvent event = *iter;
|
| + if (event.entry()) {
|
| + // Add this interval into the tree
|
| + tree.Insert(event.interval());
|
| + // See whether the associated segment has been subdivided yet
|
| + if (!event.interval().data()->segment->marked_for_subdivision()) {
|
| + // Query the tree
|
| + overlaps.clear();
|
| + tree.AllOverlaps(event.interval(), overlaps);
|
| + // Now see exactly which triangles overlap this one
|
| + for (std::vector<SweepInterval>::iterator iter = overlaps.begin();
|
| + iter != overlaps.end();
|
| + iter++) {
|
| + SweepInterval overlap = *iter;
|
| + // Only pay attention to overlaps from a different Segment
|
| + if (event.interval().data()->segment != overlap.data()->segment) {
|
| + // See whether the triangles actually overlap
|
| + if (TrianglesOverlap(event.interval().data()->triangle,
|
| + overlap.data()->triangle)) {
|
| + // Actually subdivide the segments.
|
| + // Each one might already have been subdivided.
|
| + Segment* seg = event.interval().data()->segment;
|
| + ConditionallySubdivide(seg, &next_segments);
|
| + seg = overlap.data()->segment;
|
| + ConditionallySubdivide(seg, &next_segments);
|
| + }
|
| + }
|
| + }
|
| + }
|
| + } else {
|
| + // Remove this interval from the tree
|
| + tree.Delete(event.interval());
|
| + }
|
| + }
|
| +
|
| + cur_segments = next_segments;
|
| + next_segments.clear();
|
| + }
|
| +}
|
| +
|
| +void PathProcessor::ConditionallySubdivide(
|
| + Segment* seg, std::vector<Segment*>* next_segments) {
|
| + if (!seg->marked_for_subdivision()) {
|
| + seg->set_marked_for_subdivision(true);
|
| + Segment* next = seg->contour()->Subdivide(seg);
|
| + // Triangulate the newly subdivided segments.
|
| + next->Triangulate(false, NULL);
|
| + next->next()->Triangulate(false, NULL);
|
| + // Add them for the next iteration.
|
| + next_segments->push_back(next);
|
| + next_segments->push_back(next->next());
|
| + }
|
| +}
|
| +
|
| +void PathProcessor::SubdivideCurvesSlow() {
|
| + // Alternate, significantly slower algorithm for curve subdivision
|
| + // for use in debugging.
|
| + std::vector<Segment*> cur_segments;
|
| + std::vector<Segment*> next_segments;
|
| +
|
| + // Start things off by considering all of the segments
|
| + for (std::vector<Contour*>::iterator iter = contours_.begin();
|
| + iter != contours_.end();
|
| + iter++) {
|
| + Contour* cur = *iter;
|
| + for (Segment* seg = cur->begin(); seg != cur->end(); seg = seg->next()) {
|
| + if (seg->kind() == Segment::kCubic) {
|
| + seg->Triangulate(false, NULL);
|
| + cur_segments.push_back(seg);
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Subdivide curves at most this many times
|
| + const int kMaxIter = 5;
|
| +
|
| + for (int cur_iter = 0; cur_iter < kMaxIter; ++cur_iter) {
|
| + if (cur_segments.size() == 0) {
|
| + // Done
|
| + break;
|
| + }
|
| +
|
| + for (std::vector<Segment*>::iterator iter = cur_segments.begin();
|
| + iter != cur_segments.end();
|
| + iter++) {
|
| + Segment* seg = *iter;
|
| + if (seg->kind() == Segment::kCubic) {
|
| + for (std::vector<Segment*>::iterator iter2 = cur_segments.begin();
|
| + iter2 != cur_segments.end();
|
| + iter2++) {
|
| + Segment* seg2 = *iter2;
|
| + if (seg2->kind() == Segment::kCubic &&
|
| + seg != seg2) {
|
| + for (int i = 0; i < seg->num_triangles(); i++) {
|
| + LocalTriangulator::Triangle* triangle = seg->get_triangle(i);
|
| + for (int j = 0; j < seg2->num_triangles(); j++) {
|
| + LocalTriangulator::Triangle* triangle2 = seg2->get_triangle(j);
|
| + if (TrianglesOverlap(triangle, triangle2)) {
|
| + ConditionallySubdivide(seg, &next_segments);
|
| + ConditionallySubdivide(seg2, &next_segments);
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| +
|
| + cur_segments = next_segments;
|
| + next_segments.clear();
|
| + }
|
| +}
|
| +
|
| +//----------------------------------------------------------------------
|
| +// Structures and callbacks for tessellation of the interior region of
|
| +// the contours.
|
| +
|
| +// The user data for the GLU tessellator.
|
| +struct TessellationState {
|
| + PathCache* cache;
|
| + std::vector<void*> allocated_pointers;
|
| +};
|
| +
|
| +namespace {
|
| +
|
| +void VertexCallback(void* vertex_data, void* data) {
|
| + TessellationState* state = static_cast<TessellationState*>(data);
|
| + PathCache* cache = state->cache;
|
| + GLdouble* location = static_cast<GLdouble*>(vertex_data);
|
| + cache->AddInteriorVertex(static_cast<float>(location[0]),
|
| + static_cast<float>(location[1]));
|
| +}
|
| +
|
| +void CombineCallback(GLdouble coords[3], void* vertex_data[4],
|
| + GLfloat weight[4], void** out_data,
|
| + void* polygon_data) {
|
| + TessellationState* state = static_cast<TessellationState*>(polygon_data);
|
| + GLdouble* out_vertex = static_cast<GLdouble*>(malloc(3 * sizeof(GLdouble)));
|
| + state->allocated_pointers.push_back(out_vertex);
|
| + out_vertex[0] = coords[0];
|
| + out_vertex[1] = coords[1];
|
| + out_vertex[2] = coords[2];
|
| + *out_data = out_vertex;
|
| +}
|
| +
|
| +void EdgeFlagCallback(GLboolean flag) {
|
| + // No-op just to prevent triangle strips and fans from being passed
|
| + // to us
|
| +}
|
| +
|
| +} // anonymous namespace
|
| +
|
| +void PathProcessor::TessellateInterior(PathCache* cache) {
|
| + // Because the GLU tessellator requires its input in
|
| + // double-precision format, we need to make a separate copy of the
|
| + // data.
|
| + std::vector<GLdouble> vertex_data;
|
| + std::vector<size_t> contour_endings;
|
| + // For avoiding adding coincident vertices.
|
| + float cur_x = 0, cur_y = 0;
|
| + for (std::vector<Contour*>::iterator iter = contours_.begin();
|
| + iter != contours_.end();
|
| + iter++) {
|
| + Contour* cur = *iter;
|
| + bool first = true;
|
| + for (Segment* seg = cur->begin(); seg != cur->end(); seg = seg->next()) {
|
| + int num_interior_vertices = seg->num_interior_vertices();
|
| + for (int i = 0; i < num_interior_vertices - 1; i++) {
|
| + SkPoint point = seg->get_interior_vertex(i);
|
| + if (first) {
|
| + first = false;
|
| + vertex_data.push_back(point.fX);
|
| + vertex_data.push_back(point.fY);
|
| + vertex_data.push_back(0);
|
| + cur_x = point.fX;
|
| + cur_y = point.fY;
|
| + } else if (point.fX != cur_x || point.fY != cur_y) {
|
| + vertex_data.push_back(point.fX);
|
| + vertex_data.push_back(point.fY);
|
| + vertex_data.push_back(0);
|
| + cur_x = point.fX;
|
| + cur_y = point.fY;
|
| + }
|
| + }
|
| + }
|
| + contour_endings.push_back(vertex_data.size());
|
| + }
|
| + // Now that we have all of the vertex data in a stable location in
|
| + // memory, call the tessellator.
|
| + GLUtesselator* tess = internal_gluNewTess();
|
| + TessellationState state;
|
| + state.cache = cache;
|
| + internal_gluTessCallback(tess, GLU_TESS_VERTEX_DATA,
|
| + reinterpret_cast<GLvoid (*)()>(VertexCallback));
|
| + internal_gluTessCallback(tess, GLU_TESS_COMBINE_DATA,
|
| + reinterpret_cast<GLvoid (*)()>(CombineCallback));
|
| + internal_gluTessCallback(tess, GLU_TESS_EDGE_FLAG,
|
| + reinterpret_cast<GLvoid (*)()>(EdgeFlagCallback));
|
| + internal_gluTessBeginPolygon(tess, &state);
|
| + internal_gluTessBeginContour(tess);
|
| + GLdouble* base = &vertex_data.front();
|
| + int contour_idx = 0;
|
| + for (size_t i = 0; i < vertex_data.size(); i += 3) {
|
| + if (i == contour_endings[contour_idx]) {
|
| + internal_gluTessEndContour(tess);
|
| + internal_gluTessBeginContour(tess);
|
| + ++contour_idx;
|
| + }
|
| + internal_gluTessVertex(tess, &base[i], &base[i]);
|
| + }
|
| + internal_gluTessEndContour(tess);
|
| + internal_gluTessEndPolygon(tess);
|
| + for (size_t i = 0; i < state.allocated_pointers.size(); i++) {
|
| + free(state.allocated_pointers[i]);
|
| + }
|
| + internal_gluDeleteTess(tess);
|
| +}
|
| +
|
| +} // namespace gpu2d
|
| +} // namespace o3d
|
| +
|
|
|
| Property changes on: core/cross/gpu2d/path_processor.cc
|
| ___________________________________________________________________
|
| Added: svn:eol-style
|
| + LF
|
|
|
|
|