blob: 4eced27780c704917f60f92a45df0d9716c0a3f7 [file] [log] [blame]
// 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