From d7120637d75792eb3805add199adce6b3a8942fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Micha=C3=ABl=20Celerier?= Date: Sat, 9 Nov 2024 20:10:32 -0500 Subject: [PATCH] [pointclouds] Support for color --- CMakeLists.txt | 3 + Threedim/ModelDisplay/ModelDisplayNode.cpp | 75 ++++++ Threedim/ModelDisplay/Process.cpp | 1 + Threedim/ObjLoader.cpp | 254 ++++----------------- Threedim/ObjLoader.hpp | 2 - Threedim/Ply.cpp | 214 +++++++++++++++++ Threedim/Ply.hpp | 13 ++ Threedim/TinyObj.hpp | 7 +- 8 files changed, 349 insertions(+), 220 deletions(-) create mode 100644 Threedim/Ply.cpp create mode 100644 Threedim/Ply.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ad6424e..7555615 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,10 @@ endif() add_library( score_addon_threedim + Threedim/TinyObj.hpp Threedim/TinyObj.cpp + Threedim/Ply.hpp + Threedim/Ply.cpp Threedim/StructureSynth.hpp Threedim/StructureSynth.cpp diff --git a/Threedim/ModelDisplay/ModelDisplayNode.cpp b/Threedim/ModelDisplay/ModelDisplayNode.cpp index 78d01c1..f914ea0 100644 --- a/Threedim/ModelDisplay/ModelDisplayNode.cpp +++ b/Threedim/ModelDisplay/ModelDisplayNode.cpp @@ -537,6 +537,62 @@ void main () } )_"; +static const constexpr auto model_display_vertex_shader_color = R"_(#version 450 +layout(location = 0) in vec3 position; +layout(location = 2) in vec3 color; + +layout(location = 0) out vec3 v_color; + +layout(std140, binding = 0) uniform renderer_t { + mat4 clipSpaceCorrMatrix; + vec2 renderSize; +} renderer; + +layout(std140, binding = 2) uniform material_t { + mat4 matrixModelViewProjection; + mat4 matrixModelView; + mat4 matrixModel; + mat4 matrixView; + mat4 matrixProjection; + mat3 matrixNormal; +} mat; + +%vtx_output% + +void main() +{ + v_color = color; + gl_Position = renderer.clipSpaceCorrMatrix * mat.matrixModelViewProjection * vec4(position.xyz, 1.0); + %vtx_output_process% +} +)_"; + +static const constexpr auto model_display_fragment_shader_color = R"_(#version 450 +layout(std140, binding = 0) uniform renderer_t { + mat4 clipSpaceCorrMatrix; + vec2 renderSize; +} renderer; + +layout(std140, binding = 2) uniform material_t { + mat4 matrixModelViewProjection; + mat4 matrixModelView; + mat4 matrixModel; + mat4 matrixView; + mat4 matrixProjection; + mat3 matrixNormal; +} mat; + + +layout(location = 0) in vec3 v_color; +layout(location = 0) out vec4 fragColor; + +void main () +{ + fragColor.rgb = v_color; + fragColor.a = 1.; +} +)_"; + ModelDisplayNode::ModelDisplayNode() { input.push_back(new Port{this, nullptr, Types::Image, {}}); @@ -567,6 +623,7 @@ class ModelDisplayNode::Renderer : public GenericNodeRenderer QShader spherical2VS, spherical2FS; QShader viewspaceVS, viewspaceFS; QShader barycentricVS, barycentricFS; + QShader colorVS, colorFS; } triangle, point; QShader pclVS, pclFS; @@ -589,6 +646,13 @@ class ModelDisplayNode::Renderer : public GenericNodeRenderer auto& n = (ModelDisplayNode&)node; bool has_texcoord = mesh.flags() & Mesh::HasTexCoord; bool has_normals = mesh.flags() & Mesh::HasNormals; + bool has_colors = mesh.flags() & Mesh::HasColor; + if (has_colors && n.wantedProjection == 7) + { + defaultPassesInit(renderer, mesh, shaders.colorVS, shaders.colorFS); + return; + } + if (has_texcoord && has_normals) { switch (n.wantedProjection) @@ -737,6 +801,10 @@ class ModelDisplayNode::Renderer : public GenericNodeRenderer model_display_vertex_shader_barycentric, vtx_output_triangle, vtx_output_process_triangle); + static const QString triangle_colorVS = processShader( + model_display_vertex_shader_color, + vtx_output_triangle, + vtx_output_process_triangle); std::tie(triangle.phongVS, triangle.phongFS) = score::gfx::makeShaders( renderer.state, triangle_phongVS, model_display_fragment_shader_phong); @@ -754,6 +822,8 @@ class ModelDisplayNode::Renderer : public GenericNodeRenderer renderer.state, triangle_barycentricVS, model_display_fragment_shader_barycentric); + std::tie(triangle.colorVS, triangle.colorFS) = score::gfx::makeShaders( + renderer.state, triangle_colorVS, model_display_fragment_shader_color); static const QString point_phongVS = processShader( model_display_vertex_shader_phong, vtx_output_point, vtx_output_process_point); @@ -781,6 +851,9 @@ class ModelDisplayNode::Renderer : public GenericNodeRenderer model_display_vertex_shader_barycentric, vtx_output_point, vtx_output_process_point); + static const QString point_colorVS = processShader( + model_display_vertex_shader_color, vtx_output_point, vtx_output_process_point); + std::tie(point.phongVS, point.phongFS) = score::gfx::makeShaders( renderer.state, point_phongVS, model_display_fragment_shader_phong); std::tie(point.texCoordVS, point.texCoordFS) = score::gfx::makeShaders( @@ -795,6 +868,8 @@ class ModelDisplayNode::Renderer : public GenericNodeRenderer renderer.state, point_viewspaceVS, model_display_fragment_shader_viewspace); std::tie(point.barycentricVS, point.barycentricFS) = score::gfx::makeShaders( renderer.state, point_barycentricVS, model_display_fragment_shader_barycentric); + std::tie(point.colorVS, point.colorFS) = score::gfx::makeShaders( + renderer.state, point_colorVS, model_display_fragment_shader_color); } void init(RenderList& renderer, QRhiResourceUpdateBatch& res) override { diff --git a/Threedim/ModelDisplay/Process.cpp b/Threedim/ModelDisplay/Process.cpp index bb18408..4746abc 100644 --- a/Threedim/ModelDisplay/Process.cpp +++ b/Threedim/ModelDisplay/Process.cpp @@ -54,6 +54,7 @@ Model::Model( {"Funky A", 1}, {"Funky B", 3}, {"Light", 6}, + {"Color", 7}, }; m_inlets.push_back( diff --git a/Threedim/ObjLoader.cpp b/Threedim/ObjLoader.cpp index 6c983c3..01358c5 100644 --- a/Threedim/ObjLoader.cpp +++ b/Threedim/ObjLoader.cpp @@ -1,13 +1,10 @@ #include "ObjLoader.hpp" -#include +#include "Ply.hpp" -#include #include #include -#include - namespace Threedim { @@ -76,6 +73,14 @@ void ObjLoader::rebuild_geometry() .classification = halp::dynamic_geometry::binding::per_vertex}); } + if (m.colors) + { + geom.bindings.push_back(halp::dynamic_geometry::binding{ + .stride = 3 * sizeof(float), + .step_rate = 1, + .classification = halp::dynamic_geometry::binding::per_vertex}); + } + // Attributes geom.attributes.push_back(halp::dynamic_geometry::attribute{ .binding = 0, @@ -86,7 +91,7 @@ void ObjLoader::rebuild_geometry() if (m.texcoord) { geom.attributes.push_back(halp::dynamic_geometry::attribute{ - .binding = 1, + .binding = geom.attributes.back().binding + 1, .location = halp::dynamic_geometry::attribute::tex_coord, .format = halp::dynamic_geometry::attribute::float2, .offset = 0}); @@ -95,12 +100,21 @@ void ObjLoader::rebuild_geometry() if (m.normals) { geom.attributes.push_back(halp::dynamic_geometry::attribute{ - .binding = m.texcoord ? 2 : 1, + .binding = geom.attributes.back().binding + 1, .location = halp::dynamic_geometry::attribute::normal, .format = halp::dynamic_geometry::attribute::float3, .offset = 0}); } + if (m.colors) + { + geom.attributes.push_back(halp::dynamic_geometry::attribute{ + .binding = geom.attributes.back().binding + 1, + .location = halp::dynamic_geometry::attribute::color, + .format = halp::dynamic_geometry::attribute::float3, + .offset = 0}); + } + // Vertex input using input_t = struct halp::dynamic_geometry::input; geom.input.push_back( @@ -118,6 +132,11 @@ void ObjLoader::rebuild_geometry() input_t{.buffer = 0, .offset = m.normal_offset * (int)sizeof(float)}); } + if (m.colors) + { + geom.input.push_back( + input_t{.buffer = 0, .offset = m.color_offset * (int)sizeof(float)}); + } outputs.geometry.mesh.push_back(std::move(geom)); outputs.geometry.dirty_mesh = true; } @@ -134,232 +153,37 @@ static bool check_file_extension(std::string_view filename, std::string_view exp return true; } -bool print_ply_header(const char* filename) -{ - static const char* kPropertyTypes[] = { - "char", - "uchar", - "short", - "ushort", - "int", - "uint", - "float", - "double", - }; - - miniply::PLYReader reader(filename); - if (!reader.valid()) - { - return false; - } - using namespace miniply; - for (uint32_t i = 0, endI = reader.num_elements(); i < endI; i++) - { - const miniply::PLYElement* elem = reader.get_element(i); - fprintf(stderr, "element %s %u\n", elem->name.c_str(), elem->count); - for (const miniply::PLYProperty& prop : elem->properties) - { - if (prop.countType != miniply::PLYPropertyType::None) - { - fprintf( - stderr, - "property list %s %s %s\n", - kPropertyTypes[uint32_t(prop.countType)], - kPropertyTypes[uint32_t(prop.type)], - prop.name.c_str()); - } - else - { - fprintf( - stderr, - "property %s %s\n", - kPropertyTypes[uint32_t(prop.type)], - prop.name.c_str()); - } - } - } - fprintf(stderr, "end_header\n"); - - return true; -} -// Very basic triangle mesh struct, for example purposes -struct TriMesh -{ - // Per-vertex data - float* pos = nullptr; // has 3 * numVerts elements. - float* uv = nullptr; // if non-null, has 2 * numVerts elements. - float* norm = nullptr; // if non-null, has 3 * numVerts elements. - float* color = nullptr; // if non-null, has 3 * numVerts elements. - uint32_t numVerts = 0; - - // Per-index data - int* indices = nullptr; - uint32_t numIndices = 0; // number of indices = 3 times the number of triangles. -}; - -bool load_vert_from_ply(miniply::PLYReader& reader, TriMesh* trimesh) -{ - uint32_t indexes[3]; - if (reader.element_is(miniply::kPLYVertexElement) && reader.load_element() - && reader.find_pos(indexes)) - { - trimesh->numVerts = reader.num_rows(); - if (trimesh->numVerts <= 0) - return false; - - trimesh->pos = new float[trimesh->numVerts * 3]; - reader.extract_properties(indexes, 3, miniply::PLYPropertyType::Float, trimesh->pos); - if (reader.find_texcoord(indexes)) - { - trimesh->uv = new float[trimesh->numVerts * 2]; - reader.extract_properties( - indexes, 2, miniply::PLYPropertyType::Float, trimesh->uv); - } - if (reader.find_normal(indexes)) - { - trimesh->uv = new float[trimesh->numVerts * 3]; - reader.extract_properties( - indexes, 3, miniply::PLYPropertyType::Float, trimesh->uv); - } - if (reader.find_color(indexes)) - { - trimesh->color = new float[trimesh->numVerts * 3]; - reader.extract_properties( - indexes, 3, miniply::PLYPropertyType::Float, trimesh->color); - } - return true; - } - return false; -} - -bool load_face_from_ply(miniply::PLYReader& reader, TriMesh* trimesh, bool gotVerts) -{ - uint32_t indexes[3]; - if (reader.element_is(miniply::kPLYFaceElement) && reader.load_element() - && reader.find_indices(indexes)) - { - bool polys = reader.requires_triangulation(indexes[0]); - if (polys && !gotVerts) - { - fprintf(stderr, "Error: need vertex positions to triangulate faces.\n"); - return false; - } - if (polys) - { - trimesh->numIndices = reader.num_triangles(indexes[0]) * 3; - if (trimesh->numIndices <= 0) - return false; - trimesh->indices = new int[trimesh->numIndices]; - reader.extract_triangles( - indexes[0], - trimesh->pos, - trimesh->numVerts, - miniply::PLYPropertyType::Int, - trimesh->indices); - } - else - { - trimesh->numIndices = reader.num_rows() * 3; - trimesh->indices = new int[trimesh->numIndices]; - reader.extract_list_property( - indexes[0], miniply::PLYPropertyType::Int, trimesh->indices); - } - return true; - } - return false; -} - -TriMesh* load_trimesh_from_ply(miniply::PLYReader& reader) +std::function ObjLoader::ins::obj_t::process(file_type tv) { - bool gotVerts = false, gotFaces = false; - - TriMesh* trimesh = new TriMesh(); - while (reader.has_element() && (!gotVerts || !gotFaces)) + auto upload = [](auto&& mesh, auto&& buf) { - if (auto verts = load_vert_from_ply(reader, trimesh)) - gotVerts = verts; - else if (auto faces = load_face_from_ply(reader, trimesh, gotVerts)) - gotFaces = faces; - - if (gotVerts && gotFaces) + return [mesh = std::move(mesh), buf = std::move(buf)](ObjLoader& o) mutable { - break; - } - reader.next_element(); - } + // This part happens in the execution thread + std::swap(o.meshinfo, mesh); + std::swap(o.complete, buf); - if (!gotVerts || !gotFaces) - { - delete trimesh; - return nullptr; - } - - return trimesh; -} - -TriMesh* load_vertices_from_ply(miniply::PLYReader& reader) -{ - TriMesh* trimesh = new TriMesh(); - while (reader.has_element()) - { - if (load_vert_from_ply(reader, trimesh)) - return trimesh; - reader.next_element(); - } - - delete trimesh; - return nullptr; -} + o.rebuild_geometry(); + }; + }; -std::function ObjLoader::ins::obj_t::process(file_type tv) -{ + Threedim::float_vec buf; if (check_file_extension(tv.filename, "obj")) { // This part happens in a separate thread - Threedim::float_vec buf; if (auto mesh = Threedim::ObjFromString(tv.bytes, buf); !mesh.empty()) { - return [mesh = std::move(mesh), buf = std::move(buf)](ObjLoader& o) mutable - { - // This part happens in the execution thread - std::swap(o.meshinfo, mesh); - std::swap(o.complete, buf); - - o.rebuild_geometry(); - }; + return upload(std::move(mesh), std::move(buf)); } } else if (check_file_extension(tv.filename, "ply")) { - print_ply_header(tv.filename.data()); - - std::vector mesh; - miniply::PLYReader reader{tv.filename.data()}; - if (!reader.valid()) - return {}; - - auto res = load_vertices_from_ply(reader); - if (!res->pos) - return {}; - Threedim::float_vec buf; - buf.assign(res->pos, res->pos + res->numVerts * 3); - delete res; - - mesh.push_back({.vertices = res->numVerts, .points = true}); - - if (!mesh.empty()) + // This part happens in a separate thread + if (auto mesh = Threedim::PlyFromFile(tv.filename, buf); !mesh.empty()) { - return [mesh = std::move(mesh), buf = std::move(buf)](ObjLoader& o) mutable - { - // This part happens in the execution thread - std::swap(o.meshinfo, mesh); - std::swap(o.complete, buf); - - o.rebuild_geometry(); - }; + return upload(std::move(mesh), std::move(buf)); } } return {}; } - } diff --git a/Threedim/ObjLoader.hpp b/Threedim/ObjLoader.hpp index 45cbd04..ed97cd8 100644 --- a/Threedim/ObjLoader.hpp +++ b/Threedim/ObjLoader.hpp @@ -6,8 +6,6 @@ #include #include -#include - namespace Threedim { diff --git a/Threedim/Ply.cpp b/Threedim/Ply.cpp new file mode 100644 index 0000000..5a6ff94 --- /dev/null +++ b/Threedim/Ply.cpp @@ -0,0 +1,214 @@ +#include "Ply.hpp" + +#include +namespace Threedim +{ + +struct TriMesh +{ + float_vec storage; + float* pos = nullptr; + float* uv = nullptr; + float* norm = nullptr; + float* color = nullptr; + uint32_t numVerts = 0; + + int* indices = nullptr; + uint32_t numIndices = 0; +}; + +bool print_ply_header(const char* filename) +{ + static const char* kPropertyTypes[] = { + "char", + "uchar", + "short", + "ushort", + "int", + "uint", + "float", + "double", + }; + + miniply::PLYReader reader(filename); + if (!reader.valid()) + { + return false; + } + using namespace miniply; + for (uint32_t i = 0, endI = reader.num_elements(); i < endI; i++) + { + const miniply::PLYElement* elem = reader.get_element(i); + fprintf(stderr, "element %s %u\n", elem->name.c_str(), elem->count); + for (const miniply::PLYProperty& prop : elem->properties) + { + if (prop.countType != miniply::PLYPropertyType::None) + { + fprintf( + stderr, + "property list %s %s %s\n", + kPropertyTypes[uint32_t(prop.countType)], + kPropertyTypes[uint32_t(prop.type)], + prop.name.c_str()); + } + else + { + fprintf( + stderr, + "property %s %s\n", + kPropertyTypes[uint32_t(prop.type)], + prop.name.c_str()); + } + } + } + fprintf(stderr, "end_header\n"); + + return true; +} +bool load_vert_from_ply(miniply::PLYReader& reader, TriMesh* trimesh, float_vec& buf) +{ + uint32_t pos_indices[3]; + uint32_t uv_indices[3]; + uint32_t n_indices[3]; + uint32_t col_indices[3]; + + if (reader.element_is(miniply::kPLYVertexElement) && reader.load_element()) + { + const auto N = reader.num_rows(); + if (N <= 0) + return false; + + trimesh->numVerts = N; + + bool pos = reader.find_pos(pos_indices); + bool uv = reader.find_texcoord(uv_indices); + bool norms = reader.find_normal(n_indices); + bool col = reader.find_color(col_indices); + if (!col) + { + col = reader.find_properties( + col_indices, 3, "diffuse_red", "diffuse_green", "diffuse_blue"); + } + + int num_elements = 0; + if (pos) + num_elements += 3; + if (uv) + num_elements += 2; + if (norms) + num_elements += 3; + if (col) + num_elements += 3; + + if (!pos) + return false; + + buf.resize(num_elements * trimesh->numVerts, boost::container::default_init); + + float* cur = buf.data(); + if (pos) + { + trimesh->pos = cur; + reader.extract_properties( + pos_indices, 3, miniply::PLYPropertyType::Float, trimesh->pos); + cur += 3 * N; + } + + if (uv) + { + trimesh->uv = cur; + reader.extract_properties( + uv_indices, 2, miniply::PLYPropertyType::Float, trimesh->uv); + cur += 2 * N; + } + + if (norms) + { + trimesh->norm = cur; + reader.extract_properties( + n_indices, 3, miniply::PLYPropertyType::Float, trimesh->norm); + cur += 3 * N; + } + + if (col) + { + const auto t = reader.element()->properties[col_indices[0]].type; + trimesh->color = cur; + reader.extract_properties( + col_indices, 3, miniply::PLYPropertyType::Float, trimesh->color); + switch (t) + { + case miniply::PLYPropertyType::Float: + break; + case miniply::PLYPropertyType::Char: + break; + case miniply::PLYPropertyType::UChar: + { + for (float *begin = trimesh->color, *end = trimesh->color + 3 * N; + begin != end; + ++begin) + *begin /= 255.f; + break; + } + default: + break; + } + } + return true; + } + return false; +} + +TriMesh load_vertices_from_ply(miniply::PLYReader& reader, float_vec& buf) +{ + TriMesh mesh; + + while (reader.has_element()) + { + if (load_vert_from_ply(reader, &mesh, buf)) + return mesh; + reader.next_element(); + } + + return {}; +} + +std::vector PlyFromFile(std::string_view filename, float_vec& buf) +{ + print_ply_header(filename.data()); + + std::vector meshes; + miniply::PLYReader reader{filename.data()}; + if (!reader.valid()) + return {}; + + auto res = load_vertices_from_ply(reader, buf); + if (!res.pos) + return {}; + + auto begin = buf.data(); + mesh m{}; + m.vertices = res.numVerts; + m.points = true; + m.pos_offset = 0; + if (res.uv) + { + m.texcoord_offset = res.uv - begin; + m.texcoord = true; + } + if (res.norm) + { + m.normal_offset = res.norm - begin; + m.normals = true; + } + if (res.color) + { + m.color_offset = res.color - begin; + m.colors = true; + } + + meshes.push_back(m); + return meshes; +} + +} diff --git a/Threedim/Ply.hpp b/Threedim/Ply.hpp new file mode 100644 index 0000000..f1ddb19 --- /dev/null +++ b/Threedim/Ply.hpp @@ -0,0 +1,13 @@ +#pragma once +#include + +#include + +namespace Threedim +{ + +std::vector PlyFromFile(std::string_view filename, float_vec& data); + +bool print_ply_header(const char* filename); + +} diff --git a/Threedim/TinyObj.hpp b/Threedim/TinyObj.hpp index f3bc703..75f185a 100644 --- a/Threedim/TinyObj.hpp +++ b/Threedim/TinyObj.hpp @@ -13,9 +13,11 @@ using float_vec = boost::container::vector>; struct mesh { int64_t vertices{}; - int64_t pos_offset{}, texcoord_offset{}, normal_offset{}; + // offset are in "elements", not bytes + int64_t pos_offset{}, texcoord_offset{}, normal_offset{}, color_offset{}; bool texcoord{}; bool normals{}; + bool colors{}; bool points{}; }; @@ -53,8 +55,7 @@ inline void rebuild_transform(auto& inputs, auto& outputs) toGL(model, outputs.geometry.transform); outputs.geometry.dirty_transform = true; } -struct PositionControl - : halp::xyz_spinboxes_f32<"Position", halp::range{-1000., 1000., 0.}> +struct PositionControl : halp::xyz_spinboxes_f32<"Position", halp::free_range_min<>> { void update(auto& o) { rebuild_transform(o.inputs, o.outputs); } };