summaryrefslogtreecommitdiffstats
path: root/o3d/core
diff options
context:
space:
mode:
authorkbr@chromium.org <kbr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-26 00:26:00 +0000
committerkbr@chromium.org <kbr@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-02-26 00:26:00 +0000
commit00d721c144d35882dfaf24b3869e95a8d6404ee5 (patch)
tree07243b1cb15a7fb0eddf54fce686c96cda594640 /o3d/core
parent0a46d19ce396112b8a42005d1369de30df3bc872 (diff)
downloadchromium_src-00d721c144d35882dfaf24b3869e95a8d6404ee5.zip
chromium_src-00d721c144d35882dfaf24b3869e95a8d6404ee5.tar.gz
chromium_src-00d721c144d35882dfaf24b3869e95a8d6404ee5.tar.bz2
Added the bulk of the algorithm for GPU accelerated 2D vector curve
rendering from "Rendering Vector Art on the GPU" by Loop and Blinn, GPU Gems 3, Chapter 25. The main entry point to the algorithm is the PathProcessor, which takes in a Skia path and converts it to two triangle meshes: one for the exterior region of the shape containing the curve segments, and one for the interior region of the shape which is filled with constant (1.0) alpha. The o3d.ProcessedPath class is the internal object which exposes the needed entry points to JavaScript. However, o3djs.gpu2d is the user-level entry point to the algorithm. This exposes a Path primitive to which line, quadratic curve and cubic curve segments can be added, and simple fills (currently only a solid color). An SVG loader in samples/gpu2d/svgloader.js illustrates how content might be imported at run time. Several samples and regression tests demonstrate the current state of the implementation. More work is planned. Some small generalizations to the O3D code were necessary to support two-dimensional vertices. Note that I plan to submit gpu2d.js and/or svgloader.js for JavaScript readability. I have run both through the JS compiler and have fixed as many of the doc generation errors as possible in svgloader.js without pulling this file into the o3djs namespace. Tested in O3D on Windows and Mac OS X. BUG=none TEST=various SVG based tests Review URL: http://codereview.chromium.org/652016 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@40079 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'o3d/core')
-rw-r--r--o3d/core/core.gyp7
-rw-r--r--o3d/core/cross/class_manager.cc2
-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
-rw-r--r--o3d/core/cross/primitive.cc87
-rw-r--r--o3d/core/cross/processed_path.cc159
-rw-r--r--o3d/core/cross/processed_path.h115
11 files changed, 2052 insertions, 22 deletions
diff --git a/o3d/core/core.gyp b/o3d/core/core.gyp
index 1b8ee2d..a3e171a 100644
--- a/o3d/core/core.gyp
+++ b/o3d/core/core.gyp
@@ -79,6 +79,7 @@
'../../<(pngdir)/libpng.gyp:libpng',
'../../<(zlibdir)/zlib.gyp:zlib',
'../../skia/skia.gyp:skia',
+ '../third_party/glu/libtess.gyp:libtess',
],
'sources': [
'cross/bitmap.cc',
@@ -207,6 +208,8 @@
'cross/precompile.h',
'cross/primitive.cc',
'cross/primitive.h',
+ 'cross/processed_path.cc',
+ 'cross/processed_path.h',
'cross/profiler.cc',
'cross/profiler.h',
'cross/ray_intersection_info.cc',
@@ -279,6 +282,10 @@
'cross/gpu2d/interval_tree.h',
'cross/gpu2d/local_triangulator.cc',
'cross/gpu2d/local_triangulator.h',
+ 'cross/gpu2d/path_cache.cc',
+ 'cross/gpu2d/path_cache.h',
+ 'cross/gpu2d/path_processor.cc',
+ 'cross/gpu2d/path_processor.h',
'cross/gpu2d/red_black_tree.h',
],
'direct_dependent_settings': {
diff --git a/o3d/core/cross/class_manager.cc b/o3d/core/cross/class_manager.cc
index d74376d..1a3b909 100644
--- a/o3d/core/cross/class_manager.cc
+++ b/o3d/core/cross/class_manager.cc
@@ -54,6 +54,7 @@
#include "core/cross/param_array.h"
#include "core/cross/param_operation.h"
#include "core/cross/primitive.h"
+#include "core/cross/processed_path.h"
#include "core/cross/render_surface_set.h"
#include "core/cross/sampler.h"
#include "core/cross/shape.h"
@@ -157,6 +158,7 @@ ClassManager::ClassManager(ServiceLocator* service_locator)
AddTypedClass<ParamArray>();
AddTypedClass<ParamObject>();
AddTypedClass<Primitive>();
+ AddTypedClass<ProcessedPath>();
AddTypedClass<RenderFrameCounter>();
AddTypedClass<RenderNode>();
AddTypedClass<RenderSurfaceSet>();
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_
+
diff --git a/o3d/core/cross/primitive.cc b/o3d/core/cross/primitive.cc
index d223df0..e2dad14 100644
--- a/o3d/core/cross/primitive.cc
+++ b/o3d/core/cross/primitive.cc
@@ -182,8 +182,8 @@ class FieldReadAccessor {
return *reinterpret_cast<T*>(reinterpret_cast<char*>(data_) + offset_ +
stride_ * (translated_index + real_start_index_));
}
- void Initialize(const Field& field, unsigned int start_index,
- unsigned int length) {
+ virtual void Initialize(const Field& field, unsigned int start_index,
+ unsigned int length) {
buffer_ = field.buffer();
locked_ = false;
data_ = NULL;
@@ -225,6 +225,42 @@ class FieldReadAccessor {
DISALLOW_COPY_AND_ASSIGN(FieldReadAccessor);
};
+// Specialization which pads out the fetched coordinates with zeros,
+// to be able to handle 2D Position streams.
+class FieldReadAccessorPoint3 : public FieldReadAccessor<Point3> {
+ public:
+ FieldReadAccessorPoint3()
+ : FieldReadAccessor<Point3>(),
+ num_components_(0),
+ cache_index_(0) { }
+
+ virtual void Initialize(const Field& field, unsigned int start_index,
+ unsigned int length) {
+ FieldReadAccessor<Point3>::Initialize(field, start_index, length);
+ num_components_ = field.num_components();
+ }
+
+ virtual Point3& operator[](unsigned int translated_index) {
+ Point3& tmp = FieldReadAccessor<Point3>::operator[](translated_index);
+ Point3& cur = cache_[cache_index_];
+ cache_index_ = (cache_index_ + 1) % 3;
+ for (unsigned int i = 0; i < num_components_; i++) {
+ cur[i] = tmp[i];
+ }
+ for (unsigned int i = num_components_; i < 3; i++) {
+ cur[i] = 0;
+ }
+ return cur;
+ }
+
+ protected:
+ unsigned int num_components_;
+ // We need a cache of the three most recently fetched vertices to
+ // match the usage elsewhere in this file
+ int cache_index_;
+ Point3 cache_[3];
+};
+
class FieldReadAccessorUnsignedInt : public FieldReadAccessor<unsigned int> {
public:
FieldReadAccessorUnsignedInt()
@@ -257,18 +293,18 @@ class FieldReadAccessorUnsignedInt : public FieldReadAccessor<unsigned int> {
};
// Attempts to initialize a FieldReadAccessor for the stream of vertices of
-// the primitive. Returns success.
-bool GetVerticesAccessor(const Primitive* primitive,
- int position_stream_index,
- FieldReadAccessor<Point3>* accessor) {
- if (!(accessor && primitive))
- return false;
+// the primitive. Returns newly allocated accessor which must be deleted
+// by caller, or NULL upon failure.
+FieldReadAccessor<Point3>* GetVerticesAccessor(const Primitive* primitive,
+ int position_stream_index) {
+ if (!primitive)
+ return NULL;
const StreamBank* stream_bank = primitive->stream_bank();
if (!stream_bank) {
O3D_ERROR(primitive->service_locator())
<< "No stream bank on Primitive '" << primitive->name() << "'";
- return false;
+ return NULL;
}
const Stream* vertex_stream = stream_bank->GetVertexStream(
@@ -279,28 +315,33 @@ bool GetVerticesAccessor(const Primitive* primitive,
O3D_ERROR(primitive->service_locator())
<< "No POSITION stream index "
<< position_stream_index;
- return false;
+ return NULL;
}
const Field& field = vertex_stream->field();
if (!field.buffer()) {
O3D_ERROR(primitive->service_locator()) << "Vertex Buffer not set";
- return false;
+ return NULL;
}
if (!field.IsA(FloatField::GetApparentClass())) {
O3D_ERROR(primitive->service_locator()) << "POSITION stream index "
<< position_stream_index
<< " is not a FLOAT stream";
- return false;
+ return NULL;
}
- if (field.num_components() != 3) {
- O3D_ERROR(primitive->service_locator())
- << "POSITION stream index " << position_stream_index
- << " does not have 3 components";
- return false;
+ // Don't even try to lock fields that don't have any data
+ if (vertex_stream->GetMaxVertices() == 0) {
+ return NULL;
+ }
+
+ FieldReadAccessor<Point3>* accessor;
+ if (field.num_components() == 3) {
+ accessor = new FieldReadAccessor<Point3>();
+ } else {
+ accessor = new FieldReadAccessorPoint3();
}
accessor->Initialize(field, vertex_stream->start_index(),
@@ -309,10 +350,11 @@ bool GetVerticesAccessor(const Primitive* primitive,
if (!accessor->Valid()) {
O3D_ERROR(primitive->service_locator())
<< "Could not lock vertex buffer";
- return false;
+ delete accessor;
+ return NULL;
}
- return true;
+ return accessor;
}
// Attempts to initialize a FieldReadAccessor for the stream of indices of
@@ -347,11 +389,12 @@ bool Primitive::WalkPolygons(
PolygonFunctor* polygon_functor) const {
DLOG_ASSERT(polygon_functor);
- FieldReadAccessor<Point3> vertices;
FieldReadAccessorUnsignedInt indices;
-
- if (!GetVerticesAccessor(this, position_stream_index, &vertices))
+ scoped_ptr<FieldReadAccessor<Point3> > vertices_pointer(
+ GetVerticesAccessor(this, position_stream_index));
+ if (vertices_pointer.get() == NULL)
return false;
+ FieldReadAccessor<Point3>& vertices = *vertices_pointer;
unsigned int index_count;
if (indexed()) {
diff --git a/o3d/core/cross/processed_path.cc b/o3d/core/cross/processed_path.cc
new file mode 100644
index 0000000..ef5ab4e
--- /dev/null
+++ b/o3d/core/cross/processed_path.cc
@@ -0,0 +1,159 @@
+/*
+ * 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.
+ */
+
+// This file contains the definition of ProcessedPath.
+
+#include "core/cross/processed_path.h"
+
+#include "core/cross/buffer.h"
+#include "core/cross/field.h"
+#include "core/cross/gpu2d/path_processor.h"
+
+namespace o3d {
+
+O3D_DEFN_CLASS(ProcessedPath, ObjectBase);
+
+ProcessedPath::ProcessedPath(ServiceLocator* service_locator)
+ : ObjectBase(service_locator) {
+}
+
+ObjectBase::Ref ProcessedPath::Create(ServiceLocator* service_locator) {
+ return ObjectBase::Ref(
+ ProcessedPath::Ref(new ProcessedPath(service_locator)));
+}
+
+ProcessedPath::~ProcessedPath() {
+}
+
+void ProcessedPath::Clear() {
+ path.reset();
+}
+
+void ProcessedPath::MoveTo(float x, float y) {
+ path.moveTo(SkFloatToScalar(x), SkFloatToScalar(y));
+}
+
+void ProcessedPath::LineTo(float x, float y) {
+ path.lineTo(SkFloatToScalar(x), SkFloatToScalar(y));
+}
+
+void ProcessedPath::QuadraticTo(float cx, float cy, float x, float y) {
+ path.quadTo(SkFloatToScalar(cx),
+ SkFloatToScalar(cy),
+ SkFloatToScalar(x),
+ SkFloatToScalar(y));
+}
+
+void ProcessedPath::CubicTo(float c0x, float c0y,
+ float c1x, float c1y,
+ float x, float y) {
+ path.cubicTo(SkFloatToScalar(c0x),
+ SkFloatToScalar(c0y),
+ SkFloatToScalar(c1x),
+ SkFloatToScalar(c1y),
+ SkFloatToScalar(x),
+ SkFloatToScalar(y));
+}
+
+void ProcessedPath::Close() {
+ path.close();
+}
+
+void ProcessedPath::CreateMesh(Field* exterior_positions,
+ Field* exterior_texture_coordinates,
+ Field* interior_positions) {
+ gpu2d::PathProcessor processor;
+ cache.Clear();
+ processor.Process(path, &cache);
+ // Copy the exterior vertices and texture coordinates into the
+ // fields for the exterior triangles.
+ SetFields(cache.vertices(), exterior_positions,
+ cache.texcoords(), exterior_texture_coordinates,
+ cache.num_vertices());
+ // Copy the interior vertices into the field for the interior
+ // triangles.
+ SetFields(cache.interior_vertices(), interior_positions,
+ NULL, NULL,
+ cache.num_interior_vertices());
+}
+
+void ProcessedPath::SetFields(const float* positions,
+ Field* position_field,
+ const float* texture_coordinates,
+ Field* texture_coordinate_field,
+ unsigned int num_vertices) {
+ // It may happen that there were no exterior or interior triangles.
+ // In this case we allocate a single vertex in the fields to
+ // indicate this to JavaScript since we can not allocate a
+ // zero-sized buffer.
+ DCHECK_NE(num_vertices, 1u);
+ if (num_vertices == 0) {
+ DCHECK_EQ(positions, static_cast<const float*>(NULL));
+ DCHECK_EQ(texture_coordinates, static_cast<const float*>(NULL));
+ num_vertices = 1;
+ }
+ Buffer* buffer = position_field->buffer();
+ if (!buffer)
+ return;
+ if (buffer->num_elements() != num_vertices) {
+ if (!buffer->AllocateElements(num_vertices)) {
+ return;
+ }
+ }
+ if (num_vertices == 1) {
+ float tmp[2] = { 0 };
+ position_field->SetFromFloats(tmp, 2, 0, 1);
+ } else {
+ position_field->SetFromFloats(positions, 2, 0, num_vertices);
+ }
+
+ // The texture coordinates are NULL for the interior triangles.
+ if (texture_coordinate_field != NULL) {
+ buffer = texture_coordinate_field->buffer();
+ if (!buffer)
+ return;
+ if (buffer->num_elements() != num_vertices) {
+ if (!buffer->AllocateElements(num_vertices)) {
+ return;
+ }
+ }
+ if (num_vertices == 1) {
+ float tmp[3] = { 0 };
+ texture_coordinate_field->SetFromFloats(tmp, 3, 0, 1);
+ } else {
+ texture_coordinate_field->SetFromFloats(texture_coordinates,
+ 3, 0, num_vertices);
+ }
+ }
+}
+
+} // namespace o3d
+
diff --git a/o3d/core/cross/processed_path.h b/o3d/core/cross/processed_path.h
new file mode 100644
index 0000000..68233f2
--- /dev/null
+++ b/o3d/core/cross/processed_path.h
@@ -0,0 +1,115 @@
+/*
+ * 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_PROCESSED_PATH_H_
+#define O3D_CORE_CROSS_PROCESSED_PATH_H_
+
+#include "core/cross/object_base.h"
+#include "core/cross/gpu2d/path_cache.h"
+#include "third_party/skia/include/core/SkPath.h"
+
+namespace o3d {
+
+class Field;
+
+// This class is the link between the generic curve processing code in
+// this directory and O3D. It runs the PathProcessor on a set of
+// curves and returns the resulting triangles' vertices and texture
+// coordinates in Fields which can be incorporated into primitives in
+// the scene graph.
+class ProcessedPath : public ObjectBase {
+ public:
+ typedef SmartPointer<ProcessedPath> Ref;
+
+ virtual ~ProcessedPath();
+
+ // Clears out all of the curve segments that have been added to this
+ // path.
+ void Clear();
+
+ // Moves the pen to the given absolute X,Y coordinates. If a contour
+ // isn't currently open on this path, one is opened.
+ void MoveTo(float x, float y);
+
+ // Draws a line from the current coordinates to the given absolute
+ // X,Y coordinates.
+ void LineTo(float x, float y);
+
+ // Draws a quadratic curve from the current coordinates through the
+ // given control point and end point, specified in absolute
+ // coordinates.
+ void QuadraticTo(float cx, float cy, float x, float y);
+
+ // Draws a cubic curve from the current coordinates through the
+ // given control points and end point, specified in absolute
+ // coordinates.
+ void CubicTo(float c0x, float c0y,
+ float c1x, float c1y,
+ float x, float y);
+
+ // Closes the currently open contour on this path.
+ void Close();
+
+ // Creates the triangle mesh which will render the given curve
+ // segments. There are two regions: exterior and interior. The
+ // exterior region covers the portions containing the curve
+ // segments. It has two associated fields: a 2D floating point field
+ // for the positions, and a 3D floating point field for the
+ // Loop/Blinn texture coordinates. The interior region has one 2D
+ // floating point field for the positions. The contents of the
+ // fields are organized for rendering as non-indexed triangles.
+ void CreateMesh(Field* exterior_positions,
+ Field* exterior_texture_coordinates,
+ Field* interior_positions);
+
+ private:
+ explicit ProcessedPath(ServiceLocator* service_locator);
+
+ void SetFields(const float* positions,
+ Field* position_field,
+ const float* texture_coordinates,
+ Field* texture_coordinate_field,
+ unsigned int num_vertices);
+
+ friend class IClassManager;
+ static ObjectBase::Ref Create(ServiceLocator* service_locator);
+
+ SkPath path;
+ gpu2d::PathCache cache;
+
+ O3D_DECL_CLASS(ProcessedPath, ObjectBase);
+ DISALLOW_COPY_AND_ASSIGN(ProcessedPath);
+};
+
+} // namespace o3d
+
+#endif // O3D_CORE_CROSS_PROCESSED_PATH_H_
+