|  | // 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 "src/ui/lib/escher/mesh/tessellation.h" | 
|  |  | 
|  | #include <lib/syslog/cpp/macros.h> | 
|  | #include <math.h> | 
|  |  | 
|  | #include <algorithm> | 
|  |  | 
|  | #include "src/ui/lib/escher/shape/mesh_builder.h" | 
|  | #include "src/ui/lib/escher/shape/mesh_builder_factory.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) { | 
|  | FX_CHECK(builder->vertex_stride() <= vertex_size); | 
|  | FX_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) { | 
|  | FX_DCHECK((spec == MeshSpec{.attributes = {MeshAttribute::kPosition2D, MeshAttribute::kUV}})); | 
|  | IndexedTriangleMesh2d<vec2> mesh; | 
|  |  | 
|  | // Compute the number of vertices in the tessellated circle. | 
|  | uint32_t outer_vertex_count = 4; | 
|  | while (subdivisions-- > 0) { | 
|  | outer_vertex_count *= 2; | 
|  | } | 
|  |  | 
|  | const uint32_t vertex_count = outer_vertex_count + 1;  // Add 1 for center. | 
|  | const uint32_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 = static_cast<float>(2 * M_PI / outer_vertex_count); | 
|  | for (uint32_t i = 0; i < outer_vertex_count; ++i) { | 
|  | // Direction of the current vertex from the center of the circle. | 
|  | float radians = static_cast<float>(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 (uint32_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; | 
|  | } | 
|  |  | 
|  | IndexedTriangleMesh2d<vec2> NewFlatRectangleMesh(vec2 origin, vec2 extent, vec2 top_left_uv, | 
|  | vec2 bottom_right_uv) { | 
|  | MeshSpec spec{MeshAttribute::kPosition2D | MeshAttribute::kUV}; | 
|  | IndexedTriangleMesh2d<vec2> mesh; | 
|  |  | 
|  | const size_t vertex_count = 4; | 
|  | const size_t index_count = 6; | 
|  |  | 
|  | mesh.resize_indices(index_count); | 
|  | mesh.resize_vertices(vertex_count); | 
|  |  | 
|  | vec2* pos = mesh.positions.data(); | 
|  | vec2* uv = mesh.attributes1.data(); | 
|  | auto* indices = mesh.indices.data(); | 
|  |  | 
|  | // Positions. Start from the bottom left-hand corner | 
|  | // and wind counterclockwise. | 
|  | pos[0] = vec2(origin.x, origin.y + extent.y); | 
|  | pos[1] = origin + extent; | 
|  | pos[2] = vec2(origin.x + extent.x, origin.y); | 
|  | pos[3] = origin; | 
|  |  | 
|  | uv[0] = vec2(top_left_uv.x, bottom_right_uv.y); | 
|  | uv[1] = bottom_right_uv; | 
|  | uv[2] = vec2(bottom_right_uv.x, top_left_uv.y); | 
|  | uv[3] = top_left_uv; | 
|  |  | 
|  | indices[0] = 0; | 
|  | indices[1] = 1; | 
|  | indices[2] = 2; | 
|  | indices[3] = 0; | 
|  | indices[4] = 2; | 
|  | indices[5] = 3; | 
|  |  | 
|  | return mesh; | 
|  | } | 
|  |  | 
|  | // Constructs an axis-aligned unit cube mesh. | 
|  | IndexedTriangleMesh3d<vec2> NewCubeIndexedTriangleMesh(const MeshSpec& spec) { | 
|  | FX_DCHECK((spec == MeshSpec{.attributes = {MeshAttribute::kPosition3D, MeshAttribute::kUV}})); | 
|  | IndexedTriangleMesh3d<vec2> mesh; | 
|  |  | 
|  | const size_t vertex_count = 8;  // Four in front, four in back. | 
|  | const size_t index_count = 36;  // 6 faces * 2 triangles * 3 verts. | 
|  |  | 
|  | mesh.resize_indices(index_count); | 
|  | mesh.resize_vertices(vertex_count); | 
|  |  | 
|  | vec3* pos = mesh.positions.data(); | 
|  | vec2* uv = mesh.attributes1.data(); | 
|  | auto* indices = mesh.indices.data(); | 
|  |  | 
|  | // Front four verts. | 
|  | pos[0] = vec3(0, 0, 0); | 
|  | pos[1] = vec3(1, 0, 0); | 
|  | pos[2] = vec3(1, 1, 0); | 
|  | pos[3] = vec3(0, 1, 0); | 
|  |  | 
|  | // Back four verts. | 
|  | pos[4] = vec3(0, 1, 1); | 
|  | pos[5] = vec3(1, 1, 1); | 
|  | pos[6] = vec3(1, 0, 1); | 
|  | pos[7] = vec3(0, 0, 1); | 
|  |  | 
|  | // TODO(fxbug.dev/7307): Add separate box mesh type with split verts and proper uv coords. Since | 
|  | // this box is currently only being used for wireframe rendering, it doesn't need texcoords. | 
|  | for (size_t t = 0; t < vertex_count; t++) { | 
|  | uv[t] = vec2(0.f, 0.f); | 
|  | } | 
|  |  | 
|  | size_t index = 0; | 
|  | auto AddIndex = [&index, &indices](uint32_t val) { | 
|  | indices[index] = val; | 
|  | index++; | 
|  | }; | 
|  |  | 
|  | auto AddTriangle = [&AddIndex](uint32_t val1, uint32_t val2, uint32_t val3) { | 
|  | AddIndex(val1); | 
|  | AddIndex(val2); | 
|  | AddIndex(val3); | 
|  | }; | 
|  |  | 
|  | // Front face. | 
|  | AddTriangle(0, 1, 2); | 
|  | AddTriangle(0, 2, 3); | 
|  |  | 
|  | // Top face. | 
|  | AddTriangle(2, 4, 3); | 
|  | AddTriangle(2, 5, 4); | 
|  |  | 
|  | // Right face. | 
|  | AddTriangle(1, 5, 2); | 
|  | AddTriangle(1, 5, 6); | 
|  |  | 
|  | // Left face. | 
|  | AddTriangle(0, 4, 7); | 
|  | AddTriangle(0, 3, 4); | 
|  |  | 
|  | // Back face. | 
|  | AddTriangle(5, 7, 4); | 
|  | AddTriangle(5, 6, 7); | 
|  |  | 
|  | // Bottom face. | 
|  | AddTriangle(0, 7, 6); | 
|  | AddTriangle(0, 6, 1); | 
|  |  | 
|  | return mesh; | 
|  | } | 
|  |  | 
|  | MeshPtr NewCircleMesh(MeshBuilderFactory* factory, BatchGpuUploader* gpu_uploader, | 
|  | const MeshSpec& spec, int subdivisions, vec2 center, float radius, | 
|  | float offset_magnitude) { | 
|  | // Compute the number of vertices in the tessellated circle. | 
|  | FX_DCHECK(subdivisions >= 0); | 
|  | FX_DCHECK(spec.IsValidOneBufferMesh()); | 
|  | uint32_t outer_vertex_count = 4; | 
|  | while (subdivisions-- > 0) { | 
|  | outer_vertex_count *= 2; | 
|  | } | 
|  |  | 
|  | uint32_t vertex_count = outer_vertex_count + 1;  // Add 1 for center vertex. | 
|  | uint32_t index_count = outer_vertex_count * 3; | 
|  |  | 
|  | auto builder = factory->NewMeshBuilder(gpu_uploader, 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. | 
|  | FX_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 = static_cast<float>(1. / outer_vertex_count); | 
|  | const float radian_step = static_cast<float>(2. * M_PI / outer_vertex_count); | 
|  | for (uint32_t i = 0; i < outer_vertex_count; ++i) { | 
|  | float radians = static_cast<float>(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) = static_cast<float>(i) * outer_vertex_count_reciprocal; | 
|  |  | 
|  | builder->AddVertexData(vertex, builder->vertex_stride()); | 
|  | } | 
|  |  | 
|  | // Vertex indices. | 
|  | for (uint32_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(); | 
|  | FX_DCHECK(mesh->num_indices() == index_count); | 
|  | FX_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, BatchGpuUploader* gpu_uploader, | 
|  | 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. | 
|  | FX_DCHECK(subdivisions >= 0); | 
|  | FX_DCHECK(spec.IsValidOneBufferMesh()); | 
|  | uint32_t outer_vertex_count = 4; | 
|  | while (subdivisions-- > 0) { | 
|  | outer_vertex_count *= 2; | 
|  | } | 
|  |  | 
|  | uint32_t vertex_count = outer_vertex_count * 2; | 
|  | uint32_t index_count = outer_vertex_count * 6; | 
|  |  | 
|  | auto builder = factory->NewMeshBuilder(gpu_uploader, 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); | 
|  | FX_CHECK(vertex_p.pos2); | 
|  |  | 
|  | const float outer_vertex_count_reciprocal = static_cast<float>(1. / outer_vertex_count); | 
|  | const float radian_step = static_cast<float>(2. * M_PI / outer_vertex_count); | 
|  | for (uint32_t i = 0; i < outer_vertex_count; ++i) { | 
|  | float radians = static_cast<float>(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(fxbug.dev/7199): 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) = static_cast<float>(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 (uint32_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(); | 
|  | FX_DCHECK(mesh->num_indices() == index_count); | 
|  | FX_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 NewRectangleMesh(MeshBuilderFactory* factory, BatchGpuUploader* gpu_uploader, | 
|  | const MeshSpec& spec, int subdivisions, vec2 extent, vec2 top_left, | 
|  | float top_offset_magnitude, float bottom_offset_magnitude) { | 
|  | // Compute the number of vertices in the tessellated circle. | 
|  | FX_DCHECK(subdivisions >= 0); | 
|  | uint32_t vertices_per_side = 2; | 
|  | while (subdivisions-- > 0) { | 
|  | vertices_per_side *= 2; | 
|  | } | 
|  |  | 
|  | uint32_t vertex_count = vertices_per_side * 2; | 
|  | uint32_t index_count = (vertices_per_side - 1) * 6; | 
|  |  | 
|  | auto builder = factory->NewMeshBuilder(gpu_uploader, 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); | 
|  | FX_CHECK(vertex_p.pos2); | 
|  |  | 
|  | const float vertices_per_side_reciprocal = 1.f / static_cast<float>(vertices_per_side - 1); | 
|  | for (uint32_t i = 0; i < vertices_per_side; ++i) { | 
|  | // Build bottom vertex. | 
|  | (*vertex_p.pos2) = | 
|  | top_left + vec2(extent.x * static_cast<float>(i) * vertices_per_side_reciprocal, extent.y); | 
|  | if (vertex_p.uv) | 
|  | (*vertex_p.uv) = vec2(static_cast<float>(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) = static_cast<float>(i) * vertices_per_side_reciprocal; | 
|  | builder->AddVertexData(vertex, builder->vertex_stride()); | 
|  |  | 
|  | // Build top vertex. | 
|  | (*vertex_p.pos2) = | 
|  | top_left + vec2(extent.x * static_cast<float>(i) * vertices_per_side_reciprocal, 0); | 
|  | if (vertex_p.uv) | 
|  | (*vertex_p.uv) = vec2(static_cast<float>(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) = static_cast<float>(i) * vertices_per_side_reciprocal; | 
|  | builder->AddVertexData(vertex, builder->vertex_stride()); | 
|  | } | 
|  |  | 
|  | // Generate vertex indices. | 
|  | for (uint32_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(); | 
|  | FX_DCHECK(mesh->num_indices() == index_count); | 
|  | return mesh; | 
|  | } | 
|  |  | 
|  | MeshPtr NewFullScreenMesh(MeshBuilderFactory* factory, BatchGpuUploader* gpu_uploader) { | 
|  | 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(gpu_uploader, 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, BatchGpuUploader* gpu_uploader, | 
|  | const MeshSpec& spec, int subdivisions, vec3 center, float radius) { | 
|  | FX_DCHECK(subdivisions >= 0); | 
|  | FX_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(gpu_uploader, spec, vertex_count, triangle_count * 3); | 
|  | constexpr size_t kMaxVertexSize = 100; | 
|  | uint8_t vertex[kMaxVertexSize]; | 
|  | auto vertex_p = GetVertexAttributePointers(vertex, kMaxVertexSize, spec, builder); | 
|  | FX_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(fxbug.dev/7329): 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. | 
|  | FX_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(fxbug.dev/7329): 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; | 
|  | FX_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 = static_cast<uint32_t>(builder->vertex_count()) - 1; | 
|  | tri[2] = new_ind; | 
|  | builder->AddTriangle(ind1, ind2, new_ind).AddTriangle(ind2, ind0, new_ind); | 
|  | } | 
|  | } | 
|  | return builder->Build(); | 
|  | } | 
|  |  | 
|  | // Helper function that returns a standard mesh used for testing.  It looks like | 
|  | // like this in the standard Vulkan coordinate system (positive y down). | 
|  | //     (-1,-1) _______ (1,-1) | 
|  | //           /\      /\                        3    4 | 
|  | //         /   \   /   \         indices: | 
|  | //       /______\/______\                   0    1    2 | 
|  | //  (-2,1)    (0,1)     (2,1) | 
|  | IndexedTriangleMesh2d<vec2> GetStandardTestMesh2d() { | 
|  | return IndexedTriangleMesh2d<vec2>{ | 
|  | .indices = {0, 1, 3, 3, 1, 4, 4, 1, 2}, | 
|  | .positions = {vec2(-2, 1), vec2(0, 1), vec2(2, 1), vec2(-1, -1), vec2(1, -1)}, | 
|  | .attributes1 = {vec2(0, 1), vec2(0.5f, 1), vec2(1, 1), vec2(0, 0), vec2(1, 0)}}; | 
|  | } | 
|  |  | 
|  | IndexedTriangleMesh3d<vec2> GetStandardTestMesh3d() { | 
|  | auto mesh2d = GetStandardTestMesh2d(); | 
|  |  | 
|  | IndexedTriangleMesh3d<vec2> mesh3d; | 
|  | mesh3d.indices = mesh2d.indices; | 
|  | mesh3d.attributes1 = mesh2d.attributes1; | 
|  | for (const vec2& pos : mesh2d.positions) { | 
|  | mesh3d.positions.push_back(vec3(pos, 11)); | 
|  | } | 
|  | return mesh3d; | 
|  | } | 
|  |  | 
|  | }  // namespace escher |