diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fd7ef6b..d5cee80 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,6 +3,7 @@ set(HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/colour.h ${CMAKE_CURRENT_SOURCE_DIR}/fbo.h ${CMAKE_CURRENT_SOURCE_DIR}/mesh.h + ${CMAKE_CURRENT_SOURCE_DIR}/mesh_generator.h ${CMAKE_CURRENT_SOURCE_DIR}/peripherals.h ${CMAKE_CURRENT_SOURCE_DIR}/peripherals_glfw.h ${CMAKE_CURRENT_SOURCE_DIR}/shader.h @@ -15,6 +16,7 @@ set(SOURCE ${SOURCE} ${CMAKE_CURRENT_SOURCE_DIR}/fbo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mesh.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mesh_generator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/peripherals_glfw.cpp ${CMAKE_CURRENT_SOURCE_DIR}/shader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/texture.cpp diff --git a/src/mesh_generator.cpp b/src/mesh_generator.cpp new file mode 100644 index 0000000..d3cd498 --- /dev/null +++ b/src/mesh_generator.cpp @@ -0,0 +1,606 @@ +#include "mesh_generator.h" +#include "glm/gtx/rotate_vector.hpp" +#include + +BEGIN_NAMESPACE + +static void add_vertex(mesh &m, float x, float y, float z) { + vertex v{}; + v.position.x = x; + v.position.y = y; + v.position.z = z; + m.vertices.push_back(v); +} + +static void add_facet(mesh &m, size_t a, size_t b, size_t c, + float u1, float v1, + float u2, float v2, + float u3, float v3, + bool edge1_visible, + bool edge2_visible, + bool edge3_visible, + float nx = 0, float ny = 0, float nz = 0) { + facet f{}; + f.a = a; + f.b = b; + f.c = c; + f.text_coords[0] = glm::vec2(u1, v1); + f.text_coords[1] = glm::vec2(u2, v2); + f.text_coords[2] = glm::vec2(u3, v3); + f.normal.x = nx; + f.normal.y = ny; + f.normal.z = nz; + m.facets.push_back(f); + + if (edge1_visible) m.edges.push_back({a, b}); + if (edge2_visible) m.edges.push_back({b, c}); + if (edge3_visible) m.edges.push_back({c, a}); +} + +std::shared_ptr mesh_generator::cube() { + mesh *result = new mesh; + + add_vertex(*result, -0.5, -0.5, -0.5); + add_vertex(*result, 0.5, -0.5, -0.5); + add_vertex(*result, 0.5, 0.5, -0.5); + add_vertex(*result, -0.5, 0.5, -0.5); + + add_vertex(*result, -0.5, -0.5, 0.5); + add_vertex(*result, 0.5, -0.5, 0.5); + add_vertex(*result, 0.5, 0.5, 0.5); + add_vertex(*result, -0.5, 0.5, 0.5); + + // back + add_facet(*result, 0, 2, 1, 1, 0, 0, 1, 0, 0, false, true, true, 0, 0, -1); + add_facet(*result, 2, 0, 3, 0, 1, 1, 0, 1, 1, false, true, true, 0, 0, -1); + + // front + add_facet(*result, 5, 7, 4, 1, 0, 0, 1, 0, 0, false, true, true, 0, 0, 1); + add_facet(*result, 7, 5, 6, 0, 1, 1, 0, 1, 1, false, true, true, 0, 0, 1); + + // right side + add_facet(*result, 1, 6, 5, 1, 0, 0, 1, 0, 0, false, true, true, 1, 0, 0); + add_facet(*result, 6, 1, 2, 0, 1, 1, 0, 1, 1, false, true, true, 1, 0, 0); + + // left side + add_facet(*result, 4, 3, 0, 1, 0, 0, 1, 0, 0, false, true, true, -1, 0, 0); + add_facet(*result, 3, 4, 7, 0, 1, 1, 0, 1, 1, false, true, true, -1, 0, 0); + + // bottom + add_facet(*result, 4, 1, 5, 1, 0, 0, 1, 0, 0, false, true, true, 0, -1, 0); + add_facet(*result, 1, 4, 0, 0, 1, 1, 0, 1, 1, false, true, true, 0, -1, 0); + + // top + add_facet(*result, 3, 6, 2, 1, 0, 0, 1, 0, 0, false, true, true, 0, 1, 0); + add_facet(*result, 6, 3, 7, 0, 1, 1, 0, 1, 1, false, true, true, 0, 1, 0); + + calculate_normals(*result); + return std::shared_ptr(result); +} + + +std::shared_ptr mesh_generator::sphere(int lon_points, int lat_points) { + mesh *result = new mesh; + + vertex v{}; + facet f{}; + edge edge{}; + + double x, y, z; + + double du = 1.0 / static_cast(lon_points); + double dv = 1.0 / static_cast(lat_points + 1); + + for (int j = 0; j < lat_points; j++) { + + y = 1.0 - (j + 1) * 2.0 / static_cast(lat_points + 1); + double current_circle_radius = sin(M_PI * (j + 1) / static_cast(lat_points + 1)); + + for (int i = 0; i < lon_points; i++) { + + x = current_circle_radius * cos((i * 2.0 * M_PI) / static_cast(lon_points)); + z = current_circle_radius * sin((i * 2.0 * M_PI) / static_cast(lon_points)); + + v.position = glm::normalize(glm::vec3(x, y, z)); + result->vertices.push_back(v); + + if (j > 0) { + int current_vertex_index = j * lon_points + i; + int new_vertex_index = j * lon_points + ((i + 1) % lon_points); + int top_vertex_index = (j - 1) * lon_points + i; + int top_next_vertex_index = (j - 1) * lon_points + ((i + 1) % lon_points); + + glm::vec2 current_uv = glm::vec2(i * du, (j + 2) * dv); + glm::vec2 next_uv = glm::vec2((i + 1) * du, (j + 2) * dv); + glm::vec2 top_uv = glm::vec2(i * du, (j + 1) * dv); + glm::vec2 top_next_uv = glm::vec2((i + 1) * du, (j + 1) * dv); + + f.a = current_vertex_index; + f.b = top_vertex_index; + f.c = new_vertex_index; + + f.text_coords[0] = current_uv; + f.text_coords[1] = top_uv; + f.text_coords[2] = next_uv; + + result->facets.push_back(f); + + f.a = new_vertex_index; + f.b = top_vertex_index; + f.c = top_next_vertex_index; + + f.text_coords[0] = next_uv; + f.text_coords[1] = top_uv; + f.text_coords[2] = top_next_uv; + + result->facets.push_back(f); + + edge.from = top_vertex_index; + edge.to = top_next_vertex_index; + result->edges.push_back(edge); + + edge.from = top_vertex_index; + edge.to = current_vertex_index; + result->edges.push_back(edge); + } + } + } + + // top capping + v.position = glm::vec3(0, 1.0, 0); + result->vertices.push_back(v); + + int top_vertex_index = lon_points * lat_points; + for (int i = 0; i < lon_points; i++) { + + int current_vertex_index = i; + int new_vertex_index = (current_vertex_index + 1) % lon_points; + + glm::vec2 current_uv = glm::vec2(i * du, dv); + glm::vec2 next_uv = glm::vec2((i + 1) * du, dv); + glm::vec2 top_uv = glm::vec2((i + 0.5) * du, 0); + + f.text_coords[0] = current_uv; + f.text_coords[1] = next_uv; + f.text_coords[2] = top_uv; + + f.a = current_vertex_index; + f.c = new_vertex_index; + f.b = top_vertex_index; + result->facets.push_back(f); + + edge.from = top_vertex_index; + edge.to = current_vertex_index; + result->edges.push_back(edge); + } + + // bottom capping + v.position = glm::vec3(0, -1.0, 0); + result->vertices.push_back(v); + + int bottom_vertex_index = top_vertex_index + 1; + for (int i = 0; i < lon_points; i++) { + + int current_vertex_index = i + (lat_points - 1) * lon_points; + int next_vertex_index = (i + 1) % lon_points + (lat_points - 1) * lon_points; + + glm::vec2 current_uv = glm::vec2(i * du, 1.0 - dv); + glm::vec2 next_uv = glm::vec2((i + 1) * du, 1.0 - dv); + glm::vec2 bottom_uv = glm::vec2((i + 0.5) * du, 1.0); + + f.text_coords[0] = current_uv; + f.text_coords[1] = bottom_uv; + f.text_coords[2] = next_uv; + + f.a = current_vertex_index; + f.c = bottom_vertex_index; + f.b = next_vertex_index; + result->facets.push_back(f); + + edge.from = current_vertex_index; + edge.to = next_vertex_index; + result->edges.push_back(edge); + + edge.from = current_vertex_index; + edge.to = bottom_vertex_index; + result->edges.push_back(edge); + } + + calculate_normals(*result); + return std::shared_ptr(result); +} + +std::shared_ptr mesh_generator::grid(int x_segments, int y_segments) { + mesh *result = new mesh; + for (int y = 0; y < (y_segments + 1); y++) { + float y_pos = (static_cast(y) / static_cast(y_segments)) - 0.5f; + for (int x = 0; x < (x_segments + 1); x++) { + float x_pos = (static_cast(x) / static_cast(x_segments)) - 0.5f; + add_vertex(*result, x_pos, y_pos, 0); + } + } + + for (int y = 0; y < y_segments; y++) { + for (int x = 0; x < x_segments; x++) { + int current = x + (y * (y_segments + 1)); + int next = current + 1; + int bottom = next + x_segments; + int bottom_next = bottom + 1; + add_facet(*result, current, bottom, next, + result->vertices[current].position.x + 0.5f, + 0.5f - result->vertices[current].position.y, + result->vertices[bottom].position.x + 0.5f, + 0.5f - result->vertices[bottom].position.y, + result->vertices[next].position.x + 0.5f, + 0.5f - result->vertices[next].position.y, + true, false, true); + + add_facet(*result, next, bottom, bottom_next, + result->vertices[next].position.x + 0.5f, + 0.5f - result->vertices[next].position.y, + result->vertices[bottom].position.x + 0.5f, + 0.5f - result->vertices[bottom].position.y, + result->vertices[bottom_next].position.x + 0.5f, + 0.5f - result->vertices[bottom_next].position.y, + false, true, true); + } + } + + calculate_normals(*result); + return std::shared_ptr(result); +} + +std::shared_ptr mesh_generator::cylinder(int x_segments, int y_segments, bool cap_top, bool cap_bottom) { + mesh *result = new mesh; + for (int y = 0; y <= y_segments; y++) { + float yPos = 1.0f - (static_cast(y) / static_cast(y_segments)) - 0.5f; + for (int x = 0; x <= x_segments; x++) { + float xPos = 0.5f * sinf((static_cast(x) / static_cast(x_segments * 2.0 * M_PI))); + float zPos = 0.5f * cosf((static_cast(x) / static_cast(x_segments * 2.0 * M_PI))); + add_vertex(*result, xPos, yPos, zPos); + } + } + + float du = 1.0f / static_cast(x_segments); + float dv = 1.0f / static_cast(y_segments + 2); // +2 because of capping + + for (int y = 0; y < y_segments; y++) { + for (int x = 0; x < x_segments; x++) { + int current = x + (y * (y_segments + 1)); + int next = current + 1; + int bottom = next + x_segments; + int bottomNext = bottom + 1; + add_facet(*result, current, bottom, next, + static_cast(x) * du, + static_cast(y + 1) * dv, + static_cast(x) * du, + static_cast(y + 2) * dv, + static_cast(x + 1) * du, + static_cast(y + 1) * dv, + true, false, true); + + add_facet(*result, next, bottom, bottomNext, + static_cast(x + 1) * du, + static_cast(y + 1) * dv, + static_cast(x) * du, + static_cast(y + 2) * dv, + static_cast(x + 1) * du, + static_cast(y + 2) * dv, + false, true, true); + } + } + + if (cap_top) { + int topCapIndex = (int) result->vertices.size(); + add_vertex(*result, 0, 0.5f, 0); + + for (int x = 0; x < x_segments; x++) { + add_facet(*result, x, x + 1, topCapIndex, + static_cast(x) * du, + 1.0f - dv, + static_cast(x + 1) * du, + 1.0f - dv, + (static_cast(x) + 0.5f) * du, + 1.0f, + false, false, true); + } + } + + if (cap_bottom) { + int bottomCapIndex = (int) result->vertices.size(); + add_vertex(*result, 0, -0.5f, 0); + + int offsetOfLastRing = (x_segments + 1) * y_segments; + + for (int x = 0; x < x_segments; x++) { + add_facet(*result, x + 1 + offsetOfLastRing, x + offsetOfLastRing, bottomCapIndex, + static_cast(x + 1) * du, + dv, + static_cast(x) * du, + dv, + static_cast(x + 0.5f) * du, + 0, + false, false, true); + } + } + + calculate_normals(*result); + return std::shared_ptr(result); +} + +std::shared_ptr mesh_generator::cog(float wheel_inner_diameter, + float wheel_outer_diameter, + int wheel_contour_segments, + int number_of_teeth, + float tooth_height, + float tooth_width_base, + float tooth_width_top, + float cog_depth) { + + std::shared_ptr mesh = mesh_generator::revolve({ + {-wheel_inner_diameter / 2.0f, cog_depth / 2.0f}, + {-wheel_outer_diameter / 2.0f, cog_depth / 2.0f}, + {-wheel_outer_diameter / 2.0f, -cog_depth / + 2.0f}, + {-wheel_inner_diameter / 2.0f, -cog_depth / + 2.0f}, + {-wheel_inner_diameter / 2.0f, cog_depth / 2.0f} + }, wheel_contour_segments); + return mesh; + +// return MeshGenerator::extrude(MeshGenerator::sphere(20, 20), {100, 101, 200, 201, 400, 401}, 0.05, 20); +} + +std::shared_ptr mesh_generator::revolve(const std::vector &contour, int rotation_steps) { + mesh *result = new mesh; + + // create vertices by revolving the contours + for (int i = 0; i < rotation_steps; i++) { + for (auto v: contour) { + add_vertex(*result, + (float) (v.x * cos(i * 2.0 * M_PI / rotation_steps)), + v.y, + (float) (v.x * sin(i * 2.0 * M_PI / rotation_steps))); + } + } + + // create facets by linking between the revolved pieces + int number_of_contour_vertices = (int) contour.size(); + for (int i = 0; i < rotation_steps; i++) { + int current_rotation_index = i; + int next_rotation_index = (i + 1) % rotation_steps; + + for (int j = 0; j < (number_of_contour_vertices - 1); j++) { + int a = current_rotation_index * number_of_contour_vertices + j; + int b = current_rotation_index * number_of_contour_vertices + j + 1; + int c = next_rotation_index * number_of_contour_vertices + j + 1; + int d = next_rotation_index * number_of_contour_vertices + j; + + add_facet(*result, a, b, c, 0, 0, 0, 0, 0, 0, false, true, false); + add_facet(*result, c, d, a, 0, 0, 0, 0, 0, 0, false, true, false); + } + } + + calculate_normals(*result); + flip_normals(*result); + return std::shared_ptr(result); +} + +std::shared_ptr mesh_generator::extrude(const std::shared_ptr &m, + std::vector facets_to_extrude, + float extrusion_height, + int times) { + + mesh *result = new mesh; + result->copy(*m); + + glm::vec3 normal_offset = glm::vec3(0); + for (int i = 0; i < times; i++) { + std::vector new_facets_to_extrude; + for (int facetIndex: facets_to_extrude) { + new_facets_to_extrude.push_back( + extrude_face(result, result->facets[facetIndex], extrusion_height, normal_offset)); + } + facets_to_extrude = new_facets_to_extrude; + normal_offset += glm::vec3(1, 0, 0); + calculate_normals(*result); + } + +// flip_normals(*result); + return std::shared_ptr(result); +} + +// returns the index of the extruded face cap +int mesh_generator::extrude_face(mesh *m, facet facet, float amount, glm::vec3 normal_offset) { + + size_t new_a = m->vertices.size(); + size_t new_b = m->vertices.size() + 1; + size_t new_c = m->vertices.size() + 2; + + glm::mat4 normal_rotation_matrix = glm::rotate(0.2f, glm::normalize(glm::vec3(2, 1, 1))); + + for (unsigned long v: facet.vertices) { + glm::vec3 new_vertex = m->vertices[v].position + amount * facet.normal; + new_vertex = glm::vec3(glm::vec4(new_vertex, 1.0f) * normal_rotation_matrix); + add_vertex(*m, new_vertex.x, new_vertex.y, new_vertex.z); + } + + // AB extrusion plan + add_facet(*m, + facet.a, facet.b, new_b, + facet.text_coords[0].x, + facet.text_coords[0].y, + facet.text_coords[1].x, + facet.text_coords[1].y, + facet.text_coords[1].x, + facet.text_coords[1].y, + true, true, true); + + add_facet(*m, + new_b, new_a, facet.a, + facet.text_coords[1].x, + facet.text_coords[1].y, + facet.text_coords[0].x, + facet.text_coords[0].y, + facet.text_coords[0].x, + facet.text_coords[0].y, + true, true, true); + + // BC extrusion plan + add_facet(*m, + facet.b, facet.c, new_c, + facet.text_coords[1].x, + facet.text_coords[1].y, + facet.text_coords[2].x, + facet.text_coords[2].y, + facet.text_coords[2].x, + facet.text_coords[2].y, + true, true, true); + + add_facet(*m, + new_c, new_b, facet.b, + facet.text_coords[2].x, + facet.text_coords[2].y, + facet.text_coords[1].x, + facet.text_coords[1].y, + facet.text_coords[1].x, + facet.text_coords[1].y, + true, true, true); + + // CA extrusion plan + add_facet(*m, + facet.c, facet.a, new_a, + facet.text_coords[2].x, + facet.text_coords[2].y, + facet.text_coords[0].x, + facet.text_coords[0].y, + facet.text_coords[0].x, + facet.text_coords[0].y, + true, true, true); + + add_facet(*m, + new_a, new_c, facet.c, + facet.text_coords[0].x, + facet.text_coords[0].y, + facet.text_coords[2].x, + facet.text_coords[2].y, + facet.text_coords[2].x, + facet.text_coords[2].y, + true, true, true); + + // facet replica on top + add_facet(*m, + new_a, new_b, new_c, + facet.text_coords[0].x, + facet.text_coords[0].y, + facet.text_coords[1].x, + facet.text_coords[1].y, + facet.text_coords[2].x, + facet.text_coords[2].y, + true, true, true); + + return (int) (m->facets.size() - 1); +} + +void map_transform(std::shared_ptr mesh, + texture_generator &texgen, + uint8_t layer, + uint8_t channel, + float effect_intensity) { + + std::vector original_vertices(mesh->vertices); + + uint8_t *data = texgen.get(layer); + + for (auto &f: mesh->facets) { + for (int i = 0; i < 3; i++) { + uint16_t u = static_cast(f.text_coords[i].x * static_cast(texgen.width)) % texgen.width; + uint16_t v = static_cast(f.text_coords[i].y * static_cast(texgen.height)) % texgen.height; + + uint8_t map_value = data[(u + v * texgen.width) * 4 + channel]; + float normalized_value_in_map = static_cast(map_value) / 255.0f; + + mesh->vertices[f.vertices[i]].position = original_vertices[f.vertices[i]].position + + (mesh->vertices[f.vertices[i]].normal * normalized_value_in_map * + effect_intensity); + } + } +} + +uint32_t index_in_map(uint16_t from, uint16_t to) { + return from < to ? + from + (static_cast(to) << 16) : + to + (static_cast(from) << 16); +} + +void subdivide(const std::shared_ptr &m) { + vertex v{}; + + // we need to keep the old facets because we're modifying the new ones as we go + std::vector old_facets = m->facets; + + // key is encoded as from/to edge, 16 bits each, from in lower 16 bits, to in upper 16 bits + // "from" is always smaller than "to" + std::map subdivided_vertices; + + int facet_index = 0; + for (auto &f: old_facets) { + for (int j = 0; j < 3; j++) { + unsigned int from = f.vertices[j]; + unsigned int to = f.vertices[(j + 1) % 3]; + + // make sure all subdivided vertices are unique and not duplicated + if (subdivided_vertices[index_in_map(from, to)] == 0) { + subdivided_vertices[index_in_map(from, to)] = m->vertices.size(); + v.position = (m->vertices[from].position + m->vertices[to].position) / 2.0f; + m->vertices.push_back(v); + } + } + + // add new facets + add_facet(*m, + subdivided_vertices[index_in_map(f.vertices[0], f.vertices[1])], + f.vertices[1], + subdivided_vertices[index_in_map(f.vertices[1], f.vertices[2])], + (f.text_coords[0].x + f.text_coords[1].x) / 2.0f, (f.text_coords[0].y + f.text_coords[1].y) / 2.0f, + f.text_coords[1].x, f.text_coords[1].y, + (f.text_coords[1].x + f.text_coords[2].x) / 2.0f, (f.text_coords[1].y + f.text_coords[2].y) / 2.0f, + false, false, false); + + add_facet(*m, + subdivided_vertices[index_in_map(f.vertices[1], f.vertices[2])], + f.vertices[2], + subdivided_vertices[index_in_map(f.vertices[2], f.vertices[0])], + (f.text_coords[1].x + f.text_coords[2].x) / 2.0f, (f.text_coords[1].y + f.text_coords[2].y) / 2.0f, + f.text_coords[2].x, f.text_coords[2].y, + (f.text_coords[2].x + f.text_coords[0].x) / 2.0f, (f.text_coords[2].y + f.text_coords[0].y) / 2.0f, + false, false, false); + + add_facet(*m, + subdivided_vertices[index_in_map(f.vertices[2], f.vertices[0])], + f.vertices[0], + subdivided_vertices[index_in_map(f.vertices[0], f.vertices[1])], + (f.text_coords[2].x + f.text_coords[0].x) / 2.0f, (f.text_coords[2].y + f.text_coords[0].y) / 2.0f, + f.text_coords[0].x, f.text_coords[0].y, + (f.text_coords[0].x + f.text_coords[1].x) / 2.0f, (f.text_coords[0].y + f.text_coords[1].y) / 2.0f, + false, false, false); + + // modify current face to make it center of the subdivision + m->facets[facet_index].vertices[0] = subdivided_vertices[index_in_map(f.vertices[0], f.vertices[1])]; + m->facets[facet_index].vertices[1] = subdivided_vertices[index_in_map(f.vertices[1], f.vertices[2])]; + m->facets[facet_index].vertices[2] = subdivided_vertices[index_in_map(f.vertices[2], f.vertices[0])]; + + m->facets[facet_index].text_coords[0] = + (old_facets[facet_index].text_coords[0] + old_facets[facet_index].text_coords[1]) / 2.0f; + m->facets[facet_index].text_coords[1] = + (old_facets[facet_index].text_coords[1] + old_facets[facet_index].text_coords[2]) / 2.0f; + m->facets[facet_index].text_coords[2] = + (old_facets[facet_index].text_coords[2] + old_facets[facet_index].text_coords[0]) / 2.0f; + + facet_index++; + } + + calculate_normals(*m); +} + +END_NAMESPACE \ No newline at end of file diff --git a/src/mesh_generator.h b/src/mesh_generator.h new file mode 100644 index 0000000..03fe343 --- /dev/null +++ b/src/mesh_generator.h @@ -0,0 +1,37 @@ +#pragma once + +#include "mesh.h" +#include "texture_generator.h" + +BEGIN_NAMESPACE + +struct mesh_generator { + + static std::shared_ptr cube(); + static std::shared_ptr sphere(int lon_points, int lat_points); + static std::shared_ptr grid(int x_segments, int y_segments); + static std::shared_ptr cylinder(int x_segments, int y_segments, bool cap_top, bool cap_botton); + static std::shared_ptr revolve(const std::vector &contour, int rotation_steps); + static std::shared_ptr extrude(const std::shared_ptr &mesh, + std::vector facets_to_extrude, + float extrusion_height, + int times); + static std::shared_ptr cog(float wheel_inner_diameter, + float wheel_outer_diameter, + int wheel_contour_segments, + int number_of_teeth, + float tooth_height, + float tooth_width_base, + float tooth_width_top, + float cog_depth); + static int extrude_face(mesh *m, facet facet, float amount, glm::vec3 normal_offset); +}; + +void map_transform(std::shared_ptr mesh, + texture_generator &texgen, + uint8_t layer, + uint8_t channel, + float effect_intensity); +void subdivide(const std::shared_ptr &mesh); + +END_NAMESPACE