summaryrefslogtreecommitdiffstats
path: root/o3d/core/cross/gpu2d
diff options
context:
space:
mode:
Diffstat (limited to 'o3d/core/cross/gpu2d')
-rw-r--r--o3d/core/cross/gpu2d/cubic_math_utils.cc91
-rw-r--r--o3d/core/cross/gpu2d/cubic_math_utils.h11
-rw-r--r--o3d/core/cross/gpu2d/path_cache.cc105
-rw-r--r--o3d/core/cross/gpu2d/path_cache.h120
-rw-r--r--o3d/core/cross/gpu2d/path_processor.cc1251
-rw-r--r--o3d/core/cross/gpu2d/path_processor.h126
6 files changed, 1704 insertions, 0 deletions
diff --git a/o3d/core/cross/gpu2d/cubic_math_utils.cc b/o3d/core/cross/gpu2d/cubic_math_utils.cc
index a62ba3b..023ce6c 100644
--- a/o3d/core/cross/gpu2d/cubic_math_utils.cc
+++ b/o3d/core/cross/gpu2d/cubic_math_utils.cc
@@ -49,6 +49,59 @@ int Orientation(float x1, float y1,
return (cross_product < 0.0f) ? -1 : ((cross_product > 0.0f) ? 1 : 0);
}
+bool EdgeEdgeTest(float ax, float ay,
+ float v0x, float v0y,
+ float u0x, float u0y,
+ float u1x, float u1y) {
+ // This edge to edge test is based on Franlin Antonio's gem: "Faster
+ // Line Segment Intersection", in Graphics Gems III, pp. 199-202.
+ float bx = u0x - u1x;
+ float by = u0y - u1y;
+ float cx = v0x - u0x;
+ float cy = v0y - u0y;
+ float f = ay * bx - ax * by;
+ float d = by * cx - bx * cy;
+ if ((f > 0 && d >= 0 && d <= f) || (f < 0 && d <= 0 && d >= f)) {
+ float e = ax * cy - ay * cx;
+
+ // This additional test avoids reporting coincident edges, which
+ // is the behavior we want.
+ if (ApproxEqual(e, 0) || ApproxEqual(f, 0) || ApproxEqual(e, f)) {
+ return false;
+ }
+
+ if (f > 0) {
+ return e >= 0 && e <= f;
+ } else {
+ return e <= 0 && e >= f;
+ }
+ }
+ return false;
+}
+
+bool EdgeAgainstTriEdges(float v0x, float v0y,
+ float v1x, float v1y,
+ float u0x, float u0y,
+ float u1x, float u1y,
+ float u2x, float u2y) {
+ float ax, ay;
+ ax = v1x - v0x;
+ ay = v1y - v0y;
+ // Test edge u0, u1 against v0, v1.
+ if (EdgeEdgeTest(ax, ay, v0x, v0y, u0x, u0y, u1x, u1y)) {
+ return true;
+ }
+ // Test edge u1, u2 against v0, v1.
+ if (EdgeEdgeTest(ax, ay, v0x, v0y, u1x, u1y, u2x, u2y)) {
+ return true;
+ }
+ // Test edge u2, u1 against v0, v1.
+ if (EdgeEdgeTest(ax, ay, v0x, v0y, u2x, u2y, u0x, u0y)) {
+ return true;
+ }
+ return false;
+}
+
} // anonymous namespace
bool LinesIntersect(float p1x, float p1y,
@@ -91,6 +144,44 @@ bool PointInTriangle(float px, float py,
return (u > 0.0f) && (v > 0.0f) && (u + v < 1.0f);
}
+bool TrianglesOverlap(float a1x, float a1y,
+ float b1x, float b1y,
+ float c1x, float c1y,
+ float a2x, float a2y,
+ float b2x, float b2y,
+ float c2x, float c2y) {
+ // Derived from coplanar_tri_tri() at
+ // http://jgt.akpeters.com/papers/ShenHengTang03/tri_tri.html ,
+ // simplified for the 2D case and modified so that overlapping edges
+ // do not report overlapping triangles.
+
+ // Test all edges of triangle 1 against the edges of triangle 2.
+ if (EdgeAgainstTriEdges(a1x, a1y, b1x, b1y, a2x, a2y, b2x, b2y, c2x, c2y))
+ return true;
+ if (EdgeAgainstTriEdges(b1x, b1y, c1x, c1y, a2x, a2y, b2x, b2y, c2x, c2y))
+ return true;
+ if (EdgeAgainstTriEdges(c1x, c1y, a1x, a1y, a2x, a2y, b2x, b2y, c2x, c2y))
+ return true;
+ // Finally, test if tri1 is totally contained in tri2 or vice versa.
+ if (PointInTriangle(a1x, a1y, a2x, a2y, b2x, b2y, c2x, c2y))
+ return true;
+ if (PointInTriangle(a2x, a2y, a1x, a1y, b1x, b1y, c1x, c1y))
+ return true;
+
+ // Because we define that triangles sharing a vertex or edge don't
+ // overlap, we must perform additional point-in-triangle tests to
+ // see whether one triangle is contained in the other.
+ if (PointInTriangle(b1x, b1y, a2x, a2y, b2x, b2y, c2x, c2y))
+ return true;
+ if (PointInTriangle(c1x, c1y, a2x, a2y, b2x, b2y, c2x, c2y))
+ return true;
+ if (PointInTriangle(b2x, b2y, a1x, a1y, b1x, b1y, c1x, c1y))
+ return true;
+ if (PointInTriangle(c2x, c2y, a1x, a1y, b1x, b1y, c1x, c1y))
+ return true;
+ return false;
+}
+
} // namespace cubic
} // namespace gpu2d
} // namespace o3d
diff --git a/o3d/core/cross/gpu2d/cubic_math_utils.h b/o3d/core/cross/gpu2d/cubic_math_utils.h
index 0e03939..1051bb3 100644
--- a/o3d/core/cross/gpu2d/cubic_math_utils.h
+++ b/o3d/core/cross/gpu2d/cubic_math_utils.h
@@ -96,6 +96,17 @@ bool PointInTriangle(float px, float py,
float bx, float by,
float cx, float cy);
+// Determines whether the triangles defined by the points (a1, b1, c1)
+// and (a2, b2, c2) overlap. The definition of this function is that
+// if the two triangles only share an adjacent edge or vertex, they
+// are not considered to overlap.
+bool TrianglesOverlap(float a1x, float a1y,
+ float b1x, float b1y,
+ float c1x, float c1y,
+ float a2x, float a2y,
+ float b2x, float b2y,
+ float c2x, float c2y);
+
} // namespace cubic
} // namespace gpu2d
} // namespace o3d
diff --git a/o3d/core/cross/gpu2d/path_cache.cc b/o3d/core/cross/gpu2d/path_cache.cc
new file mode 100644
index 0000000..9dec156
--- /dev/null
+++ b/o3d/core/cross/gpu2d/path_cache.cc
@@ -0,0 +1,105 @@
+/*
+ * 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_cache.h"
+
+namespace o3d {
+namespace gpu2d {
+
+unsigned int PathCache::num_vertices() const {
+ return vertices_.size() / 2;
+}
+
+const float* PathCache::vertices() const {
+ if (num_vertices() == 0)
+ return NULL;
+ return &vertices_.front();
+}
+
+const float* PathCache::texcoords() const {
+ if (num_vertices() == 0)
+ return NULL;
+ return &texcoords_.front();
+}
+
+void PathCache::AddVertex(float x, float y,
+ float k, float l, float m) {
+ vertices_.push_back(x);
+ vertices_.push_back(y);
+ texcoords_.push_back(k);
+ texcoords_.push_back(l);
+ texcoords_.push_back(m);
+}
+
+void PathCache::Clear() {
+ vertices_.clear();
+ texcoords_.clear();
+ interior_vertices_.clear();
+#ifdef O3D_CORE_CROSS_GPU2D_PATH_CACHE_DEBUG_INTERIOR_EDGES
+ interior_edge_vertices_.clear();
+#endif // O3D_CORE_CROSS_GPU2D_PATH_CACHE_DEBUG_INTERIOR_EDGES
+}
+
+unsigned int PathCache::num_interior_vertices() const {
+ return interior_vertices_.size() / 2;
+}
+
+const float* PathCache::interior_vertices() const {
+ if (num_interior_vertices() == 0)
+ return NULL;
+ return &interior_vertices_.front();
+}
+
+void PathCache::AddInteriorVertex(float x, float y) {
+ interior_vertices_.push_back(x);
+ interior_vertices_.push_back(y);
+}
+
+#ifdef O3D_CORE_CROSS_GPU2D_PATH_CACHE_DEBUG_INTERIOR_EDGES
+unsigned int PathCache::num_interior_edge_vertices() const {
+ return interior_edge_vertices_.size() / 2;
+}
+
+const float* PathCache::interior_edge_vertices() const {
+ if (num_interior_edge_vertices() == 0)
+ return NULL;
+ return &interior_edge_vertices_.front();
+}
+
+void PathCache::AddInteriorEdgeVertex(float x, float y) {
+ interior_edge_vertices_.push_back(x);
+ interior_edge_vertices_.push_back(y);
+}
+#endif // O3D_CORE_CROSS_GPU2D_PATH_CACHE_DEBUG_INTERIOR_EDGES
+
+} // namespace gpu2d
+} // namespace o3d
+
diff --git a/o3d/core/cross/gpu2d/path_cache.h b/o3d/core/cross/gpu2d/path_cache.h
new file mode 100644
index 0000000..93ba86c0
--- /dev/null
+++ b/o3d/core/cross/gpu2d/path_cache.h
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+
+#ifndef O3D_CORE_CROSS_GPU2D_PATH_CACHE_H_
+#define O3D_CORE_CROSS_GPU2D_PATH_CACHE_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+
+namespace o3d {
+namespace gpu2d {
+
+// A cache of the processed triangle mesh for a given path. Because
+// these might be expensive to allocate (using malloc/free
+// internally), it is recommended to try to reuse them when possible.
+
+// Uncomment the following to obtain debugging information for the edges facing
+// the interior region of the mesh.
+// #define O3D_CORE_CROSS_GPU2D_PATH_CACHE_DEBUG_INTERIOR_EDGES
+
+class PathCache {
+ public:
+ PathCache() {
+ }
+
+ // The number of vertices in the mesh.
+ unsigned int num_vertices() const;
+
+ // Get the base pointer to the vertex information. There are two
+ // coordinates per vertex. This pointer is valid until the cache is
+ // cleared or another vertex is added. Returns NULL if there are no
+ // vertices in the mesh.
+ const float* vertices() const;
+
+ // Get the base pointer to the texture coordinate information. There
+ // are three coordinates per vertex. This pointer is valid until the
+ // cache is cleared or another vertex is added. Returns NULL if
+ // there are no vertices in the mesh.
+ const float* texcoords() const;
+
+ // Adds a vertex's information to the cache. The first two arguments
+ // are the x and y coordinates of the vertex on the plane; the last
+ // three arguments are the cubic texture coordinates associated with
+ // this vertex.
+ void AddVertex(float x, float y,
+ float k, float l, float m);
+
+ // The number of interior vertices.
+ unsigned int num_interior_vertices() const;
+ // Base pointer to the interior vertices; two coordinates per
+ // vertex, which can be drawn as GL_TRIANGLES. Returns NULL if there
+ // are no interior vertices in the mesh.
+ const float* interior_vertices() const;
+ // Adds an interior vertex to the cache.
+ void AddInteriorVertex(float x, float y);
+
+ // Clears all of the stored vertex information in this cache.
+ void Clear();
+
+#ifdef O3D_CORE_CROSS_GPU2D_PATH_CACHE_DEBUG_INTERIOR_EDGES
+ // The number of interior edge vertices
+ unsigned int num_interior_edge_vertices() const;
+ // Base pointer to the interior vertices; two coordinates per
+ // vertex, which can be drawn as GL_LINES. Returns NULL if there are
+ // no interior edge vertices in the mesh.
+ const float* interior_edge_vertices() const;
+ void AddInteriorEdgeVertex(float x, float y);
+#endif // O3D_CORE_CROSS_GPU2D_PATH_CACHE_DEBUG_INTERIOR_EDGES
+
+ private:
+ // The two-dimensional vertices of the triangle mesh.
+ std::vector<float> vertices_;
+
+ // The three-dimensional cubic texture coordinates.
+ std::vector<float> texcoords_;
+
+ std::vector<float> interior_vertices_;
+
+#ifdef O3D_CORE_CROSS_GPU2D_PATH_CACHE_DEBUG_INTERIOR_EDGES
+ // The following is only for debugging
+ std::vector<float> interior_edge_vertices_;
+#endif // O3D_CORE_CROSS_GPU2D_PATH_CACHE_DEBUG_INTERIOR_EDGES
+
+ DISALLOW_COPY_AND_ASSIGN(PathCache);
+};
+
+} // namespace gpu2d
+} // namespace o3d
+
+#endif // O3D_CORE_CROSS_GPU2D_PATH_CACHE_H_
+
diff --git a/o3d/core/cross/gpu2d/path_processor.cc b/o3d/core/cross/gpu2d/path_processor.cc
new file mode 100644
index 0000000..ccc31df
--- /dev/null
+++ b/o3d/core/cross/gpu2d/path_processor.cc
@@ -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
+
diff --git a/o3d/core/cross/gpu2d/path_processor.h b/o3d/core/cross/gpu2d/path_processor.h
new file mode 100644
index 0000000..f1af232
--- /dev/null
+++ b/o3d/core/cross/gpu2d/path_processor.h
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+// The main entry point for Loop and Blinn's GPU accelerated curve
+// rendering algorithm.
+
+#ifndef O3D_CORE_CROSS_GPU2D_PATH_PROCESSOR_H_
+#define O3D_CORE_CROSS_GPU2D_PATH_PROCESSOR_H_
+
+#include <vector>
+
+#include "base/basictypes.h"
+
+class SkPath;
+
+namespace o3d {
+namespace gpu2d {
+
+class Arena;
+class Contour;
+class PathCache;
+class Segment;
+
+// The PathProcessor turns an SkPath (assumed to contain one or more
+// closed regions) into a set of exterior and interior triangles,
+// stored in the PathCache. The exterior triangles have associated 3D
+// texture coordinates which are used to evaluate the curve's
+// inside/outside function on a per-pixel basis. The interior
+// triangles are filled with 100% opacity.
+//
+// Note that the fill style and management of multiple layers are
+// separate concerns, handled at a higher level with shaders and
+// polygon offsets.
+class PathProcessor {
+ public:
+ PathProcessor();
+ explicit PathProcessor(Arena* arena);
+ ~PathProcessor();
+
+ // Transforms the given path into a triangle mesh for rendering
+ // using Loop and Blinn's shader, placing the result into the given
+ // PathCache.
+ void Process(const SkPath& path, PathCache* cache);
+
+ // Enables or disables verbose logging in debug mode.
+ void set_verbose_logging(bool on_or_off);
+
+ private:
+ // Builds a list of contours for the given path.
+ void BuildContours(const SkPath& path);
+
+ // Determines whether the left or right side of each contour should
+ // be filled.
+ void DetermineSidesToFill();
+
+ // Determines whether the given (closed) contour is oriented
+ // clockwise or counterclockwise.
+ void DetermineOrientation(Contour* contour);
+
+ // Subdivides the curves so that there are no overlaps of the
+ // triangles associated with the curves' control points.
+ void SubdivideCurves();
+
+ // Helper function used during curve subdivision.
+ void ConditionallySubdivide(Segment* seg,
+ std::vector<Segment*>* next_segments);
+
+ // Tessellates the interior regions of the contours.
+ void TessellateInterior(PathCache* cache);
+
+ // For debugging the orientation computation. Returns all of the
+ // segments overlapping the given Y coordinate.
+ std::vector<Segment*> AllSegmentsOverlappingY(float y);
+
+ // For debugging the curve subdivision algorithm. Subdivides the
+ // curves using an alternate, slow (O(n^3)) algorithm.
+ void SubdivideCurvesSlow();
+
+ // Arena from which to allocate temporary objects.
+ Arena* arena_;
+
+ // Whether to delete the arena upon deletion of the PathProcessor.
+ bool should_delete_arena_;
+
+ // The contours described by the path.
+ std::vector<Contour*> contours_;
+
+ // Whether or not to perform verbose logging in debug mode.
+ bool verbose_logging_;
+
+ DISALLOW_COPY_AND_ASSIGN(PathProcessor);
+};
+
+} // namespace gpu2d
+} // namespace o3d
+
+#endif // O3D_CORE_CROSS_GPU2D_PATH_PROCESSOR_H_
+