/******************************************************************** * Copyright (C) 2015 Liangliang Nan <liangliang.nan@gmail.com> * https://3d.bk.tudelft.nl/liangliang/ * * This file is part of Easy3D. If it is useful in your research/work, * I would be grateful if you show your appreciation by citing it: * ------------------------------------------------------------------ * Liangliang Nan. * Easy3D: a lightweight, easy-to-use, and efficient C++ library * for processing and rendering 3D data. * Journal of Open Source Software, 6(64), 3255, 2021. * ------------------------------------------------------------------ * * Easy3D is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License Version 3 * as published by the Free Software Foundation. * * Easy3D is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ********************************************************************/ #include <easy3d/renderer/shape.h> #include <easy3d/renderer/opengl_error.h> #include <easy3d/renderer/shader_manager.h> #include <easy3d/renderer/shader_program.h> #include <easy3d/renderer/vertex_array_object.h> #include <easy3d/renderer/drawable_lines.h> #include <easy3d/renderer/opengl.h> #include <easy3d/algo/tessellator.h> #include <easy3d/util/setting.h> namespace easy3d { namespace shape { void draw_quad_wire(const Rect &rect, const vec4 &color, int width, int height, float depth) { const std::string name = "screen_space/screen_space_color"; auto program = ShaderManager::get_program(name); if (!program) { std::vector<ShaderProgram::Attribute> attributes = { ShaderProgram::Attribute(ShaderProgram::POSITION, "ndc_position") }; program = ShaderManager::create_program_from_files(name, attributes); } if (!program) { LOG_N_TIMES(3, ERROR) << "shader doesn't exist: " << name << ". " << COUNTER; return; } const float x0 = rect.x_min(); const float y0 = static_cast<float>(height) - rect.y_max() - 1; const float w = rect.width(); const float h = rect.height(); const float min_x = 2.0f * x0 / static_cast<float>(width) - 1.0f; const float min_y = 2.0f * y0 / static_cast<float>(height) - 1.0f; const float max_x = 2.0f * (x0 + w) / static_cast<float>(width) - 1.0f; const float max_y = 2.0f * (y0 + h) / static_cast<float>(height) - 1.0f; const std::vector<vec2> points = { vec2(min_x, min_y), vec2(max_x, min_y), vec2(max_x, max_y), vec2(min_x, max_y) }; unsigned int vertex_buffer = 0; VertexArrayObject vao; easy3d_debug_log_gl_error vao.create_array_buffer(vertex_buffer, ShaderProgram::POSITION, points.data(), points.size() * sizeof(vec2), 2, true); easy3d_debug_log_gl_error program->bind(); easy3d_debug_log_gl_error program->set_uniform("screen_color", color); easy3d_debug_log_gl_error program->set_uniform("depth", depth); vao.bind(); easy3d_debug_log_gl_error glDrawArrays(GL_LINE_LOOP, 0, static_cast<int>(points.size())); easy3d_debug_log_gl_error vao.release(); easy3d_debug_log_gl_error program->release(); easy3d_debug_log_gl_error VertexArrayObject::release_buffer(vertex_buffer); } void draw_quad_filled(const Rect &rect, const vec4 &color, int width, int height, float depth) { const std::string name = "screen_space/screen_space_color"; auto program = ShaderManager::get_program(name); if (!program) { std::vector<ShaderProgram::Attribute> attributes = { ShaderProgram::Attribute(ShaderProgram::POSITION, "ndc_position") }; program = ShaderManager::create_program_from_files(name, attributes); } if (!program) { LOG_N_TIMES(3, ERROR) << "shader doesn't exist: " << name << ". " << COUNTER; return; } const float x0 = rect.x_min(); const float y0 = static_cast<float>(height) - rect.y_max() - 1; const float w = rect.width(); const float h = rect.height(); const float min_x = 2.0f * x0 / static_cast<float>(width) - 1.0f; const float min_y = 2.0f * y0 / static_cast<float>(height) - 1.0f; const float max_x = 2.0f * (x0 + w) / static_cast<float>(width) - 1.0f; const float max_y = 2.0f * (y0 + h) / static_cast<float>(height) - 1.0f; const std::vector<vec2> points = { vec2(min_x, min_y), vec2(max_x, min_y), vec2(max_x, max_y), vec2(min_x, max_y) }; const std::vector<unsigned int> indices = {0, 1, 2, 0, 2, 3}; unsigned int vertex_buffer = 0, element_buffer = 0; VertexArrayObject vao; easy3d_debug_log_gl_error vao.create_array_buffer(vertex_buffer, ShaderProgram::POSITION, points.data(), points.size() * sizeof(vec2), 2, true); easy3d_debug_log_gl_error vao.create_element_buffer(element_buffer, indices.data(), indices.size() * sizeof(unsigned int), true); easy3d_debug_log_gl_error program->bind(); easy3d_debug_log_gl_error program->set_uniform("screen_color", color); easy3d_debug_log_gl_error program->set_uniform("depth", depth); vao.bind(); easy3d_debug_log_gl_error glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); easy3d_debug_log_gl_error glDrawElements(GL_TRIANGLES, static_cast<int>(indices.size()), GL_UNSIGNED_INT, nullptr); easy3d_debug_log_gl_error glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); easy3d_debug_log_gl_error vao.release(); easy3d_debug_log_gl_error program->release(); easy3d_debug_log_gl_error VertexArrayObject::release_buffer(vertex_buffer); VertexArrayObject::release_buffer(element_buffer); } void draw_quad_filled(const Rect &rect, unsigned int texture, int width, int height, float depth) { const std::string name = "screen_space/screen_space_texture"; auto program = ShaderManager::get_program(name); if (!program) { std::vector<ShaderProgram::Attribute> attributes = { ShaderProgram::Attribute(ShaderProgram::POSITION, "vtx_position"), ShaderProgram::Attribute(ShaderProgram::TEXCOORD, "tex_coord") }; program = ShaderManager::create_program_from_files(name, attributes); } if (!program) { LOG_N_TIMES(3, ERROR) << "shader doesn't exist: " << name << ". " << COUNTER; return; } const float x0 = rect.x_min(); const float y0 = static_cast<float>(height) - rect.y_max() - 1; const float w = rect.width(); const float h = rect.height(); const float min_x = 2.0f * x0 / static_cast<float>(width) - 1.0f; const float min_y = 2.0f * y0 / static_cast<float>(height) - 1.0f; const float max_x = 2.0f * (x0 + w) / static_cast<float>(width) - 1.0f; const float max_y = 2.0f * (y0 + h) / static_cast<float>(height) - 1.0f; const std::vector<vec2> points = { vec2(min_x, min_y), vec2(max_x, min_y), vec2(max_x, max_y), vec2(min_x, max_y) }; static const std::vector<vec2> texcoords = { vec2(0, 0), vec2(1.0, 0), vec2(1.0, 1.0), vec2(0, 1.0), }; static const std::vector<unsigned int> indices = {0, 1, 2, 0, 2, 3}; unsigned int vertex_buffer = 0, texcoord_buffer = 0, element_buffer = 0; VertexArrayObject vao; easy3d_debug_log_gl_error vao.create_array_buffer(vertex_buffer, ShaderProgram::POSITION, points.data(), points.size() * sizeof(vec2), 2, true); easy3d_debug_log_gl_error vao.create_array_buffer(texcoord_buffer, ShaderProgram::TEXCOORD, texcoords.data(), texcoords.size() * sizeof(vec2), 2, true); easy3d_debug_log_gl_error vao.create_element_buffer(element_buffer, indices.data(), indices.size() * sizeof(unsigned int), true); easy3d_debug_log_gl_error program->bind(); easy3d_debug_log_gl_error program->set_uniform("depth", depth); program->bind_texture("textureID", texture, 0); vao.bind(); easy3d_debug_log_gl_error glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); easy3d_debug_log_gl_error glDrawElements(GL_TRIANGLES, static_cast<int>(indices.size()), GL_UNSIGNED_INT, nullptr); easy3d_debug_log_gl_error glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); easy3d_debug_log_gl_error program->release_texture(); vao.release(); easy3d_debug_log_gl_error program->release(); easy3d_debug_log_gl_error VertexArrayObject::release_buffer(vertex_buffer); VertexArrayObject::release_buffer(texcoord_buffer); VertexArrayObject::release_buffer(element_buffer); } void draw_full_screen_quad(unsigned int texture, float depth) { const std::string name = "screen_space/screen_space_texture"; auto program = ShaderManager::get_program(name); if (!program) { std::vector<ShaderProgram::Attribute> attributes = { ShaderProgram::Attribute(ShaderProgram::POSITION, "vtx_position"), ShaderProgram::Attribute(ShaderProgram::TEXCOORD, "tex_coord") }; program = ShaderManager::create_program_from_files(name, attributes); } if (!program) { LOG_N_TIMES(3, ERROR) << "shader doesn't exist: " << name << ". " << COUNTER; return; } // vertex positions in NDC (Normalized Device Coordinates) static const std::vector<vec2> points = { vec2(-1.0f, -1.0f), vec2(1.0f, -1.0f), vec2(1.0f, 1.0f), vec2(-1.0f, 1.0f) }; static const std::vector<vec2> texcoords = { vec2(0, 0), vec2(1.0, 0), vec2(1.0, 1.0), vec2(0, 1.0), }; static const std::vector<unsigned int> indices = {0, 1, 2, 0, 2, 3}; unsigned int vertex_buffer = 0, texcoord_buffer = 0, element_buffer = 0; VertexArrayObject vao; easy3d_debug_log_gl_error vao.create_array_buffer(vertex_buffer, ShaderProgram::POSITION, points.data(), points.size() * sizeof(vec2), 2, true); easy3d_debug_log_gl_error vao.create_array_buffer(texcoord_buffer, ShaderProgram::TEXCOORD, texcoords.data(), texcoords.size() * sizeof(vec2), 2, true); easy3d_debug_log_gl_error vao.create_element_buffer(element_buffer, indices.data(), indices.size() * sizeof(unsigned int), true); easy3d_debug_log_gl_error program->bind(); easy3d_debug_log_gl_error program->set_uniform("depth", depth); program->bind_texture("textureID", texture, 0); vao.bind(); easy3d_debug_log_gl_error glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); easy3d_debug_log_gl_error glDrawElements(GL_TRIANGLES, static_cast<int>(indices.size()), GL_UNSIGNED_INT, nullptr); easy3d_debug_log_gl_error glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); easy3d_debug_log_gl_error program->release_texture(); vao.release(); easy3d_debug_log_gl_error program->release(); easy3d_debug_log_gl_error VertexArrayObject::release_buffer(vertex_buffer); VertexArrayObject::release_buffer(texcoord_buffer); VertexArrayObject::release_buffer(element_buffer); } void draw_depth_texture(const Rect &rect, unsigned int texture, int width, int height, float depth) { const std::string name = "screen_space/screen_space_depth_texture"; auto program = ShaderManager::get_program(name); if (!program) { std::vector<ShaderProgram::Attribute> attributes = { ShaderProgram::Attribute(ShaderProgram::POSITION, "vtx_position"), ShaderProgram::Attribute(ShaderProgram::TEXCOORD, "tex_coord") }; program = ShaderManager::create_program_from_files(name, attributes); } if (!program) { LOG_N_TIMES(3, ERROR) << "shader doesn't exist: " << name << ". " << COUNTER; return; } const float x0 = rect.x_min(); const float y0 = static_cast<float>(height) - rect.y_max() - 1; const float w = rect.width(); const float h = rect.height(); const float min_x = 2.0f * x0 / static_cast<float>(width) - 1.0f; const float min_y = 2.0f * y0 / static_cast<float>(height) - 1.0f; const float max_x = 2.0f * (x0 + w) / static_cast<float>(width) - 1.0f; const float max_y = 2.0f * (y0 + h) / static_cast<float>(height) - 1.0f; const std::vector<vec2> points = { vec2(min_x, min_y), vec2(max_x, min_y), vec2(max_x, max_y), vec2(min_x, max_y) }; static const std::vector<vec2> texcoords = { vec2(0, 0), vec2(1.0, 0), vec2(1.0, 1.0), vec2(0, 1.0), }; static const std::vector<unsigned int> indices = {0, 1, 2, 0, 2, 3}; unsigned int vertex_buffer = 0, texcoord_buffer = 0, element_buffer = 0; VertexArrayObject vao; easy3d_debug_log_gl_error vao.create_array_buffer(vertex_buffer, ShaderProgram::POSITION, points.data(), points.size() * sizeof(vec2), 2, true); easy3d_debug_log_gl_error vao.create_array_buffer(texcoord_buffer, ShaderProgram::TEXCOORD, texcoords.data(), texcoords.size() * sizeof(vec2), 2, true); easy3d_debug_log_gl_error vao.create_element_buffer(element_buffer, indices.data(), indices.size() * sizeof(unsigned int), true); easy3d_debug_log_gl_error program->bind(); easy3d_debug_log_gl_error program->set_uniform("depth", depth); program->bind_texture("textureID", texture, 0); vao.bind(); easy3d_debug_log_gl_error glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); easy3d_debug_log_gl_error glDrawElements(GL_TRIANGLES, static_cast<int>(indices.size()), GL_UNSIGNED_INT, nullptr); easy3d_debug_log_gl_error glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); easy3d_debug_log_gl_error program->release_texture(); vao.release(); easy3d_debug_log_gl_error program->release(); easy3d_debug_log_gl_error VertexArrayObject::release_buffer(vertex_buffer); VertexArrayObject::release_buffer(texcoord_buffer); VertexArrayObject::release_buffer(element_buffer); } void draw_quad(unsigned int positionAttrib, unsigned int texcoordAttrib, int x, int y, int w, int h, int vpw, int vph, float depth) { // vertex positions in NDC (Normalized Device Coordinates) // I assume viewportX = 0 and viewportY = 0. Otherwise, use the following equation to // convert from screen coordinates to NDC. //Xndc = 2.0f * (x - viewportX) / vpw - 1.0f; //Yndc = 2.0f * (y - viewportY) / vph - 1.0f; const float min_x = 2.0f * static_cast<float>(x) / static_cast<float>(vpw) - 1.0f; const float min_y = 2.0f * static_cast<float>(y) / static_cast<float>(vph) - 1.0f; const float max_x = 2.0f * static_cast<float>(x + w) / static_cast<float>(vpw) - 1.0f; const float max_y = 2.0f * static_cast<float>(y + h) / static_cast<float>(vph) - 1.0f; const float max_yTexCoord = (h == 0) ? 0.0f : 1.0f; const float positions[] = { min_x, min_y, depth, max_x, min_y, depth, min_x, max_y, depth, max_x, max_y, depth }; const float texcoords[] = { 0, 0, 1.0, 0, 0, max_yTexCoord, 1.0, max_yTexCoord }; // create vao and buffers, prepare data GLuint vao_handle = 0; glGenVertexArrays(1, &vao_handle); glBindVertexArray(vao_handle); GLuint vbo_positions = 0; glGenBuffers(1, &vbo_positions); glBindBuffer(GL_ARRAY_BUFFER, vbo_positions); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 12, positions, GL_STATIC_DRAW); glEnableVertexAttribArray(positionAttrib); glVertexAttribPointer(positionAttrib, 3, GL_FLOAT, GL_FALSE, 0, nullptr); GLuint vbo_texcoords = 0; glGenBuffers(1, &vbo_texcoords); glBindBuffer(GL_ARRAY_BUFFER, vbo_texcoords); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, texcoords, GL_STATIC_DRAW); glEnableVertexAttribArray(texcoordAttrib); glVertexAttribPointer(texcoordAttrib, 2, GL_FLOAT, GL_FALSE, 0, nullptr); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); // draw glBindVertexArray(vao_handle); easy3d_debug_log_gl_error glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); easy3d_debug_log_gl_error glBindVertexArray(0); glDeleteBuffers(1, &vbo_positions); easy3d_debug_log_gl_error glDeleteBuffers(1, &vbo_texcoords); easy3d_debug_log_gl_error glDeleteVertexArrays(1, &vao_handle); easy3d_debug_log_gl_error } void draw_full_screen_quad(unsigned int positionAttrib, unsigned int texcoordAttrib, float depth) { // vertex positions in NDC (Normalized Device Coordinates) const float positions[] = { -1.0f, -1.0f, depth, 1.0f, -1.0f, depth, -1.0f, 1.0f, depth, 1.0f, 1.0f, depth }; // texture coordinates const float texcoords[] = { 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f }; // create vao and buffers, prepare data GLuint vao_handle = 0; glGenVertexArrays(1, &vao_handle); glBindVertexArray(vao_handle); GLuint vbo_positions = 0; glGenBuffers(1, &vbo_positions); glBindBuffer(GL_ARRAY_BUFFER, vbo_positions); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 12, positions, GL_STATIC_DRAW); glEnableVertexAttribArray(positionAttrib); glVertexAttribPointer(positionAttrib, 3, GL_FLOAT, GL_FALSE, 0, nullptr); GLuint vbo_texcoords = 0; glGenBuffers(1, &vbo_texcoords); glBindBuffer(GL_ARRAY_BUFFER, vbo_texcoords); glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 8, texcoords, GL_STATIC_DRAW); glEnableVertexAttribArray(texcoordAttrib); glVertexAttribPointer(texcoordAttrib, 2, GL_FLOAT, GL_FALSE, 0, nullptr); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); // draw glBindVertexArray(vao_handle); easy3d_debug_log_gl_error glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); easy3d_debug_log_gl_error glBindVertexArray(0); glDeleteBuffers(1, &vbo_positions); easy3d_debug_log_gl_error glDeleteBuffers(1, &vbo_texcoords); easy3d_debug_log_gl_error glDeleteVertexArrays(1, &vao_handle); easy3d_debug_log_gl_error } void draw_polygon_wire(const Polygon2 &polygon, const vec4 &color, int width, int height, float depth) { if (polygon.size() < 3) return; const std::string name = "screen_space/screen_space_color"; auto program = ShaderManager::get_program(name); if (!program) { std::vector<ShaderProgram::Attribute> attributes = { ShaderProgram::Attribute(ShaderProgram::POSITION, "ndc_position") }; program = ShaderManager::create_program_from_files(name, attributes); } if (!program) return; std::vector<vec2> points(polygon.size()); for (std::size_t i = 0; i < polygon.size(); ++i) { const auto &p = polygon[i]; // to use the screen space shaders, I need to convert the point coordinates into the NDC space. // also have to follow the OpenGL coordinates rule. points[i].x = {2.0f * p.x / static_cast<float>(width) - 1.0f}; points[i].y = {2.0f * (static_cast<float>(height) - p.y - 1) / static_cast<float>(height) - 1.0f}; } unsigned int vertex_buffer = 0; VertexArrayObject vao; easy3d_debug_log_gl_error vao.create_array_buffer(vertex_buffer, ShaderProgram::POSITION, points.data(), points.size() * sizeof(vec2), 2, true); easy3d_debug_log_gl_error program->bind(); easy3d_debug_log_gl_error program->set_uniform("screen_color", color); easy3d_debug_log_gl_error program->set_uniform("depth", depth); vao.bind(); easy3d_debug_log_gl_error glDrawArrays(GL_LINE_LOOP, 0, static_cast<int>(points.size())); easy3d_debug_log_gl_error vao.release(); easy3d_debug_log_gl_error program->release(); easy3d_debug_log_gl_error VertexArrayObject::release_buffer(vertex_buffer); } void draw_polygon_filled(const Polygon2 &polygon, const vec4 &color, int width, int height, float depth) { if (polygon.size() < 3) return; const std::string name = "screen_space/screen_space_color"; auto program = ShaderManager::get_program(name); if (!program) { std::vector<ShaderProgram::Attribute> attributes = { ShaderProgram::Attribute(ShaderProgram::POSITION, "ndc_position") }; program = ShaderManager::create_program_from_files(name, attributes); } if (!program) return; // draw the face Tessellator tess; tess.begin_polygon(vec3(0, 0, 1)); tess.begin_contour(); for (std::size_t i = 0; i < polygon.size(); ++i) { const auto &p = polygon[i]; // to use the screen space shaders, I need to convert the point coordinates into the NDC space. // also have to follow the OpenGL coordinates rule. float x = {2.0f * p.x / static_cast<float>(width) - 1.0f}; float y = {2.0f * (static_cast<float>(height) - p.y - 1) / static_cast<float>(height) - 1.0f}; tess.add_vertex(vec3(x, y, 0)); } tess.end_contour(); tess.end_polygon(); const auto &vts = tess.vertices(); std::vector<vec2> points(vts.size()); for (std::size_t i = 0; i < vts.size(); ++i) points[i] = vec2(vts[i]->data()); const auto &indices = tess.elements(); std::vector<unsigned int> elements; for (const auto &array : indices) elements.insert(elements.end(), array.begin(), array.end()); unsigned int vertex_buffer = 0, element_buffer = 0; VertexArrayObject vao; easy3d_debug_log_gl_error vao.create_array_buffer(vertex_buffer, ShaderProgram::POSITION, points.data(), points.size() * sizeof(vec2), 2, true); easy3d_debug_log_gl_error vao.create_element_buffer(element_buffer, elements.data(), elements.size() * sizeof(unsigned int), true);easy3d_debug_log_gl_error program->bind(); easy3d_debug_log_gl_error program->set_uniform("screen_color", color); easy3d_debug_log_gl_error program->set_uniform("depth", depth); vao.bind(); easy3d_debug_log_gl_error glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer); easy3d_debug_log_gl_error glDrawElements(GL_TRIANGLES, static_cast<int>(elements.size()), GL_UNSIGNED_INT, nullptr); easy3d_debug_log_gl_error glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); easy3d_debug_log_gl_error vao.release(); easy3d_debug_log_gl_error program->release();easy3d_debug_log_gl_error VertexArrayObject::release_buffer(vertex_buffer); VertexArrayObject::release_buffer(element_buffer); } void draw_sphere_big_circles(LinesDrawable *drawable, const mat4 &mvp, const mat4 &m, bool axes) { if (!drawable) return; ShaderProgram *program = ShaderManager::get_program("lines/lines_plain_color"); if (!program) { std::vector<ShaderProgram::Attribute> attributes; attributes.emplace_back(ShaderProgram::Attribute(ShaderProgram::POSITION, "vtx_position")); attributes.emplace_back(ShaderProgram::Attribute(ShaderProgram::COLOR, "vtx_color")); program = ShaderManager::create_program_from_files("lines/lines_plain_color", attributes); } if (!program) return; if (drawable->vertex_buffer() == 0) { std::vector<vec3> points, colors; std::vector<unsigned int> indices; std::vector<vec3> points_xoy; // xoy create_circle(50, points_xoy, indices); for (auto &p : points_xoy) { points.push_back(p); colors.emplace_back(vec3(0, 0, 1)); } colors.resize(points_xoy.size(), vec3(0, 0, 1)); auto rot_x = mat4::rotation(vec3(1, 0, 0), static_cast<float>(M_PI * 0.5f)); for (std::size_t i = 0; i < points_xoy.size(); ++i) { points.push_back(rot_x * points_xoy[i]); colors.emplace_back(vec3(0, 1, 0)); indices.push_back(static_cast<unsigned int>(points_xoy.size() + i)); indices.push_back(static_cast<unsigned int>(points_xoy.size() + (i + 1) % points_xoy.size())); } auto rot_y = mat4::rotation(vec3(0, 1, 0), static_cast<float>(M_PI * 0.5f)); for (std::size_t i = 0; i < points_xoy.size(); ++i) { points.push_back(rot_y * points_xoy[i]); colors.emplace_back(vec3(1, 0, 0)); indices.push_back(static_cast<unsigned int>(points_xoy.size() * 2 + i)); indices.push_back(static_cast<unsigned int>(points_xoy.size() * 2 + (i + 1) % points_xoy.size())); } if (axes) { // x axis points.emplace_back(vec3(-1, 0, 0)); points.emplace_back(vec3(1, 0, 0)); colors.emplace_back(vec3(1, 0, 0)); colors.emplace_back(vec3(1, 0, 0)); indices.push_back(static_cast<unsigned int>(points.size() - 2)); indices.push_back(static_cast<unsigned int>(points.size() - 1)); // y axis points.emplace_back(vec3(0, -1, 0)); points.emplace_back(vec3(0, 1, 0)); colors.emplace_back(vec3(0, 1, 0)); colors.emplace_back(vec3(0, 1, 0)); indices.push_back(static_cast<unsigned int>(points.size() - 2)); indices.push_back(static_cast<unsigned int>(points.size() - 1)); // z axis points.emplace_back(vec3(0, 0, -1)); points.emplace_back(vec3(0, 0, 1)); colors.emplace_back(vec3(0, 0, 1)); colors.emplace_back(vec3(0, 0, 1)); indices.push_back(static_cast<unsigned int>(points.size() - 2)); indices.push_back(static_cast<unsigned int>(points.size() - 1)); } drawable->update_vertex_buffer(points); drawable->update_color_buffer(colors); drawable->update_element_buffer(indices); drawable->set_property_coloring(State::VERTEX); } program->bind(); program->set_uniform("MVP", mvp) ->set_uniform("MANIP", m) ->set_uniform("per_vertex_color", true) ->set_uniform("clippingPlaneEnabled", false) ->set_uniform("selected", false) ->set_uniform("highlight_color", setting::highlight_color); drawable->gl_draw(); program->release(); } void draw_box_wire(LinesDrawable *drawable, const mat4 &mvp, const mat4 &m, bool abstracted) { if (!drawable) return; ShaderProgram *program = ShaderManager::get_program("lines/lines_plain_color"); if (!program) { std::vector<ShaderProgram::Attribute> attributes; attributes.emplace_back(ShaderProgram::Attribute(ShaderProgram::POSITION, "vtx_position")); attributes.emplace_back(ShaderProgram::Attribute(ShaderProgram::COLOR, "vtx_color")); program = ShaderManager::create_program_from_files("lines/lines_plain_color", attributes); } if (!program) return; if (drawable->vertex_buffer() == 0) { std::vector<vec3> points, colors; create_box(points, colors, abstracted); drawable->update_vertex_buffer(points); drawable->update_color_buffer(colors); } program->bind(); program->set_uniform("MVP", mvp) ->set_uniform("MANIP", m) ->set_uniform("per_vertex_color", true) ->set_uniform("clippingPlaneEnabled", false) ->set_uniform("selected", false) ->set_uniform("highlight_color", setting::highlight_color); drawable->gl_draw(); program->release(); } // --------------------------------------------------------------------------- void create_grid(int x_steps, int y_steps, std::vector<vec3> &points, float depth, float scale) { // Horizontal lines float x = scale * 0.5f * static_cast<float>(x_steps - 1); float y = -scale * 0.5f * static_cast<float>(y_steps - 1); for (int i = 0; i < y_steps; i++) { points.emplace_back(vec3(-x, y, depth)); points.emplace_back(vec3(x, y, depth)); y += scale; } // Vertical lines x = -scale * 0.5f * (float) (x_steps - 1); y = scale * 0.5f * (float) (y_steps - 1); for (int i = 0; i < x_steps; i++) { points.emplace_back(vec3(x, -y, depth)); points.emplace_back(vec3(x, y, depth)); x += scale; } } void create_box(std::vector<vec3> &points, std::vector<vec3> &colors, bool abstracted) { points.clear(); colors.clear(); const float min_coord = -0.5f; const float max_coord = 0.5f; vec3 red(1, 0, 0), green(0, 1, 0), blue(0, 0, 1); std::vector<vec3> vts = { vec3(min_coord, min_coord, min_coord), // 0 vec3(max_coord, min_coord, min_coord), // 1 vec3(max_coord, max_coord, min_coord), // 2 vec3(min_coord, max_coord, min_coord), // 3 vec3(min_coord, min_coord, max_coord), // 4 vec3(max_coord, min_coord, max_coord), // 5 vec3(max_coord, max_coord, max_coord), // 6 vec3(min_coord, max_coord, max_coord) // 7 }; if (abstracted) { const float ratio = 0.2f; points = { vts[0], vts[0] + red * ratio, vts[1], vts[1] - red * ratio, vts[1], vts[1] + green * ratio, vts[2], vts[2] - green * ratio, vts[2], vts[2] - red * ratio, vts[3], vts[3] + red * ratio, vts[3], vts[3] - green * ratio, vts[0], vts[0] + green * ratio, vts[4], vts[4] + red * ratio, vts[5], vts[5] - red * ratio, vts[5], vts[5] + green * ratio, vts[6], vts[6] - green * ratio, vts[6], vts[6] - red * ratio, vts[7], vts[7] + red * ratio, vts[7], vts[7] - green * ratio, vts[4], vts[4] + green * ratio, vts[0], vts[0] + blue * ratio, vts[1], vts[1] + blue * ratio, vts[2], vts[2] + blue * ratio, vts[3], vts[3] + blue * ratio, vts[4], vts[4] - blue * ratio, vts[5], vts[5] - blue * ratio, vts[6], vts[6] - blue * ratio, vts[7], vts[7] - blue * ratio }; colors = { red, red, red, red, green, green, green, green, red, red, red, red, green, green, green, green, red, red, red, red, green, green, green, green, red, red, red, red, green, green, green, green, blue, blue, blue, blue, blue, blue, blue, blue, blue, blue, blue, blue, blue, blue, blue, blue }; } else { points = { vts[0], vts[1], vts[1], vts[2], vts[2], vts[3], vts[3], vts[0], vts[4], vts[5], vts[5], vts[6], vts[6], vts[7], vts[7], vts[4], vts[0], vts[4], vts[1], vts[5], vts[2], vts[6], vts[3], vts[7] }; colors = { red, red, green, green, red, red, green, green, red, red, green, green, red, red, green, green, blue, blue, blue, blue, blue, blue, blue, blue }; } } void create_circle(int slices, std::vector<vec3> &points, std::vector<unsigned int> &indices) { points.clear(); indices.clear(); // Compute vertex position of the body const auto step_theta = static_cast<float>(2.0 * M_PI / slices); for (int i = 0; i < slices; ++i) { const float angle = static_cast<float>(i) * step_theta; const float x = std::cos(angle); const float y = std::sin(angle); points.emplace_back(vec3(x, y, 0.f)); indices.push_back(i); indices.push_back((i + 1) % slices); } } void create_sphere( const vec3 ¢er, double radius, int slices, int stacks, const vec3 &color, std::vector<vec3> &points, std::vector<vec3> &normals, std::vector<vec3> &colors) { create_checker_sphere(center, radius, slices, stacks, 1, color, color, points, normals, colors); } void create_checker_sphere( const vec3 ¢er, double radius, int slices, int stacks, int checker_size, const vec3 &color1, const vec3 &color2, std::vector<vec3> &points, std::vector<vec3> &normals, std::vector<vec3> &colors) { for (int u = 0; u < slices; u += 1) { double theta1 = u * 2.0 * M_PI / slices; double theta2 = (u + 1) * 2.0 * M_PI / slices; for (int v = 0; v < stacks; v++) { double phi1 = v * M_PI / stacks - M_PI / 2.0; double phi2 = (v + 1) * M_PI / stacks - M_PI / 2.0; double x11 = radius * std::cos(theta1) * std::cos(phi1); double y11 = radius * std::sin(theta1) * std::cos(phi1); double z11 = radius * std::sin(phi1); double x12 = radius * std::cos(theta1) * std::cos(phi2); double y12 = radius * std::sin(theta1) * std::cos(phi2); double z12 = radius * std::sin(phi2); double x21 = radius * std::cos(theta2) * std::cos(phi1); double y21 = radius * std::sin(theta2) * std::cos(phi1); double z21 = radius * std::sin(phi1); double x22 = radius * std::cos(theta2) * std::cos(phi2); double y22 = radius * std::sin(theta2) * std::cos(phi2); double z22 = radius * std::sin(phi2); // we generate triangle fans, but the renderer accept triangles only. // so we collect vertices first, then we split the fan to triangles. std::vector<vec3> fan; fan.emplace_back(vec3(static_cast<float>(x11), static_cast<float>(y11), static_cast<float>(z11))); if (v != 0) fan.emplace_back(vec3(static_cast<float>(x21), static_cast<float>(y21), static_cast<float>(z21))); fan.emplace_back(vec3(static_cast<float>(x22), static_cast<float>(y22), static_cast<float>(z22))); if (v != stacks - 1) fan.emplace_back(vec3(static_cast<float>(x12), static_cast<float>(y12), static_cast<float>(z12))); int toggle = ((u / checker_size) ^ (v / checker_size)) & 1; vec3 color = toggle ? color1 : color2; if (fan.size() == 4) { // a quad points.push_back(fan[0] + center); normals.push_back(normalize(fan[0])); points.push_back(fan[1] + center); normals.push_back(normalize(fan[1])); points.push_back(fan[2] + center); normals.push_back(normalize(fan[2])); points.push_back(fan[0] + center); normals.push_back(normalize(fan[0])); points.push_back(fan[2] + center); normals.push_back(normalize(fan[2])); points.push_back(fan[3] + center); normals.push_back(normalize(fan[3])); colors.insert(colors.end(), 6, color); } else { // a triangle for (const auto &p : fan) { points.push_back(p + center); normals.push_back(normalize(p)); colors.push_back(color); } } } } } void create_cylinder(double radius, int slices, const vec3 &s, const vec3 &t, const vec3 &color, std::vector<vec3> &points, std::vector<vec3> &normals, std::vector<vec3> &colors) { // find a vector perpendicular to the direction const vec3 offset = t - s; const vec3 axis = normalize(offset); vec3 perp = geom::orthogonal(axis); perp.normalize(); const vec3 p = s + perp * radius; const double angle_interval = 2.0 * M_PI / slices; for (int i = 0; i < slices; ++i) { double angle_a = i * angle_interval; double angle_b = (i + 1) * angle_interval; // the rotation axis is just the direction (i.e., passing through the original) const vec3 a = s + mat4::rotation(axis, static_cast<float>(angle_a)) * (p - s); const vec3 b = s + mat4::rotation(axis, static_cast<float>(angle_b)) * (p - s); const vec3 c = a + offset; const vec3 d = b + offset; const vec3 na = normalize(a - s); const vec3 nb = normalize(b - s); const vec3 nc = normalize(c - t); const vec3 nd = normalize(d - t); // triangle 1 points.push_back(a); normals.push_back(na); colors.push_back(color); points.push_back(b); normals.push_back(nb); colors.push_back(color); points.push_back(c); normals.push_back(nc); colors.push_back(color); // triangle 2 points.push_back(b); normals.push_back(nb); colors.push_back(color); points.push_back(d); normals.push_back(nd); colors.push_back(color); points.push_back(c); normals.push_back(nc); colors.push_back(color); } } void create_cone(double radius, int slices, const vec3 &s, const vec3 &t, const vec3 &color, std::vector<vec3> &points, std::vector<vec3> &normals, std::vector<vec3> &colors) { // find a vector perpendicular to the direction const vec3 offset = t - s; const vec3 axis = normalize(offset); vec3 perp = geom::orthogonal(axis); perp.normalize(); const vec3 p = s + perp * radius; const double angle_interval = 2.0 * M_PI / slices; for (int i = 0; i < slices; ++i) { double angle_a = i * angle_interval; double angle_b = (i + 1) * angle_interval; // the rotation axis is just the direction (i.e., passing through the original) const vec3 a = s + mat4::rotation(axis, static_cast<float>(angle_a)) * (p - s); const vec3 b = s + mat4::rotation(axis, static_cast<float>(angle_b)) * (p - s); const vec3 c = t; // na vec3 dir = a - t; dir.normalize(); vec3 na = t + dir * dot(s - t, dir) - s; na.normalize(); // nb dir = b - t; dir.normalize(); vec3 nb = t + dir * dot(s - t, dir) - s; nb.normalize(); // nc vec3 nc = cross(a - t, b - t); nc.normalize(); points.push_back(a); normals.push_back(na); colors.push_back(color); points.push_back(b); normals.push_back(nb); colors.push_back(color); points.push_back(c); normals.push_back(nc); colors.push_back(color); } } void create_torus(double major_radius, double minor_radius, int major_slices, int minor_slices, std::vector<vec3> &points, std::vector<vec3> &normals) { bool flip = false; const double twopi = 2.0 * M_PI; for (int i = 0; i < minor_slices; i++) { // QUAD STRIPs; std::vector<vec3> tmp_pts, tmp_nms; for (int j = 0; j <= major_slices; j++) { for (int k = 1; k >= 0; k--) { double s = (i + k) % minor_slices + 0.5; double t = j % major_slices; // Calculate point on surface double x = (major_radius + minor_radius * std::cos(s * twopi / minor_slices)) * std::cos(t * twopi / major_slices); double y = minor_radius * std::sin(s * twopi / minor_slices); double z = (major_radius + minor_radius * cos(s * twopi / minor_slices)) * std::sin(t * twopi / major_slices); // Calculate surface normal double nx = x - major_radius * std::cos(t * twopi / major_slices); double ny = y; double nz = z - major_radius * std::sin(t * twopi / major_slices); vec3 n(static_cast<float>(nx), static_cast<float>(ny), static_cast<float>(nz)); n.normalize(); tmp_pts.emplace_back(vec3(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z))); tmp_nms.push_back(n); if (tmp_pts.size() <= 3) { points.emplace_back(vec3(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z))); normals.push_back(n); } else { points.emplace_back(vec3(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z))); normals.push_back(n); if (flip) { points.push_back(tmp_pts[tmp_pts.size() - 3]); normals.push_back(tmp_nms[tmp_nms.size() - 3]); points.push_back(tmp_pts[tmp_pts.size() - 2]); normals.push_back(tmp_nms[tmp_nms.size() - 2]); } else { points.push_back(tmp_pts[tmp_pts.size() - 2]); normals.push_back(tmp_nms[tmp_nms.size() - 2]); points.push_back(tmp_pts[tmp_pts.size() - 3]); normals.push_back(tmp_nms[tmp_nms.size() - 3]); } flip = !flip; } } } flip = !flip; } } void create_camera(std::vector<vec3> &points, float width, float fov, float hw_ratio) { const float halfWidth = width * 0.5f; const float halfHeight = halfWidth * hw_ratio; const auto dist = static_cast<float>(halfHeight / std::tan(fov * 0.5)); const float arrowHeight = 2.0f * halfHeight; const float baseHeight = 1.2f * halfHeight; const float arrowHalfWidth = 0.5f * halfWidth; const float baseHalfWidth = 0.3f * halfWidth; //-------------- // Frustum outline //-------------- const vec3 c(0.0f, 0.0f, 0.0f); const vec3 p0(-halfWidth, -halfHeight, -dist); const vec3 p1(halfWidth, -halfHeight, -dist); const vec3 p2(halfWidth, halfHeight, -dist); const vec3 p3(-halfWidth, halfHeight, -dist); points.push_back(p0); points.push_back(p1); points.push_back(p1); points.push_back(p2); points.push_back(p2); points.push_back(p3); points.push_back(p3); points.push_back(p0); points.push_back(c); points.push_back(p0); points.push_back(c); points.push_back(p1); points.push_back(c); points.push_back(p2); points.push_back(c); points.push_back(p3); //------------------ // Up arrow //------------------ // Base - QUAD const vec3 r0(-baseHalfWidth, halfHeight, -dist); const vec3 r1(baseHalfWidth, halfHeight, -dist); const vec3 r2(baseHalfWidth, baseHeight, -dist); const vec3 r3(-baseHalfWidth, baseHeight, -dist); points.push_back(r0); points.push_back(r1); points.push_back(r1); points.push_back(r2); points.push_back(r2); points.push_back(r3); points.push_back(r3); points.push_back(r0); // Arrow - TRIANGLE const vec3 a0(0.0f, arrowHeight, -dist); const vec3 a1(-arrowHalfWidth, baseHeight, -dist); const vec3 a2(arrowHalfWidth, baseHeight, -dist); points.push_back(a0); points.push_back(a1); points.push_back(a1); points.push_back(a2); points.push_back(a2); points.push_back(a0); } void create_camera(std::vector<vec3> &points, std::vector<unsigned int> &indices, float width, float fov, float hw_ratio) { const float halfWidth = width * 0.5f; const float halfHeight = halfWidth * hw_ratio; const auto dist = static_cast<float>(halfHeight / std::tan(fov * 0.5)); const float arrowHeight = 2.0f * halfHeight; const float baseHeight = 1.2f * halfHeight; const float arrowHalfWidth = 0.5f * halfWidth; const float baseHalfWidth = 0.3f * halfWidth; //-------------- // Frustum outline //-------------- const vec3 c(0.0f, 0.0f, 0.0f); const vec3 p0(-halfWidth, -halfHeight, -dist); const vec3 p1(halfWidth, -halfHeight, -dist); const vec3 p2(halfWidth, halfHeight, -dist); const vec3 p3(-halfWidth, halfHeight, -dist); points.push_back(c); points.push_back(p0); points.push_back(p1); points.push_back(p2); points.push_back(p3); //------------------ // Up arrow //------------------ // Base - QUAD const vec3 r0(-baseHalfWidth, halfHeight, -dist); const vec3 r1(baseHalfWidth, halfHeight, -dist); const vec3 r2(baseHalfWidth, baseHeight, -dist); const vec3 r3(-baseHalfWidth, baseHeight, -dist); points.push_back(r0); points.push_back(r1); points.push_back(r2); points.push_back(r3); // Arrow - TRIANGLE const vec3 a0(0.0f, arrowHeight, -dist); const vec3 a1(-arrowHalfWidth, baseHeight, -dist); const vec3 a2(arrowHalfWidth, baseHeight, -dist); points.push_back(a0); points.push_back(a1); points.push_back(a2); indices.insert(indices.end(), {0, 1, 2}); indices.insert(indices.end(), {0, 2, 3}); indices.insert(indices.end(), {0, 3, 4}); indices.insert(indices.end(), {0, 4, 1}); indices.insert(indices.end(), {5, 6, 7}); indices.insert(indices.end(), {5, 7, 8}); indices.insert(indices.end(), {9, 10, 11}); } } } // namespace easy3d