blob: bad3c5c94ea43536cfd29f478c7ee28cdef7be13 [file] [log] [blame]
// Copyright 2016 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/geometry/tessellation.h"
#include <math.h>
#include <algorithm>
#include "lib/escher/impl/model_data.h"
#include "lib/escher/shape/mesh_builder.h"
#include "lib/escher/shape/mesh_builder_factory.h"
#include "lib/fxl/logging.h"
namespace escher {
struct VertexAttributePointers {
vec2* pos2 = nullptr;
vec3* pos3 = nullptr;
vec2* uv = nullptr;
vec2* pos_offset = nullptr;
float* perim = nullptr;
};
// Get pointers to each of the supported vertex attributes within the
// memory pointed to by |vertex|. This is based on the attributes' offsets
// (looked up in the MeshBuilder).
// If the |MeshSpec| does not include an attribute, its corresponding pointer
// will be null.
VertexAttributePointers GetVertexAttributePointers(uint8_t* vertex,
size_t vertex_size,
const MeshSpec& spec,
MeshBuilderPtr builder) {
FXL_CHECK(builder->vertex_stride() <= vertex_size);
FXL_DCHECK(spec.IsValidOneBufferMesh());
VertexAttributePointers attribute_pointers{};
// Compute the offset of each vertex attribute.
if (spec.has_attribute(0, MeshAttribute::kPosition2D)) {
attribute_pointers.pos2 = reinterpret_cast<vec2*>(
vertex + spec.attribute_offset(0, MeshAttribute::kPosition2D));
}
if (spec.has_attribute(0, MeshAttribute::kPosition3D)) {
attribute_pointers.pos3 = reinterpret_cast<vec3*>(
vertex + spec.attribute_offset(0, MeshAttribute::kPosition3D));
}
if (spec.has_attribute(0, MeshAttribute::kUV)) {
attribute_pointers.uv = reinterpret_cast<vec2*>(
vertex + spec.attribute_offset(0, MeshAttribute::kUV));
}
if (spec.has_attribute(0, MeshAttribute::kPositionOffset)) {
attribute_pointers.pos_offset = reinterpret_cast<vec2*>(
vertex + spec.attribute_offset(0, MeshAttribute::kPositionOffset));
}
if (spec.has_attribute(0, MeshAttribute::kPerimeterPos)) {
attribute_pointers.perim = reinterpret_cast<float*>(
vertex + spec.attribute_offset(0, MeshAttribute::kPerimeterPos));
}
return attribute_pointers;
}
IndexedTriangleMesh2d<vec2> NewCircleIndexedTriangleMesh(const MeshSpec& spec,
uint32_t subdivisions,
vec2 center,
float radius) {
FXL_DCHECK((spec == MeshSpec{.attributes = {MeshAttribute::kPosition2D,
MeshAttribute::kUV}}));
IndexedTriangleMesh2d<vec2> mesh;
// Compute the number of vertices in the tessellated circle.
size_t outer_vertex_count = 4;
while (subdivisions-- > 0) {
outer_vertex_count *= 2;
}
const size_t vertex_count = outer_vertex_count + 1; // Add 1 for center.
const size_t index_count = outer_vertex_count * 3;
mesh.resize_indices(index_count);
mesh.resize_vertices(vertex_count);
// Generate vertex positions.
vec2* pos = mesh.positions.data();
vec2* uv = mesh.attributes1.data();
// Build center vertex.
pos[0] = center;
uv[0] = vec2(0.5, 0.5);
pos += 1;
uv += 1;
// Outer vertices.
const float radian_step = 2 * M_PI / outer_vertex_count;
for (size_t i = 0; i < outer_vertex_count; ++i) {
// Direction of the current vertex from the center of the circle.
float radians = i * radian_step;
vec2 dir(sin(radians), cos(radians));
pos[i] = dir * radius + center;
uv[i] = 0.5f * (dir + vec2(1.f, 1.f));
}
// Generate triangle indices.
auto* current_tri = mesh.indices.data();
const uint32_t triangle_count = index_count / 3;
current_tri[0] = 0;
current_tri[1] = 1;
current_tri[2] = triangle_count;
current_tri += 3;
for (size_t i = 1; i < triangle_count; ++i) {
current_tri[0] = 0;
current_tri[1] = i + 1;
current_tri[2] = i;
current_tri += 3;
}
return mesh;
}
MeshPtr NewCircleMesh(MeshBuilderFactory* factory, const MeshSpec& spec,
int subdivisions, vec2 center, float radius,
float offset_magnitude) {
// Compute the number of vertices in the tessellated circle.
FXL_DCHECK(subdivisions >= 0);
FXL_DCHECK(spec.IsValidOneBufferMesh());
size_t outer_vertex_count = 4;
while (subdivisions-- > 0) {
outer_vertex_count *= 2;
}
size_t vertex_count = outer_vertex_count + 1; // Add 1 for center vertex.
size_t index_count = outer_vertex_count * 3;
auto builder = factory->NewMeshBuilder(spec, vertex_count, index_count);
// Generate vertex positions.
constexpr size_t kMaxVertexSize = 100;
uint8_t vertex[kMaxVertexSize];
auto vertex_p =
GetVertexAttributePointers(vertex, kMaxVertexSize, spec, builder);
// Build center vertex.
FXL_CHECK(vertex_p.pos2);
(*vertex_p.pos2) = center;
if (vertex_p.uv)
(*vertex_p.uv) = vec2(0.5f, 0.5f);
if (vertex_p.pos_offset)
(*vertex_p.pos_offset) = vec2(0.f, 0.f);
// TODO: This is an undesirable singularity. Perhaps it would be better to
// treat circles as a ring with inner radius of zero?
if (vertex_p.perim)
(*vertex_p.perim) = 0.f;
builder->AddVertexData(vertex, builder->vertex_stride());
// Outer vertices.
const float outer_vertex_count_reciprocal = 1.f / outer_vertex_count;
const float radian_step = 2 * M_PI / outer_vertex_count;
for (size_t i = 0; i < outer_vertex_count; ++i) {
float radians = i * radian_step;
// Direction of the current vertex from the center of the circle.
vec2 dir(sin(radians), cos(radians));
(*vertex_p.pos2) = dir * radius + center;
if (vertex_p.uv)
(*vertex_p.uv) = 0.5f * (dir + vec2(1.f, 1.f));
if (vertex_p.pos_offset)
(*vertex_p.pos_offset) = dir * offset_magnitude;
if (vertex_p.perim)
(*vertex_p.perim) = i * outer_vertex_count_reciprocal;
builder->AddVertexData(vertex, builder->vertex_stride());
}
// Vertex indices.
for (size_t i = 1; i < outer_vertex_count; ++i) {
builder->AddIndex(0);
builder->AddIndex(i + 1);
builder->AddIndex(i);
}
builder->AddIndex(0);
builder->AddIndex(1);
builder->AddIndex(outer_vertex_count);
auto mesh = builder->Build();
FXL_DCHECK(mesh->num_indices() == index_count);
FXL_DCHECK(mesh->bounding_box() ==
BoundingBox(vec3(center.x - radius, center.y - radius, 0),
vec3(center.x + radius, center.y + radius, 0)));
return mesh;
}
MeshPtr NewRingMesh(MeshBuilderFactory* factory, const MeshSpec& spec,
int subdivisions, vec2 center, float outer_radius,
float inner_radius, float outer_offset_magnitude,
float inner_offset_magnitude) {
// Compute the number of vertices in the tessellated circle.
FXL_DCHECK(subdivisions >= 0);
FXL_DCHECK(spec.IsValidOneBufferMesh());
size_t outer_vertex_count = 4;
while (subdivisions-- > 0) {
outer_vertex_count *= 2;
}
size_t vertex_count = outer_vertex_count * 2;
size_t index_count = outer_vertex_count * 6;
auto builder = factory->NewMeshBuilder(spec, vertex_count, index_count);
// Generate vertex positions.
constexpr size_t kMaxVertexSize = 100;
uint8_t vertex[kMaxVertexSize];
auto vertex_p =
GetVertexAttributePointers(vertex, kMaxVertexSize, spec, builder);
FXL_CHECK(vertex_p.pos2);
const float outer_vertex_count_reciprocal = 1.f / outer_vertex_count;
const float radian_step = 2 * M_PI / outer_vertex_count;
for (size_t i = 0; i < outer_vertex_count; ++i) {
float radians = i * radian_step;
// Direction of the current vertex from the center of the circle.
vec2 dir(sin(radians), cos(radians));
// Build outer-ring vertex.
(*vertex_p.pos2) = dir * outer_radius + center;
if (vertex_p.uv) {
// Munge the texcoords slightly to avoid wrapping artifacts. This matters
// when both:
// - the vk::SamplerAddressMode is eRepeat
// - the vk::Filter is eLinear
(*vertex_p.uv) = 0.49f * (dir + vec2(1.f, 1.02f));
// TODO(ES-108): once we can specify a SamplerAddressMode of eClampToEdge,
// remove the hack above and replace it with the code below:
// (*vertex_p.uv) = 0.5f * (dir + vec2(1.f, 1.f));
}
if (vertex_p.pos_offset)
(*vertex_p.pos_offset) = dir * outer_offset_magnitude;
if (vertex_p.perim)
(*vertex_p.perim) = i * outer_vertex_count_reciprocal;
builder->AddVertexData(vertex, builder->vertex_stride());
// Build inner-ring vertex. Only the position and offset may differ from
// the corresponding outer-ring vertex.
(*vertex_p.pos2) = dir * inner_radius + center;
if (vertex_p.pos_offset) {
// Positive offsets point inward, toward the center of the circle.
(*vertex_p.pos_offset) = dir * -inner_offset_magnitude;
}
builder->AddVertexData(vertex, builder->vertex_stride());
}
// Generate vertex indices.
for (size_t i = 2; i < vertex_count; i += 2) {
builder->AddIndex(i - 2);
builder->AddIndex(i - 1);
builder->AddIndex(i);
builder->AddIndex(i);
builder->AddIndex(i - 1);
builder->AddIndex(i + 1);
}
builder->AddIndex(vertex_count - 2);
builder->AddIndex(vertex_count - 1);
builder->AddIndex(0);
builder->AddIndex(0);
builder->AddIndex(vertex_count - 1);
builder->AddIndex(1);
auto mesh = builder->Build();
FXL_DCHECK(mesh->num_indices() == index_count);
FXL_DCHECK(
mesh->bounding_box() ==
BoundingBox(vec3(center.x - outer_radius, center.y - outer_radius, 0),
vec3(center.x + outer_radius, center.y + outer_radius, 0)));
return mesh;
}
MeshPtr NewSimpleRectangleMesh(MeshBuilderFactory* factory) {
MeshSpec spec{MeshAttribute::kPosition2D | MeshAttribute::kUV};
// In each vertex, the first two floats represent the position and the second
// two are UV coordinates.
vec4 v0(0.f, 0.f, 0.f, 0.f);
vec4 v1(1.f, 0.f, 1.f, 0.f);
vec4 v2(1.f, 1.f, 1.f, 1.f);
vec4 v3(0.f, 1.f, 0.f, 1.f);
MeshBuilderPtr builder = factory->NewMeshBuilder(spec, 4, 6);
return builder->AddVertex(v0)
.AddVertex(v1)
.AddVertex(v2)
.AddVertex(v3)
.AddIndex(0)
.AddIndex(1)
.AddIndex(2)
.AddIndex(0)
.AddIndex(2)
.AddIndex(3)
.Build();
}
MeshPtr NewRectangleMesh(MeshBuilderFactory* factory, const MeshSpec& spec,
int subdivisions, vec2 size, vec2 top_left,
float top_offset_magnitude,
float bottom_offset_magnitude) {
// Compute the number of vertices in the tessellated circle.
FXL_DCHECK(subdivisions >= 0);
size_t vertices_per_side = 2;
while (subdivisions-- > 0) {
vertices_per_side *= 2;
}
size_t vertex_count = vertices_per_side * 2;
size_t index_count = (vertices_per_side - 1) * 6;
auto builder = factory->NewMeshBuilder(spec, vertex_count, index_count);
// Generate vertex positions.
constexpr size_t kMaxVertexSize = 100;
uint8_t vertex[kMaxVertexSize];
auto vertex_p =
GetVertexAttributePointers(vertex, kMaxVertexSize, spec, builder);
FXL_CHECK(vertex_p.pos2);
const float vertices_per_side_reciprocal = 1.f / (vertices_per_side - 1);
for (size_t i = 0; i < vertices_per_side; ++i) {
// Build bottom vertex.
(*vertex_p.pos2) =
top_left + vec2(size.x * i * vertices_per_side_reciprocal, size.y);
if (vertex_p.uv)
(*vertex_p.uv) = vec2(i * vertices_per_side_reciprocal, 1.f);
if (vertex_p.pos_offset)
(*vertex_p.pos_offset) = vec2(0, 1.f * bottom_offset_magnitude);
if (vertex_p.perim)
(*vertex_p.perim) = i * vertices_per_side_reciprocal;
builder->AddVertexData(vertex, builder->vertex_stride());
// Build top vertex.
(*vertex_p.pos2) =
top_left + vec2(size.x * i * vertices_per_side_reciprocal, 0);
if (vertex_p.uv)
(*vertex_p.uv) = vec2(i * vertices_per_side_reciprocal, 0);
if (vertex_p.pos_offset)
(*vertex_p.pos_offset) = vec2(0, -1.f * top_offset_magnitude);
if (vertex_p.perim)
(*vertex_p.perim) = i * vertices_per_side_reciprocal;
builder->AddVertexData(vertex, builder->vertex_stride());
}
// Generate vertex indices.
for (size_t i = 2; i < vertex_count; i += 2) {
builder->AddIndex(i - 2);
builder->AddIndex(i - 1);
builder->AddIndex(i);
builder->AddIndex(i);
builder->AddIndex(i - 1);
builder->AddIndex(i + 1);
}
auto mesh = builder->Build();
FXL_DCHECK(mesh->num_indices() == index_count);
return mesh;
}
MeshPtr NewFullScreenMesh(MeshBuilderFactory* factory) {
MeshSpec spec{MeshAttribute::kPosition2D | MeshAttribute::kUV};
// Some internet lore has it that it is better to use a single triangle rather
// than a rectangle composed of a pair of triangles, so that is what we do.
// The triangle extends beyond the bounds of the screen, and is clipped so
// that each fragment has the same position and UV coordinates as would a
// two-triangle quad. In each vertex, the first two coordinates are position,
// and the second two are UV coords.
return factory->NewMeshBuilder(spec, 3, 3)
->AddVertex(vec4(-1.f, -1.f, 0.f, 0.f))
.AddVertex(vec4(3.f, -1.f, 2.f, 0.f))
.AddVertex(vec4(-1.f, 3.f, 0.f, 2.f))
.AddIndex(0)
.AddIndex(1)
.AddIndex(2)
.Build();
}
MeshPtr NewSphereMesh(MeshBuilderFactory* factory, const MeshSpec& spec,
int subdivisions, vec3 center, float radius) {
FXL_DCHECK(subdivisions >= 0);
FXL_DCHECK(spec.IsValidOneBufferMesh());
size_t vertex_count = 9;
size_t triangle_count = 8;
for (int i = 0; i < subdivisions; ++i) {
// At each level of subdivision, an additional vertex is added for each
// triangle, and each triangle is split into three.
vertex_count += triangle_count;
triangle_count *= 3;
}
// Populate initial octahedron.
auto builder =
factory->NewMeshBuilder(spec, vertex_count, triangle_count * 3);
constexpr size_t kMaxVertexSize = 100;
uint8_t vertex[kMaxVertexSize];
auto vertex_p =
GetVertexAttributePointers(vertex, kMaxVertexSize, spec, builder);
FXL_CHECK(vertex_p.pos3);
// Positions and UV-coordinates for the initial octahedron. The vertex with
// position (-radius, 0, 0) is replicated 4 times, with different UV-coords
// each time. This is a consequence of surface parameterization that is
// described in the header file.
const vec3 positions[] = {vec3(radius, 0.f, 0.f), vec3(0.f, 0.f, radius),
vec3(0.f, -radius, 0.f), vec3(0.f, 0.f, -radius),
vec3(0.f, radius, 0.f), vec3(-radius, 0.f, 0.f),
vec3(-radius, 0.f, 0.f), vec3(-radius, 0.f, 0.f),
vec3(-radius, 0.f, 0.f)};
const vec2 uv_coords[] = {vec2(.5f, .5f), vec2(1.f, .5f), vec2(.5f, 0.f),
vec2(0.f, .5f), vec2(.5f, 1.f), vec2(0.f, 0.f),
vec2(1.f, 0.f), vec2(1.f, 1.f), vec2(0.f, 1.f)};
for (int i = 0; i < 9; ++i) {
(*vertex_p.pos3) = positions[i] + center;
if (vertex_p.uv) {
(*vertex_p.uv) = uv_coords[i];
}
builder->AddVertexData(vertex, builder->vertex_stride());
}
builder->AddTriangle(0, 1, 2)
.AddTriangle(0, 2, 3)
.AddTriangle(0, 3, 4)
.AddTriangle(0, 4, 1)
.AddTriangle(5, 2, 1)
.AddTriangle(6, 3, 2)
.AddTriangle(7, 4, 3)
.AddTriangle(8, 1, 4);
// TODO(ES-32): this is a hack to ease implementation. We don't currently
// need any tessellated spheres; this is just a way to verify that 3D meshes
// are working properly.
FXL_DCHECK(spec.attributes[0] ==
(MeshAttribute::kPosition3D | MeshAttribute::kUV))
<< "Tessellated sphere must have UV-coordinates.";
size_t position_offset = reinterpret_cast<uint8_t*>(vertex_p.pos3) - vertex;
size_t uv_offset = reinterpret_cast<uint8_t*>(vertex_p.uv) - vertex;
while (subdivisions-- > 0) {
// For each level of subdivision, iterate over all existing triangles and
// split them into three.
// TODO(ES-32): see comment in header file... this approach is broken, but
// sufficient for our current purpose.
const size_t subdiv_triangle_count = builder->index_count() / 3;
FXL_DCHECK(subdiv_triangle_count * 3 == builder->index_count());
for (size_t tri_ind = 0; tri_ind < subdiv_triangle_count; ++tri_ind) {
// Obtain indices for the current triangle, and the position/UV coords for
// the corresponding vertices.
uint32_t* tri = builder->GetIndex(tri_ind * 3);
uint32_t ind0 = tri[0];
uint32_t ind1 = tri[1];
uint32_t ind2 = tri[2];
uint8_t* vert0 = builder->GetVertex(ind0);
uint8_t* vert1 = builder->GetVertex(ind1);
uint8_t* vert2 = builder->GetVertex(ind2);
vec3 pos0 = *reinterpret_cast<vec3*>(vert0 + position_offset);
vec3 pos1 = *reinterpret_cast<vec3*>(vert1 + position_offset);
vec3 pos2 = *reinterpret_cast<vec3*>(vert2 + position_offset);
vec2 uv0 = *reinterpret_cast<vec2*>(vert0 + uv_offset);
vec2 uv1 = *reinterpret_cast<vec2*>(vert1 + uv_offset);
vec2 uv2 = *reinterpret_cast<vec2*>(vert2 + uv_offset);
// Create a new vertex by averaging the existing vertex attributes.
(*vertex_p.pos3) =
center + radius * glm::normalize((pos0 + pos1 + pos2) / 3.f - center);
(*vertex_p.uv) = (uv0 + uv1 + uv2) / 3.f;
builder->AddVertexData(vertex, builder->vertex_stride());
// Replace the current triangle in-place with a new triangle that refers
// to the new vertex. Then, add two new triangles that also refer to the
// new vertex.
uint32_t new_ind = builder->vertex_count() - 1;
tri[2] = new_ind;
builder->AddTriangle(ind1, ind2, new_ind)
.AddTriangle(ind2, ind0, new_ind);
}
}
return builder->Build();
}
} // namespace escher