| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "lib/escher/shape/rounded_rect.h" |
| |
| #include "lib/escher/geometry/types.h" |
| #include "lib/escher/shape/mesh_spec.h" |
| #include "lib/escher/util/trace_macros.h" |
| #include "lib/fxl/logging.h" |
| |
| namespace escher { |
| |
| namespace { |
| |
| // Number of times that the quarter-circle that makes up each corner is |
| // sub-divided. For example, if this is zero, then the corner consists of |
| // a single right-angled triangle. |
| constexpr uint32_t kCornerDivisions = 8; |
| |
| // The central "square" consists of 4 vertices connected to their neighbors, and |
| // to a central vertex. The 4 arms of the "cross" each share 2 vertices with |
| // the central square, and add 2 more. Each corner adds kCornerDivisions more. |
| constexpr uint32_t kVertexCount = 1 + 4 + (4 * 2) + 4 * kCornerDivisions; |
| |
| // The central "square" consists of 4 triangles, and the 4 arms of the "cross" |
| // each have 2. |
| constexpr uint32_t kTriangleCount = 4 + (4 * 2) + 4 * (kCornerDivisions + 1); |
| |
| // Triangles have 3 indices. |
| constexpr uint32_t kIndexCount = kTriangleCount * 3; |
| |
| // Vertex format used when tessellating a RoundedRectSpec (for now, only a |
| // single format is supported). |
| struct PosUvVertex { |
| vec2 pos; |
| vec2 uv; |
| }; |
| |
| struct PosVertex { |
| vec2 pos; |
| }; |
| |
| struct UvVertex { |
| vec2 uv; |
| }; |
| |
| } // anonymous namespace |
| |
| RoundedRectSpec::RoundedRectSpec(float width, float height, |
| float top_left_radius, float top_right_radius, |
| float bottom_right_radius, |
| float bottom_left_radius) |
| : width(width), |
| height(height), |
| top_left_radius(top_left_radius), |
| top_right_radius(top_right_radius), |
| bottom_right_radius(bottom_right_radius), |
| bottom_left_radius(bottom_left_radius) {} |
| |
| // Return the number of vertices and indices that are required to tessellate the |
| // specified rounded-rect. |
| std::pair<uint32_t, uint32_t> GetRoundedRectMeshVertexAndIndexCounts( |
| const RoundedRectSpec& spec) { |
| return std::make_pair(kVertexCount, kIndexCount); |
| } |
| |
| // See escher/shape/doc/RoundedRectTessellation.JPG. |
| void GenerateRoundedRectIndices(const RoundedRectSpec& spec, |
| const MeshSpec& mesh_spec, void* indices_out, |
| uint32_t max_bytes) { |
| TRACE_DURATION("gfx", "escher::GenerateRoundedRectIndices"); |
| |
| FXL_DCHECK(max_bytes >= kIndexCount * sizeof(uint32_t)); |
| uint32_t* indices = static_cast<uint32_t*>(indices_out); |
| |
| // Central square triangles. |
| indices[0] = 0; |
| indices[1] = 4; |
| indices[2] = 1; |
| indices[3] = 0; |
| indices[4] = 1; |
| indices[5] = 2; |
| indices[6] = 0; |
| indices[7] = 2; |
| indices[8] = 3; |
| indices[9] = 0; |
| indices[10] = 3; |
| indices[11] = 4; |
| |
| // "Cross arm 1" triangles. |
| indices[12] = 1; |
| indices[13] = 7; |
| indices[14] = 2; |
| indices[15] = 1; |
| indices[16] = 6; |
| indices[17] = 7; |
| |
| // "Cross arm 2" triangles. |
| indices[18] = 2; |
| indices[19] = 9; |
| indices[20] = 3; |
| indices[21] = 2; |
| indices[22] = 8; |
| indices[23] = 9; |
| |
| // "Cross arm 3" triangles. |
| indices[24] = 3; |
| indices[25] = 11; |
| indices[26] = 4; |
| indices[27] = 3; |
| indices[28] = 10; |
| indices[29] = 11; |
| |
| // "Cross arm 4" triangles. |
| indices[30] = 4; |
| indices[31] = 5; |
| indices[32] = 1; |
| indices[33] = 4; |
| indices[34] = 12; |
| indices[35] = 5; |
| |
| // WARNING: here's where it gets confusing; the number of indices generated is |
| // dependent on kCornerDivisions. |
| |
| // We've already generated output indices for the "cross triangles". |
| constexpr uint32_t kCrossTriangles = 12; |
| // Holds the position of the next index to output. |
| uint32_t out = kCrossTriangles * 3; |
| // Holds the highest index of any vertex used thus far (the central "cross" |
| // consists of 13 vertices, whose indices are 0-12). |
| uint32_t highest_index = 12; |
| |
| // These are the indices of the 4 triangles that would be output if |
| // kCornerDivisions were zero. |
| const uint32_t corner_tris[] = {1, 6, 5, 2, 8, 7, 3, 10, 9, 4, 12, 11}; |
| |
| // For each corner, generate wedges in clockwise order. |
| for (uint32_t corner = 0; corner < 4; ++corner) { |
| // Index of the vertex at the center of the current corner. |
| const uint32_t center = corner_tris[corner * 3]; |
| // As we move clockwise around the corner, this holds the index of the |
| // previous perimeter vertex. |
| uint32_t prev = corner_tris[corner * 3 + 2]; |
| |
| for (uint32_t i = 0; i < kCornerDivisions; ++i) { |
| indices[out++] = center; |
| indices[out++] = prev; |
| indices[out++] = prev = ++highest_index; |
| } |
| // One last triangle (or the only one, if kCornerDivisions == 0). |
| indices[out++] = center; |
| indices[out++] = prev; |
| indices[out++] = corner_tris[corner * 3 + 1]; |
| } |
| |
| FXL_DCHECK(out == kIndexCount); |
| } |
| |
| namespace { |
| |
| // Helper for GenerateRoundedRectVertices(). |
| template <typename VertT> |
| void GenerateRoundedRectVertexUVs(const RoundedRectSpec& spec, VertT* verts) { |
| TRACE_DURATION("gfx", "escher::GenerateRoundedRectVertexUVs"); |
| |
| const float width = spec.width; |
| const float height = spec.height; |
| |
| // First compute UV coordinates of the four "corner centers". |
| verts[1].uv = |
| vec2(spec.top_left_radius / width, spec.top_left_radius / height); |
| verts[2].uv = |
| vec2(1.f - spec.top_right_radius / width, spec.top_right_radius / height); |
| verts[3].uv = vec2(1.f - spec.bottom_right_radius / width, |
| 1.f - spec.bottom_right_radius / height); |
| verts[4].uv = vec2(spec.bottom_left_radius / width, |
| 1.f - spec.bottom_left_radius / height); |
| |
| // The "center" vertex is the average of the four "corner centers". |
| verts[0].uv = |
| 0.25f * ((verts[1].uv + verts[2].uv + verts[3].uv + verts[4].uv)); |
| |
| // Next, compute UV coords for the 8 vertices where the rounded corners meet |
| // the straight side sections. |
| verts[6].uv = vec2(verts[1].uv.x, 0.f); |
| verts[7].uv = vec2(verts[2].uv.x, 0.f); |
| verts[8].uv = vec2(1.f, verts[2].uv.y); |
| verts[9].uv = vec2(1.f, verts[3].uv.y); |
| verts[10].uv = vec2(verts[3].uv.x, 1.f); |
| verts[11].uv = vec2(verts[4].uv.x, 1.f); |
| verts[12].uv = vec2(0.f, verts[4].uv.y); |
| verts[5].uv = vec2(0.f, verts[1].uv.y); |
| |
| // Next, compute UV coords for the vertices that make up the rounded corners. |
| // We start at index 13; indices 0-12 were computed above. |
| uint32_t out = 13; |
| |
| constexpr float kPI = 3.14159265f; |
| constexpr float kAngleStep = kPI / 2 / (kCornerDivisions + 1); |
| |
| // Generate UV coordinates for top-left corner. |
| float angle = kPI + kAngleStep; |
| vec2 scale = |
| vec2(spec.top_left_radius / width, spec.top_left_radius / height); |
| for (size_t i = 0; i < kCornerDivisions; ++i) { |
| verts[out++].uv = verts[1].uv + vec2(cos(angle), sin(angle)) * scale; |
| angle += kAngleStep; |
| } |
| |
| // Generate UV coordinates for top-right corner. |
| angle = 1.5f * kPI + kAngleStep; |
| scale = vec2(spec.top_right_radius / width, spec.top_right_radius / height); |
| for (size_t i = 0; i < kCornerDivisions; ++i) { |
| verts[out++].uv = verts[2].uv + vec2(cos(angle), sin(angle)) * scale; |
| angle += kAngleStep; |
| } |
| |
| // Generate UV coordinates for bottom-right corner. |
| angle = kAngleStep; |
| scale = |
| vec2(spec.bottom_right_radius / width, spec.bottom_right_radius / height); |
| for (size_t i = 0; i < kCornerDivisions; ++i) { |
| verts[out++].uv = verts[3].uv + vec2(cos(angle), sin(angle)) * scale; |
| angle += kAngleStep; |
| } |
| |
| // Generate UV coordinates for bottom-right corner. |
| angle = 0.5f * kPI + kAngleStep; |
| scale = |
| vec2(spec.bottom_left_radius / width, spec.bottom_left_radius / height); |
| for (size_t i = 0; i < kCornerDivisions; ++i) { |
| verts[out++].uv = verts[4].uv + vec2(cos(angle), sin(angle)) * scale; |
| angle += kAngleStep; |
| } |
| } |
| |
| // Helper for GenerateRoundedRectVertices(). |
| template <typename UvVertT, typename PosVertT> |
| void GenerateRoundedRectVertexPositionsFromUVs(const RoundedRectSpec& spec, |
| UvVertT* uv_verts, |
| PosVertT* pos_verts) { |
| TRACE_DURATION("gfx", "escher::GenerateRoundedRectVertexPositionsFromUVs"); |
| |
| const vec2 extent(spec.width, spec.height); |
| const vec2 offset = -0.5f * extent; |
| for (size_t i = 0; i < kVertexCount; ++i) { |
| pos_verts[i].pos = uv_verts[i].uv * extent + offset; |
| } |
| } |
| |
| } // namespace |
| |
| void GenerateRoundedRectVertices(const RoundedRectSpec& spec, |
| const MeshSpec& mesh_spec, void* vertices_out, |
| uint32_t max_bytes) { |
| TRACE_DURATION("gfx", "escher::GenerateRoundedRectVertices"); |
| |
| const float width = spec.width; |
| const float height = spec.height; |
| FXL_DCHECK(width >= spec.top_left_radius + spec.top_right_radius); |
| FXL_DCHECK(width >= spec.bottom_left_radius + spec.bottom_right_radius); |
| FXL_DCHECK(height >= spec.top_left_radius + spec.bottom_left_radius); |
| FXL_DCHECK(height >= spec.top_right_radius + spec.bottom_right_radius); |
| FXL_DCHECK(mesh_spec.total_attribute_count() == 2); |
| FXL_DCHECK(mesh_spec.attributes[0] == |
| (MeshAttribute::kPosition2D | MeshAttribute::kUV)); |
| FXL_DCHECK(max_bytes >= kVertexCount * mesh_spec.stride(0)); |
| FXL_DCHECK(sizeof(PosUvVertex) == mesh_spec.stride(0)); |
| FXL_DCHECK(0U == mesh_spec.attribute_offset(0, MeshAttribute::kPosition2D)); |
| FXL_DCHECK(sizeof(vec2) == mesh_spec.attribute_offset(0, MeshAttribute::kUV)); |
| |
| // We first generate the UV-coordinates for each vertex, then make a second |
| // pass where we use these UV-coords to generate the vertex positions. |
| PosUvVertex* const verts = static_cast<PosUvVertex*>(vertices_out); |
| GenerateRoundedRectVertexUVs(spec, verts); |
| GenerateRoundedRectVertexPositionsFromUVs(spec, verts, verts); |
| } |
| |
| void GenerateRoundedRectVertices(const RoundedRectSpec& spec, |
| const MeshSpec& mesh_spec, |
| void* primary_attributes_out, |
| uint32_t max_primary_attribute_bytes, |
| void* secondary_attributes_out, |
| uint32_t max_secondary_attribute_bytes) { |
| TRACE_DURATION("gfx", "escher::GenerateRoundedRectVertices"); |
| |
| const float width = spec.width; |
| const float height = spec.height; |
| FXL_DCHECK(width >= spec.top_left_radius + spec.top_right_radius); |
| FXL_DCHECK(width >= spec.bottom_left_radius + spec.bottom_right_radius); |
| FXL_DCHECK(height >= spec.top_left_radius + spec.bottom_left_radius); |
| FXL_DCHECK(height >= spec.top_right_radius + spec.bottom_right_radius); |
| FXL_DCHECK(mesh_spec.total_attribute_count() == 2); |
| FXL_DCHECK(mesh_spec.attributes[0] == MeshAttribute::kPosition2D); |
| FXL_DCHECK(mesh_spec.attributes[1] == MeshAttribute::kUV); |
| FXL_DCHECK(max_primary_attribute_bytes == |
| kVertexCount * (mesh_spec.stride(0))); |
| FXL_DCHECK(max_secondary_attribute_bytes == |
| kVertexCount * (mesh_spec.stride(1))); |
| FXL_DCHECK(sizeof(PosVertex) == mesh_spec.stride(0)); |
| FXL_DCHECK(sizeof(UvVertex) == mesh_spec.stride(1)); |
| |
| // We first generate the UV-coordinates for each vertex, then make a second |
| // pass where we use these UV-coords to generate the vertex positions. |
| PosVertex* const positions = static_cast<PosVertex*>(primary_attributes_out); |
| UvVertex* const uvs = reinterpret_cast<UvVertex*>(secondary_attributes_out); |
| GenerateRoundedRectVertexUVs(spec, uvs); |
| GenerateRoundedRectVertexPositionsFromUVs(spec, uvs, positions); |
| } |
| |
| bool RoundedRectSpec::ContainsPoint(vec2 point) const { |
| // Adjust point so that we can test against a rect with bounds (0,0),(w,h). |
| // This is saves some multiplications, but mostly makes the code below more |
| // concise. |
| point += vec2(0.5f * width, 0.5f * height); |
| |
| // Check if point is outside of the bounding rectangle; if so, we don't need |
| // to test the corner cases. |
| if (point.x < 0 || point.y < 0 || point.x > width || point.y > height) { |
| return false; |
| } |
| // Now we know that the point is would be contained, if all corner radii were |
| // zero. But, since the radii are not zero, it is possible that the point is |
| // outside one of the four quarter-circles that comprise the rounded corners. |
| |
| // Check if point is inside the top-left corner. |
| { |
| // Start by computing the vector from the corner-center to the test-point, |
| // and checking whether the direction of the vector is within the corner's |
| // quarter-circle arc. If so, we can immediately determine whether or not |
| // the point is contained; otherwise, we need to test against the other |
| // corners. |
| float rad = top_left_radius; |
| vec2 diff = point - vec2(rad, rad); |
| if (diff.x < 0.f && diff.y < 0.f) { |
| // The direction is within the corner's arc. Compare squared-distances to |
| // determine whether the point is beyond the arc. |
| return diff.x * diff.x + diff.y * diff.y <= rad * rad; |
| } else { |
| // The direction is not within the corner's quarter circle, so we proceed |
| // on to the other corners. |
| } |
| } |
| |
| // Check if point is inside the top-right corner. |
| // |
| // For this corner, and subsequent ones, we follow the same approach as above, |
| // with slight adjustments to the computation of the difference-vector, and |
| // how we test the direction of that vector. |
| { |
| float rad = top_right_radius; |
| vec2 diff = point - vec2(width - rad, rad); |
| if (diff.x > 0.f && diff.y < 0.f) { |
| return diff.x * diff.x + diff.y * diff.y <= rad * rad; |
| } |
| } |
| |
| // Check if point is inside the bottom-right corner. |
| { |
| float rad = bottom_right_radius; |
| vec2 diff = point - vec2(width - rad, height - rad); |
| if (diff.x > 0.f && diff.y > 0.f) { |
| return diff.x * diff.x + diff.y * diff.y <= rad * rad; |
| } |
| } |
| |
| // Check if point is inside the bottom-left corner. |
| { |
| float rad = bottom_left_radius; |
| vec2 diff = point - vec2(rad, height - rad); |
| if (diff.x < 0.f && diff.y > 0.f) { |
| return diff.x * diff.x + diff.y * diff.y <= rad * rad; |
| } |
| } |
| |
| // Point isn't in the corner areas, so it is definitely contained. |
| return true; |
| } |
| |
| RoundedRectSpec RoundedRectSpec::operator*(float t) const { |
| return RoundedRectSpec(width * t, height * t, top_left_radius * t, |
| top_right_radius * t, bottom_right_radius * t, |
| bottom_left_radius * t); |
| } |
| |
| RoundedRectSpec RoundedRectSpec::operator+(const RoundedRectSpec& other) const { |
| return RoundedRectSpec(width + other.width, height + other.height, |
| top_left_radius + other.top_left_radius, |
| top_right_radius + other.top_right_radius, |
| bottom_right_radius + other.bottom_right_radius, |
| bottom_left_radius + other.bottom_left_radius); |
| } |
| |
| } // namespace escher |