From f85225e8988acae943e85c54ab511d854ec131d7 Mon Sep 17 00:00:00 2001 From: Fabio Pellacini Date: Tue, 6 Jul 2021 10:19:13 +0200 Subject: [PATCH] Better layout (#1258) --- libs/yocto/CMakeLists.txt | 2 + libs/yocto/yocto_imageio.cpp | 961 +++++ libs/yocto/yocto_imageio.h | 86 + libs/yocto/yocto_scene.cpp | 810 ----- libs/yocto/yocto_scene.h | 243 -- libs/yocto/yocto_sceneio.cpp | 1084 +----- libs/yocto/yocto_shape.cpp | 6435 +++++++++++++++++++++------------- libs/yocto/yocto_shape.h | 237 ++ libs/yocto/yocto_shapeio.cpp | 769 ++++ libs/yocto/yocto_shapeio.h | 100 + 10 files changed, 6075 insertions(+), 4652 deletions(-) create mode 100644 libs/yocto/yocto_imageio.cpp create mode 100644 libs/yocto/yocto_imageio.h create mode 100644 libs/yocto/yocto_shapeio.cpp create mode 100644 libs/yocto/yocto_shapeio.h diff --git a/libs/yocto/CMakeLists.txt b/libs/yocto/CMakeLists.txt index 8dd0b2746..73446d4f2 100644 --- a/libs/yocto/CMakeLists.txt +++ b/libs/yocto/CMakeLists.txt @@ -9,6 +9,8 @@ add_library(yocto yocto_scene.h yocto_scene.cpp yocto_trace.h yocto_trace.cpp yocto_commonio.h yocto_commonio.cpp + yocto_imageio.h yocto_imageio.cpp + yocto_shapeio.h yocto_shapeio.cpp yocto_sceneio.h yocto_sceneio.cpp yocto_cli.h yocto_cli.cpp yocto_parallel.h diff --git a/libs/yocto/yocto_imageio.cpp b/libs/yocto/yocto_imageio.cpp new file mode 100644 index 000000000..6f8b78bad --- /dev/null +++ b/libs/yocto/yocto_imageio.cpp @@ -0,0 +1,961 @@ +// +// Implementation for Yocto/Image Input and Output functions. +// + +// +// LICENSE: +// +// Copyright (c) 2016 -- 2021 Fabio Pellacini +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#include "yocto_imageio.h" + +#include +#include +#include +#include +#include +#include + +#include "ext/stb_image.h" +#include "ext/stb_image_resize.h" +#include "ext/stb_image_write.h" +#include "ext/tinyexr.h" +#include "yocto_color.h" +#include "yocto_commonio.h" + +// ----------------------------------------------------------------------------- +// USING DIRECTIVES +// ----------------------------------------------------------------------------- +namespace yocto { + +// using directives +using std::unique_ptr; +using namespace std::string_literals; + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMAGE IO +// ----------------------------------------------------------------------------- +namespace yocto { + +// Pfm load +[[maybe_unused]] static float* load_pfm( + const string& filename, int* width, int* height, int* components, int req) { + auto swap_endian = [](float value) -> float { + // https://stackoverflow.com/questions/105252/how-do-i-convert-between-big-endian-and-little-endian-values-in-c + static_assert(sizeof(char) == 1, "sizeof(char) == 1"); + static_assert(sizeof(float) == 4, "sizeof(float) == 4"); + union { + float value; + unsigned char bytes[4]; + } source, dest; + source.value = value; + for (auto k = 0; k < 4; k++) dest.bytes[k] = source.bytes[4 - k - 1]; + return dest.value; + }; + + auto file_error = [](FILE* fs) -> float* { + if (fs) fclose(fs); + return nullptr; + }; + + auto fs = fopen_utf8(filename, "rb"); + if (!fs) return nullptr; + + // buffer + auto buffer = array{}; + + // read magic + auto magic = array{}; + if (!fgets(buffer.data(), (int)buffer.size(), fs)) return file_error(fs); + if (sscanf(buffer.data(), "%2s", magic.data()) != 1) return file_error(fs); + if (magic[0] == 'P' && magic[1] == 'f') { + *components = 1; + } else if (magic[0] == 'P' && magic[1] == 'F') { + *components = 3; + } else { + return file_error(fs); + } + + // read width, height + if (!fgets(buffer.data(), (int)buffer.size(), fs)) return file_error(fs); + if (sscanf(buffer.data(), "%d %d", width, height) != 2) return file_error(fs); + + // read scale + auto scale = 0.0f; + if (!fgets(buffer.data(), (int)buffer.size(), fs)) return file_error(fs); + if (sscanf(buffer.data(), "%f", &scale) != 1) return file_error(fs); + + // read the data (flip y) + auto npixels = (size_t)*width * (size_t)*height; + auto nvalues = npixels * (size_t)*components; + auto nrow = (size_t)*width * (size_t)*components; + auto pixels = (float*)malloc(nvalues * 4); + for (auto j = *height - 1; j >= 0; j--) { + if (fread(pixels + j * nrow, 4, nrow, fs) != nrow) { + free(pixels); + return file_error(fs); + } + } + + // close file + fclose(fs); + + // endian conversion + if (scale > 0) { + for (auto i = (size_t)0; i < nvalues; ++i) { + pixels[i] = swap_endian(pixels[i]); + } + } + + // scale + auto scl = (scale > 0) ? scale : -scale; + if (scl != 1) { + for (auto i = (size_t)0; i < nvalues; i++) pixels[i] *= scl; + } + + // check convertions + if (req == 0 || *components == req) return pixels; + + // convert channels + auto cpixels = (float*)malloc(npixels * req * 4); + if (req == 1) { + if (*components == 3) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i] = + (pixels[i * 3 + 0] + pixels[i * 3 + 1] + pixels[i * 3 + 2]) / 3; + } + } + } else if (req == 2) { + if (*components == 1) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i * 2 + 0] = pixels[i]; + cpixels[i * 2 + 1] = 1; + } + } + if (*components == 3) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i * 2 + 0] = + (pixels[i * 3 + 0] + pixels[i * 3 + 1] + pixels[i * 3 + 2]) / 3; + cpixels[i * 2 + 1] = 1; + } + } + } else if (req == 3) { + if (*components == 1) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i * 3 + 0] = pixels[i]; + cpixels[i * 3 + 1] = pixels[i]; + cpixels[i * 3 + 2] = pixels[i]; + } + } + } else if (req == 4) { + if (*components == 1) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i * 4 + 0] = pixels[i]; + cpixels[i * 4 + 1] = pixels[i]; + cpixels[i * 4 + 2] = pixels[i]; + cpixels[i * 4 + 3] = 1; + } + } + if (*components == 3) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i * 4 + 0] = pixels[i * 3 + 0]; + cpixels[i * 4 + 1] = pixels[i * 3 + 1]; + cpixels[i * 4 + 2] = pixels[i * 3 + 2]; + cpixels[i * 4 + 3] = 1; + } + } + } else { + free(pixels); + free(cpixels); + return nullptr; + } + + // done + free(pixels); + return cpixels; +} + +// save pfm +[[maybe_unused]] static bool save_pfm(const char* filename, int width, + int height, int components, const float* pixels) { + auto file_error = [](FILE* fs) -> bool { + if (fs) fclose(fs); + return false; + }; + + auto fs = fopen_utf8(filename, "wb"); + if (!fs) return false; + + if (fprintf(fs, "%s\n", (components == 1) ? "Pf" : "PF") < 0) + return file_error(fs); + if (fprintf(fs, "%d %d\n", width, height) < 0) return file_error(fs); + if (fprintf(fs, "-1\n") < 0) return file_error(fs); + if (components == 1 || components == 3) { + for (auto j = height - 1; j >= 0; j--) { + if (fwrite(pixels + j * width * components, 4, width * components, fs) != + width * components) + return file_error(fs); + } + } else { + for (auto j = height - 1; j >= 0; j--) { + for (auto i = 0; i < width; i++) { + auto vz = 0.0f; + auto v = pixels + (j * width + i) * components; + if (fwrite(&v[0], 4, 1, fs) != 1) return file_error(fs); + if (fwrite(&v[1], 4, 1, fs) != 1) return file_error(fs); + if (components == 2) { + if (fwrite(&vz, 4, 1, fs) != 1) return file_error(fs); + } else { + if (fwrite(&v[2], 4, 1, fs) != 1) return file_error(fs); + } + } + } + } + + // close + fclose(fs); + + // done + return true; +} + +// Pfm load +static float* load_pfm_from_memory(const void* data, int size, int* width, + int* height, int* components, int req) { + auto swap_endian = [](float value) -> float { + // https://stackoverflow.com/questions/105252/how-do-i-convert-between-big-endian-and-little-endian-values-in-c + static_assert(sizeof(char) == 1, "sizeof(char) == 1"); + static_assert(sizeof(float) == 4, "sizeof(float) == 4"); + union { + float value; + unsigned char bytes[4]; + } source, dest; + source.value = value; + for (auto k = 0; k < 4; k++) dest.bytes[k] = source.bytes[4 - k - 1]; + return dest.value; + }; + + // get line + auto get_line = [](const char*& data, const char* end, char* buffer, + int bsize) -> bool { + if (data == end) return false; + while (data != end && *data != '\n') { + *buffer++ = *data++; + bsize--; + if (bsize < 1) return false; + } + if (data != end && *data == '\n') data++; + *buffer = '\0'; + return true; + }; + + // begin/end + auto cur = (const char*)data, end = (const char*)data + size; + + // buffer + auto buffer = array{}; + + // read magic + auto magic = array{}; + if (!get_line(cur, end, buffer.data(), buffer.size())) return nullptr; + if (sscanf(buffer.data(), "%2s", magic.data()) != 1) return nullptr; + if (magic[0] == 'P' && magic[1] == 'f') { + *components = 1; + } else if (magic[0] == 'P' && magic[1] == 'F') { + *components = 3; + } else { + return nullptr; + } + + // read width, height + if (!get_line(cur, end, buffer.data(), buffer.size())) return nullptr; + if (sscanf(buffer.data(), "%d %d", width, height) != 2) return nullptr; + + // read scale + auto scale = 0.0f; + if (!get_line(cur, end, buffer.data(), buffer.size())) return nullptr; + if (sscanf(buffer.data(), "%f", &scale) != 1) return nullptr; + + // read the data (flip y) + auto npixels = (size_t)*width * (size_t)*height; + auto nvalues = npixels * (size_t)*components; + auto nrow = (size_t)*width * (size_t)*components; + if (end - cur < 4 * nvalues) return nullptr; + auto pixels = (float*)malloc(nvalues * 4); + for (auto j = *height - 1; j >= 0; j--) { + memcpy(pixels + j * nrow, cur, 4 * nrow); + cur += 4 * nrow; + } + + // endian conversion + if (scale > 0) { + for (auto i = (size_t)0; i < nvalues; ++i) { + pixels[i] = swap_endian(pixels[i]); + } + } + + // scale + auto scl = (scale > 0) ? scale : -scale; + if (scl != 1) { + for (auto i = (size_t)0; i < nvalues; i++) pixels[i] *= scl; + } + + // check convertions + if (req == 0 || *components == req) return pixels; + + // convert channels + auto cpixels = (float*)malloc(npixels * req * 4); + if (req == 1) { + if (*components == 3) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i] = + (pixels[i * 3 + 0] + pixels[i * 3 + 1] + pixels[i * 3 + 2]) / 3; + } + } + } else if (req == 2) { + if (*components == 1) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i * 2 + 0] = pixels[i]; + cpixels[i * 2 + 1] = 1; + } + } + if (*components == 3) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i * 2 + 0] = + (pixels[i * 3 + 0] + pixels[i * 3 + 1] + pixels[i * 3 + 2]) / 3; + cpixels[i * 2 + 1] = 1; + } + } + } else if (req == 3) { + if (*components == 1) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i * 3 + 0] = pixels[i]; + cpixels[i * 3 + 1] = pixels[i]; + cpixels[i * 3 + 2] = pixels[i]; + } + } + } else if (req == 4) { + if (*components == 1) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i * 4 + 0] = pixels[i]; + cpixels[i * 4 + 1] = pixels[i]; + cpixels[i * 4 + 2] = pixels[i]; + cpixels[i * 4 + 3] = 1; + } + } + if (*components == 3) { + for (auto i = (size_t)0; i < npixels; i++) { + cpixels[i * 4 + 0] = pixels[i * 3 + 0]; + cpixels[i * 4 + 1] = pixels[i * 3 + 1]; + cpixels[i * 4 + 2] = pixels[i * 3 + 2]; + cpixels[i * 4 + 3] = 1; + } + } + } else { + free(pixels); + free(cpixels); + return nullptr; + } + + // done + return cpixels; +} + +// save pfm +static bool save_pfm_to_func( + void (*func)(void* context, const void* data, int size), void* context, + int width, int height, int components, const float* pixels) { + auto buffer = array{}; + func(context, (components == 1) ? "Pf\n" : "PF\n", 3); + func(context, buffer.data(), + snprintf(buffer.data(), buffer.size(), "%d %d\n", width, height)); + func(context, "-1\n", 3); + if (components == 1 || components == 3) { + for (auto j = height - 1; j >= 0; j--) { + func(context, pixels + j * width * components, 4 * width * components); + } + } else { + for (auto j = height - 1; j >= 0; j--) { + for (auto i = 0; i < width; i++) { + auto vz = 0.0f; + auto v = pixels + (j * width + i) * components; + func(context, &v[0], 4); + func(context, &v[1], 4); + if (components == 2) { + func(context, &vz, 4); + } else { + func(context, &v[2], 4); + } + } + } + } + + return true; +} + +// Check if an image is HDR based on filename. +bool is_hdr_filename(const string& filename) { + auto ext = path_extension(filename); + return ext == ".hdr" || ext == ".exr" || ext == ".pfm"; +} + +bool is_ldr_filename(const string& filename) { + auto ext = path_extension(filename); + return ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" || + ext == ".tga"; +} + +// Loads/saves an image. Chooses hdr or ldr based on file name. +image_data load_image(const string& filename) { + auto image = image_data{}; + load_image(filename, image); + return image; +} +void load_image(const string& filename, image_data& image) { + // conversion helpers + auto from_linear = [](const float* pixels, int width, int height) { + return vector{ + (vec4f*)pixels, (vec4f*)pixels + (size_t)width * (size_t)height}; + }; + auto from_srgb = [](const byte* pixels, int width, int height) { + auto pixelsf = vector((size_t)width * (size_t)height); + for (auto idx = (size_t)0; idx < pixelsf.size(); idx++) { + pixelsf[idx] = byte_to_float(((vec4b*)pixels)[idx]); + } + return pixelsf; + }; + + auto ext = path_extension(filename); + if (ext == ".exr" || ext == ".EXR") { + auto buffer = load_binary(filename); + auto pixels = (float*)nullptr; + if (LoadEXRFromMemory(&pixels, &image.width, &image.height, buffer.data(), + buffer.size(), nullptr) != 0) + throw io_error::read_error(filename); + image.linear = true; + image.pixels = from_linear(pixels, image.width, image.height); + free(pixels); + } else if (ext == ".pfm" || ext == ".PFM") { + auto buffer = load_binary(filename); + auto ncomp = 0; + auto pixels = load_pfm_from_memory(buffer.data(), (int)buffer.size(), + &image.width, &image.height, &ncomp, 4); + if (!pixels) throw io_error::read_error(filename); + image.linear = true; + image.pixels = from_linear(pixels, image.width, image.height); + free(pixels); + } else if (ext == ".hdr" || ext == ".HDR") { + auto buffer = load_binary(filename); + auto ncomp = 0; + auto pixels = stbi_loadf_from_memory(buffer.data(), (int)buffer.size(), + &image.width, &image.height, &ncomp, 4); + if (!pixels) throw io_error::read_error(filename); + image.linear = true; + image.pixels = from_linear(pixels, image.width, image.height); + free(pixels); + } else if (ext == ".png" || ext == ".PNG") { + auto buffer = load_binary(filename); + auto ncomp = 0; + auto pixels = stbi_load_from_memory(buffer.data(), (int)buffer.size(), + &image.width, &image.height, &ncomp, 4); + if (!pixels) throw io_error::read_error(filename); + image.linear = false; + image.pixels = from_srgb(pixels, image.width, image.height); + free(pixels); + } else if (ext == ".jpg" || ext == ".JPG" || ext == ".jpeg" || + ext == ".JPEG") { + auto buffer = load_binary(filename); + auto ncomp = 0; + auto pixels = stbi_load_from_memory(buffer.data(), (int)buffer.size(), + &image.width, &image.height, &ncomp, 4); + if (!pixels) throw io_error::read_error(filename); + image.linear = false; + image.pixels = from_srgb(pixels, image.width, image.height); + free(pixels); + } else if (ext == ".tga" || ext == ".TGA") { + auto buffer = load_binary(filename); + auto ncomp = 0; + auto pixels = stbi_load_from_memory(buffer.data(), (int)buffer.size(), + &image.width, &image.height, &ncomp, 4); + if (!pixels) throw io_error::read_error(filename); + image.linear = false; + image.pixels = from_srgb(pixels, image.width, image.height); + free(pixels); + } else if (ext == ".bmp" || ext == ".BMP") { + auto buffer = load_binary(filename); + auto ncomp = 0; + auto pixels = stbi_load_from_memory(buffer.data(), (int)buffer.size(), + &image.width, &image.height, &ncomp, 4); + if (!pixels) throw io_error::read_error(filename); + image.linear = false; + image.pixels = from_srgb(pixels, image.width, image.height); + free(pixels); + } else if (ext == ".ypreset" || ext == ".YPRESET") { + // create preset + image = make_image_preset(path_basename(filename)); + } else { + throw io_error::format_error(filename); + } +} + +// Saves an hdr image. +void save_image(const string& filename, const image_data& image) { + // conversion helpers + auto to_linear = [](const image_data& image) { + if (image.linear) return image.pixels; + auto pixelsf = vector(image.pixels.size()); + srgb_to_rgb(pixelsf, image.pixels); + return pixelsf; + }; + auto to_srgb = [](const image_data& image) { + auto pixelsb = vector(image.pixels.size()); + if (image.linear) { + rgb_to_srgb(pixelsb, image.pixels); + } else { + float_to_byte(pixelsb, image.pixels); + } + return pixelsb; + }; + + // write data + auto stbi_write_data = [](void* context, void* data, int size) { + auto& buffer = *(vector*)context; + buffer.insert(buffer.end(), (byte*)data, (byte*)data + size); + }; + auto pfm_write_data = [](void* context, const void* data, int size) { + auto& buffer = *(vector*)context; + buffer.insert(buffer.end(), (byte*)data, (byte*)data + size); + }; + + auto ext = path_extension(filename); + if (ext == ".hdr" || ext == ".HDR") { + auto buffer = vector{}; + if (!stbi_write_hdr_to_func(stbi_write_data, &buffer, (int)image.width, + (int)image.height, 4, (const float*)to_linear(image).data())) + throw io_error::write_error(filename); + save_binary(filename, buffer); + } else if (ext == ".pfm" || ext == ".PFM") { + auto buffer = vector{}; + if (!save_pfm_to_func(pfm_write_data, &buffer, image.width, image.height, 4, + (const float*)to_linear(image).data())) + throw io_error::write_error(filename); + save_binary(filename, buffer); + } else if (ext == ".exr" || ext == ".EXR") { + auto data = (byte*)nullptr; + auto size = (size_t)0; + if (SaveEXRToMemory((const float*)to_linear(image).data(), (int)image.width, + (int)image.height, 4, 1, &data, &size, nullptr) < 0) + throw io_error::write_error(filename); + auto buffer = vector{data, data + size}; + free(data); + save_binary(filename, buffer); + } else if (ext == ".png" || ext == ".PNG") { + auto buffer = vector{}; + if (!stbi_write_png_to_func(stbi_write_data, &buffer, (int)image.width, + (int)image.height, 4, (const byte*)to_srgb(image).data(), + (int)image.width * 4)) + throw io_error::write_error(filename); + save_binary(filename, buffer); + } else if (ext == ".jpg" || ext == ".JPG" || ext == ".jpeg" || + ext == ".JPEG") { + auto buffer = vector{}; + if (!stbi_write_jpg_to_func(stbi_write_data, &buffer, (int)image.width, + (int)image.height, 4, (const byte*)to_srgb(image).data(), 75)) + throw io_error::write_error(filename); + save_binary(filename, buffer); + } else if (ext == ".tga" || ext == ".TGA") { + auto buffer = vector{}; + if (!stbi_write_tga_to_func(stbi_write_data, &buffer, (int)image.width, + (int)image.height, 4, (const byte*)to_srgb(image).data())) + throw io_error::write_error(filename); + save_binary(filename, buffer); + } else if (ext == ".bmp" || ext == ".BMP") { + auto buffer = vector{}; + if (!stbi_write_bmp_to_func(stbi_write_data, &buffer, (int)image.width, + (int)image.height, 4, (const byte*)to_srgb(image).data())) + throw io_error::write_error(filename); + save_binary(filename, buffer); + } else { + throw io_error::format_error(filename); + } +} + +image_data make_image_preset(const string& type_) { + auto type = path_basename(type_); + auto width = 1024, height = 1024; + if (type.find("sky") != type.npos) width = 2048; + if (type.find("images2") != type.npos) width = 2048; + if (type == "grid") { + return make_grid(width, height); + } else if (type == "checker") { + return make_checker(width, height); + } else if (type == "bumps") { + return make_bumps(width, height); + } else if (type == "uvramp") { + return make_uvramp(width, height); + } else if (type == "gammaramp") { + return make_gammaramp(width, height); + } else if (type == "blackbodyramp") { + return make_blackbodyramp(width, height); + } else if (type == "uvgrid") { + return make_uvgrid(width, height); + } else if (type == "colormapramp") { + return make_colormapramp(width, height); + } else if (type == "sky") { + return make_sunsky(width, height, pif / 4, 3.0f, false, 1.0f, 1.0f, + vec3f{0.7f, 0.7f, 0.7f}); + } else if (type == "sunsky") { + return make_sunsky(width, height, pif / 4, 3.0f, true, 1.0f, 1.0f, + vec3f{0.7f, 0.7f, 0.7f}); + } else if (type == "noise") { + return make_noisemap(width, height, 1); + } else if (type == "fbm") { + return make_fbmmap(width, height, 1); + } else if (type == "ridge") { + return make_ridgemap(width, height, 1); + } else if (type == "turbulence") { + return make_turbulencemap(width, height, 1); + } else if (type == "bump-normal") { + return make_bumps(width, height); + // TODO(fabio): fix color space + // img = srgb_to_rgb(bump_to_normal(img, 0.05f)); + } else if (type == "images1") { + auto sub_types = vector{"grid", "uvgrid", "checker", "gammaramp", + "bumps", "bump-normal", "noise", "fbm", "blackbodyramp"}; + auto sub_images = vector(); + for (auto& sub_type : sub_types) + sub_images.push_back(make_image_preset(sub_type)); + auto montage_size = zero2i; + for (auto& sub_image : sub_images) { + montage_size.x += sub_image.width; + montage_size.y = max(montage_size.y, sub_image.height); + } + auto image = make_image( + montage_size.x, montage_size.y, sub_images[0].linear); + auto pos = 0; + for (auto& sub_image : sub_images) { + set_region(image, sub_image, pos, 0); + pos += sub_image.width; + } + return image; + } else if (type == "images2") { + auto sub_types = vector{"sky", "sunsky"}; + auto sub_images = vector(); + for (auto& sub_type : sub_types) + sub_images.push_back(make_image_preset(sub_type)); + auto montage_size = zero2i; + for (auto& sub_image : sub_images) { + montage_size.x += sub_image.width; + montage_size.y = max(montage_size.y, sub_image.height); + } + auto image = make_image( + montage_size.x, montage_size.y, sub_images[0].linear); + auto pos = 0; + for (auto& sub_image : sub_images) { + set_region(image, sub_image, pos, 0); + pos += sub_image.width; + } + return image; + } else if (type == "test-floor") { + return add_border(make_grid(width, height), 0.0025f); + } else if (type == "test-grid") { + return make_grid(width, height); + } else if (type == "test-checker") { + return make_checker(width, height); + } else if (type == "test-bumps") { + return make_bumps(width, height); + } else if (type == "test-uvramp") { + return make_uvramp(width, height); + } else if (type == "test-gammaramp") { + return make_gammaramp(width, height); + } else if (type == "test-blackbodyramp") { + return make_blackbodyramp(width, height); + } else if (type == "test-colormapramp") { + return make_colormapramp(width, height); + // TODO(fabio): fix color space + // img = srgb_to_rgb(img); + } else if (type == "test-uvgrid") { + return make_uvgrid(width, height); + } else if (type == "test-sky") { + return make_sunsky(width, height, pif / 4, 3.0f, false, 1.0f, 1.0f, + vec3f{0.7f, 0.7f, 0.7f}); + } else if (type == "test-sunsky") { + return make_sunsky(width, height, pif / 4, 3.0f, true, 1.0f, 1.0f, + vec3f{0.7f, 0.7f, 0.7f}); + } else if (type == "test-noise") { + return make_noisemap(width, height); + } else if (type == "test-fbm") { + return make_noisemap(width, height); + } else if (type == "test-bumps-normal") { + return bump_to_normal(make_bumps(width, height), 0.05f); + } else if (type == "test-bumps-displacement") { + return make_bumps(width, height); + // TODO(fabio): fix color space + // img = srgb_to_rgb(img); + } else if (type == "test-fbm-displacement") { + return make_fbmmap(width, height); + // TODO(fabio): fix color space + // img = srgb_to_rgb(img); + } else if (type == "test-checker-opacity") { + return make_checker(width, height, 1, {1, 1, 1, 1}, {0, 0, 0, 0}); + } else if (type == "test-grid-opacity") { + return make_grid(width, height, 1, {1, 1, 1, 1}, {0, 0, 0, 0}); + } else { + throw io_error::preset_error(type_); + } +} + +// Loads/saves an image. Chooses hdr or ldr based on file name. +bool load_image(const string& filename, image_data& image, string& error) { + try { + load_image(filename, image); + return true; + } catch (const io_error& exception) { + error = exception.what(); + return false; + } +} + +// Saves an hdr image. +bool save_image( + const string& filename, const image_data& image, string& error) { + try { + save_image(filename, image); + return true; + } catch (const io_error& exception) { + error = exception.what(); + return false; + } +} + +bool make_image_preset(image_data& image, const string& type, string& error) { + try { + image = make_image_preset(type); + return true; + } catch (const io_error& exception) { + error = exception.what(); + return false; + } +} + +} // namespace yocto + +#if 0 + +// ----------------------------------------------------------------------------- +// IMPLEMENTATION FOR VOLUME IMAGE IO +// ----------------------------------------------------------------------------- +namespace yocto { + +// Volume load +static bool load_yvol(const string& filename, int& width, int& height, + int& depth, int& components, vector& voxels, string& error) { + // error helpers + auto open_error = [filename, &error]() { + error = filename + ": file not found"; + return false; + }; + auto parse_error = [filename, &error]() { + error = filename + ": parse error"; + return false; + }; + auto read_error = [filename, &error]() { + error = filename + ": read error"; + return false; + }; + + // Split a string + auto split_string = [](const string& str) -> vector { + auto ret = vector(); + if (str.empty()) return ret; + auto lpos = (size_t)0; + while (lpos != string::npos) { + auto pos = str.find_first_of(" \t\n\r", lpos); + if (pos != string::npos) { + if (pos > lpos) ret.push_back(str.substr(lpos, pos - lpos)); + lpos = pos + 1; + } else { + if (lpos < str.size()) ret.push_back(str.substr(lpos)); + lpos = pos; + } + } + return ret; + }; + + auto fs = fopen_utf8(filename.c_str(), "rb"); + auto fs_guard = unique_ptr(fs, &fclose); + if (!fs) return open_error(); + + // buffer + auto buffer = array{}; + auto toks = vector(); + + // read magic + if (!fgets(buffer.data(), (int)buffer.size(), fs)) return parse_error(); + toks = split_string(buffer.data()); + if (toks[0] != "YVOL") return parse_error(); + + // read width, height + if (!fgets(buffer.data(), (int)buffer.size(), fs)) return parse_error(); + toks = split_string(buffer.data()); + width = atoi(toks[0].c_str()); + height = atoi(toks[1].c_str()); + depth = atoi(toks[2].c_str()); + components = atoi(toks[3].c_str()); + + // read data + auto nvoxels = (size_t)width * (size_t)height * (size_t)depth; + auto nvalues = nvoxels * (size_t)components; + voxels = vector(nvalues); + if (!read_values(fs, voxels.data(), nvalues)) return read_error(); + + // done + return true; +} + +// save pfm +static bool save_yvol(const string& filename, int width, int height, int depth, + int components, const vector& voxels, string& error) { + // error helpers + auto open_error = [filename, &error]() { + error = filename + ": file not found"; + return false; + }; + auto write_error = [filename, &error]() { + error = filename + ": read error"; + return false; + }; + + auto fs = fopen_utf8(filename.c_str(), "wb"); + auto fs_guard = unique_ptr(fs, &fclose); + if (!fs) return open_error(); + + if (!write_text(fs, "YVOL\n")) return write_error(); + if (!write_text(fs, std::to_string(width) + " " + std::to_string(height) + + " " + std::to_string(depth) + " " + + std::to_string(components) + "\n")) + return write_error(); + auto nvalues = (size_t)width * (size_t)height * (size_t)depth * + (size_t)components; + if (!write_values(fs, voxels.data(), nvalues)) return write_error(); + return true; +} + +// Loads volume data from binary format. +bool load_volume(const string& filename, volume& vol, string& error) { + auto read_error = [filename, &error]() { + error = filename + ": read error"; + return false; + }; + auto width = 0, height = 0, depth = 0, ncomp = 0; + auto voxels = vector{}; + if (!load_yvol(filename, width, height, depth, ncomp, voxels, error)) + return false; + if (ncomp != 1) voxels = convert_components(voxels, ncomp, 1); + vol = volume{{width, height, depth}, (const float*)voxels.data()}; + return true; +} + +// Saves volume data in binary format. +bool save_volume( + const string& filename, const volume& vol, string& error) { + return save_yvol(filename, vol.width(), vol.height(), vol.depth(), 1, + {vol.data(), vol.data() + vol.count()}, error); +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMPLEMENTATION FOR DEPRECATED CODE +// ----------------------------------------------------------------------------- +namespace yocto { + +image filter_bilateral(const image& img, float spatial_sigma, + float range_sigma, const vector>& features, + const vector& features_sigma) { + auto filtered = image{img.imsize(), zero4f}; + auto filter_width = (int)ceil(2.57f * spatial_sigma); + auto sw = 1 / (2.0f * spatial_sigma * spatial_sigma); + auto rw = 1 / (2.0f * range_sigma * range_sigma); + auto fw = vector(); + for (auto feature_sigma : features_sigma) + fw.push_back(1 / (2.0f * feature_sigma * feature_sigma)); + for (auto j = 0; j < img.height(); j++) { + for (auto i = 0; i < img.width(); i++) { + auto av = zero4f; + auto aw = 0.0f; + for (auto fj = -filter_width; fj <= filter_width; fj++) { + for (auto fi = -filter_width; fi <= filter_width; fi++) { + auto ii = i + fi, jj = j + fj; + if (ii < 0 || jj < 0) continue; + if (ii >= img.width() || jj >= img.height()) continue; + auto uv = vec2f{float(i - ii), float(j - jj)}; + auto rgb = img[{i, j}] - img[{i, j}]; + auto w = (float)exp(-dot(uv, uv) * sw) * + (float)exp(-dot(rgb, rgb) * rw); + for (auto fi = 0; fi < features.size(); fi++) { + auto feat = features[fi][{i, j}] - features[fi][{i, j}]; + w *= exp(-dot(feat, feat) * fw[fi]); + } + av += w * img[{ii, jj}]; + aw += w; + } + } + filtered[{i, j}] = av / aw; + } + } + return filtered; +} + +image filter_bilateral( + const image& img, float spatial_sigma, float range_sigma) { + auto filtered = image{img.imsize(), zero4f}; + auto fwidth = (int)ceil(2.57f * spatial_sigma); + auto sw = 1 / (2.0f * spatial_sigma * spatial_sigma); + auto rw = 1 / (2.0f * range_sigma * range_sigma); + for (auto j = 0; j < img.height(); j++) { + for (auto i = 0; i < img.width(); i++) { + auto av = zero4f; + auto aw = 0.0f; + for (auto fj = -fwidth; fj <= fwidth; fj++) { + for (auto fi = -fwidth; fi <= fwidth; fi++) { + auto ii = i + fi, jj = j + fj; + if (ii < 0 || jj < 0) continue; + if (ii >= img.width() || jj >= img.height()) continue; + auto uv = vec2f{float(i - ii), float(j - jj)}; + auto rgb = img[{i, j}] - img[{ii, jj}]; + auto w = exp(-dot(uv, uv) * sw) * exp(-dot(rgb, rgb) * rw); + av += w * img[{ii, jj}]; + aw += w; + } + } + filtered[{i, j}] = av / aw; + } + } + return filtered; +} + +} // namespace yocto + +#endif diff --git a/libs/yocto/yocto_imageio.h b/libs/yocto/yocto_imageio.h new file mode 100644 index 000000000..ff74dd5eb --- /dev/null +++ b/libs/yocto/yocto_imageio.h @@ -0,0 +1,86 @@ +// +// # Yocto/ImageIO: Image serialization +// +// Yocto/ImageIO supports loading and saving images from Png, Jpg, Tga, Bmp, +// Pnm, Hdr, Exr, Pfm. +// Yocto/ImageIO is implemented in `yocto_imageio.h` and `yocto_imageio.cpp` +// and depends on `stb_image.h`, `stb_image_write.h`, and `tinyexr.h`. +// + +// +// LICENSE: +// +// Copyright (c) 2016 -- 2021 Fabio Pellacini +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#ifndef _YOCTO_IMAGEIO_H_ +#define _YOCTO_IMAGEIO_H_ + +// ----------------------------------------------------------------------------- +// INCLUDES +// ----------------------------------------------------------------------------- + +#include +#include +#include +#include + +#include "yocto_image.h" + +// ----------------------------------------------------------------------------- +// USING DIRECTIVES +// ----------------------------------------------------------------------------- +namespace yocto { + +// using directives +using std::pair; +using std::string; +using std::vector; + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMAGE IO +// ----------------------------------------------------------------------------- +namespace yocto { + +// Check if an image is HDR or LDR based on filename. +bool is_hdr_filename(const string& filename); +bool is_ldr_filename(const string& filename); + +// Loads/saves a 4 channels float/byte image in linear/srgb color space. +image_data load_image(const string& filename); +void load_image(const string& filename, image_data& image); +void save_image(const string& filename, const image_data& image); + +// Make presets. Supported mostly in IO. +image_data make_image_preset(const string& type); + +// Loads/saves a 4 channels float/byte image in linear/srgb color space. +bool load_image(const string& filename, image_data& img, string& error); +bool save_image(const string& filename, const image_data& img, string& error); + +// Make presets. Supported mostly in IO. +bool make_image_preset(image_data& image, const string& type, string& error); + +} // namespace yocto + +#endif diff --git a/libs/yocto/yocto_scene.cpp b/libs/yocto/yocto_scene.cpp index eae580ff4..73bd64e9c 100644 --- a/libs/yocto/yocto_scene.cpp +++ b/libs/yocto/yocto_scene.cpp @@ -271,478 +271,6 @@ bool has_volume(const material_point& material) { } // namespace yocto -// ----------------------------------------------------------------------------- -// SHAPE PROPERTIES -// ----------------------------------------------------------------------------- -namespace yocto { - -// Interpolate vertex data -vec3f eval_position(const shape_data& shape, int element, const vec2f& uv) { - if (!shape.points.empty()) { - auto& point = shape.points[element]; - return shape.positions[point]; - } else if (!shape.lines.empty()) { - auto& line = shape.lines[element]; - return interpolate_line( - shape.positions[line.x], shape.positions[line.y], uv.x); - } else if (!shape.triangles.empty()) { - auto& triangle = shape.triangles[element]; - return interpolate_triangle(shape.positions[triangle.x], - shape.positions[triangle.y], shape.positions[triangle.z], uv); - } else if (!shape.quads.empty()) { - auto& quad = shape.quads[element]; - return interpolate_quad(shape.positions[quad.x], shape.positions[quad.y], - shape.positions[quad.z], shape.positions[quad.w], uv); - } else { - return {0, 0, 0}; - } -} - -vec3f eval_normal(const shape_data& shape, int element, const vec2f& uv) { - if (shape.normals.empty()) return eval_element_normal(shape, element); - if (!shape.points.empty()) { - auto& point = shape.points[element]; - return normalize(shape.normals[point]); - } else if (!shape.lines.empty()) { - auto& line = shape.lines[element]; - return normalize( - interpolate_line(shape.normals[line.x], shape.normals[line.y], uv.x)); - } else if (!shape.triangles.empty()) { - auto& triangle = shape.triangles[element]; - return normalize(interpolate_triangle(shape.normals[triangle.x], - shape.normals[triangle.y], shape.normals[triangle.z], uv)); - } else if (!shape.quads.empty()) { - auto& quad = shape.quads[element]; - return normalize( - interpolate_quad(shape.normals[quad.x], shape.normals[quad.y], - shape.normals[quad.z], shape.normals[quad.w], uv)); - } else { - return {0, 0, 1}; - } -} - -vec3f eval_tangent(const shape_data& shape, int element, const vec2f& uv) { - return eval_normal(shape, element, uv); -} - -vec2f eval_texcoord(const shape_data& shape, int element, const vec2f& uv) { - if (shape.texcoords.empty()) return uv; - if (!shape.points.empty()) { - auto& point = shape.points[element]; - return shape.texcoords[point]; - } else if (!shape.lines.empty()) { - auto& line = shape.lines[element]; - return interpolate_line( - shape.texcoords[line.x], shape.texcoords[line.y], uv.x); - } else if (!shape.triangles.empty()) { - auto& triangle = shape.triangles[element]; - return interpolate_triangle(shape.texcoords[triangle.x], - shape.texcoords[triangle.y], shape.texcoords[triangle.z], uv); - } else if (!shape.quads.empty()) { - auto& quad = shape.quads[element]; - return interpolate_quad(shape.texcoords[quad.x], shape.texcoords[quad.y], - shape.texcoords[quad.z], shape.texcoords[quad.w], uv); - } else { - return uv; - } -} - -vec4f eval_color(const shape_data& shape, int element, const vec2f& uv) { - if (shape.colors.empty()) return {1, 1, 1, 1}; - if (!shape.points.empty()) { - auto& point = shape.points[element]; - return shape.colors[point]; - } else if (!shape.lines.empty()) { - auto& line = shape.lines[element]; - return interpolate_line(shape.colors[line.x], shape.colors[line.y], uv.x); - } else if (!shape.triangles.empty()) { - auto& triangle = shape.triangles[element]; - return interpolate_triangle(shape.colors[triangle.x], - shape.colors[triangle.y], shape.colors[triangle.z], uv); - } else if (!shape.quads.empty()) { - auto& quad = shape.quads[element]; - return interpolate_quad(shape.colors[quad.x], shape.colors[quad.y], - shape.colors[quad.z], shape.colors[quad.w], uv); - } else { - return {0, 0}; - } -} - -float eval_radius(const shape_data& shape, int element, const vec2f& uv) { - if (shape.radius.empty()) return 0; - if (!shape.points.empty()) { - auto& point = shape.points[element]; - return shape.radius[point]; - } else if (!shape.lines.empty()) { - auto& line = shape.lines[element]; - return interpolate_line(shape.radius[line.x], shape.radius[line.y], uv.x); - } else if (!shape.triangles.empty()) { - auto& triangle = shape.triangles[element]; - return interpolate_triangle(shape.radius[triangle.x], - shape.radius[triangle.y], shape.radius[triangle.z], uv); - } else if (!shape.quads.empty()) { - auto& quad = shape.quads[element]; - return interpolate_quad(shape.radius[quad.x], shape.radius[quad.y], - shape.radius[quad.z], shape.radius[quad.w], uv); - } else { - return 0; - } -} - -// Evaluate element normals -vec3f eval_element_normal(const shape_data& shape, int element) { - if (!shape.points.empty()) { - return {0, 0, 1}; - } else if (!shape.lines.empty()) { - auto& line = shape.lines[element]; - return line_tangent(shape.positions[line.x], shape.positions[line.y]); - } else if (!shape.triangles.empty()) { - auto& triangle = shape.triangles[element]; - return triangle_normal(shape.positions[triangle.x], - shape.positions[triangle.y], shape.positions[triangle.z]); - } else if (!shape.quads.empty()) { - auto& quad = shape.quads[element]; - return quad_normal(shape.positions[quad.x], shape.positions[quad.y], - shape.positions[quad.z], shape.positions[quad.w]); - } else { - return {0, 0, 0}; - } -} - -// Compute per-vertex normals/tangents for lines/triangles/quads. -vector compute_normals(const shape_data& shape) { - if (!shape.points.empty()) { - return vector(shape.positions.size(), {0, 0, 1}); - } else if (!shape.lines.empty()) { - return lines_tangents(shape.lines, shape.positions); - } else if (!shape.triangles.empty()) { - return triangles_normals(shape.triangles, shape.positions); - } else if (!shape.quads.empty()) { - return quads_normals(shape.quads, shape.positions); - } else { - return vector(shape.positions.size(), {0, 0, 1}); - } -} -void compute_normals(vector& normals, const shape_data& shape) { - if (!shape.points.empty()) { - normals.assign(shape.positions.size(), {0, 0, 1}); - } else if (!shape.lines.empty()) { - lines_tangents(normals, shape.lines, shape.positions); - } else if (!shape.triangles.empty()) { - triangles_normals(normals, shape.triangles, shape.positions); - } else if (!shape.quads.empty()) { - quads_normals(normals, shape.quads, shape.positions); - } else { - normals.assign(shape.positions.size(), {0, 0, 1}); - } -} - -// Shape sampling -vector sample_shape_cdf(const shape_data& shape) { - if (!shape.points.empty()) { - return sample_points_cdf((int)shape.points.size()); - } else if (!shape.lines.empty()) { - return sample_lines_cdf(shape.lines, shape.positions); - } else if (!shape.triangles.empty()) { - return sample_triangles_cdf(shape.triangles, shape.positions); - } else if (!shape.quads.empty()) { - return sample_quads_cdf(shape.quads, shape.positions); - } else { - return sample_points_cdf((int)shape.positions.size()); - } -} - -void sample_shape_cdf(vector& cdf, const shape_data& shape) { - if (!shape.points.empty()) { - sample_points_cdf(cdf, (int)shape.points.size()); - } else if (!shape.lines.empty()) { - sample_lines_cdf(cdf, shape.lines, shape.positions); - } else if (!shape.triangles.empty()) { - sample_triangles_cdf(cdf, shape.triangles, shape.positions); - } else if (!shape.quads.empty()) { - sample_quads_cdf(cdf, shape.quads, shape.positions); - } else { - sample_points_cdf(cdf, (int)shape.positions.size()); - } -} - -shape_point sample_shape(const shape_data& shape, const vector& cdf, - float rn, const vec2f& ruv) { - if (!shape.points.empty()) { - auto element = sample_points(cdf, rn); - return {element, {0, 0}}; - } else if (!shape.lines.empty()) { - auto [element, u] = sample_lines(cdf, rn, ruv.x); - return {element, {u, 0}}; - } else if (!shape.triangles.empty()) { - auto [element, uv] = sample_triangles(cdf, rn, ruv); - return {element, uv}; - } else if (!shape.quads.empty()) { - auto [element, uv] = sample_quads(cdf, rn, ruv); - return {element, uv}; - } else { - auto element = sample_points(cdf, rn); - return {element, {0, 0}}; - } -} - -vector sample_shape( - const shape_data& shape, int num_samples, uint64_t seed) { - auto cdf = sample_shape_cdf(shape); - auto points = vector(num_samples); - auto rng = make_rng(seed); - for (auto& point : points) { - point = sample_shape(shape, cdf, rand1f(rng), rand2f(rng)); - } - return points; -} - -// Conversions -shape_data quads_to_triangles(const shape_data& shape) { - auto result = shape; - quads_to_triangles(result, result); - return result; -} -void quads_to_triangles(shape_data& result, const shape_data& shape) { - result.triangles = quads_to_triangles(shape.quads); - result.quads = {}; -} - -// Subdivision -shape_data subdivide_shape( - const shape_data& shape, int subdivisions, bool catmullclark) { - // This should probably be re-implemented in a faster fashion, - // but how it is not obvious - if (subdivisions == 0) return shape; - auto subdivided = shape_data{}; - if (!subdivided.points.empty()) { - subdivided = shape; - } else if (!subdivided.lines.empty()) { - std::tie(std::ignore, subdivided.normals) = subdivide_lines( - shape.lines, shape.normals, subdivisions); - std::tie(std::ignore, subdivided.texcoords) = subdivide_lines( - shape.lines, shape.texcoords, subdivisions); - std::tie(std::ignore, subdivided.colors) = subdivide_lines( - shape.lines, shape.colors, subdivisions); - std::tie(std::ignore, subdivided.radius) = subdivide_lines( - subdivided.lines, shape.radius, subdivisions); - std::tie(subdivided.lines, subdivided.positions) = subdivide_lines( - shape.lines, shape.positions, subdivisions); - } else if (!subdivided.triangles.empty()) { - std::tie(std::ignore, subdivided.normals) = subdivide_triangles( - shape.triangles, shape.normals, subdivisions); - std::tie(std::ignore, subdivided.texcoords) = subdivide_triangles( - shape.triangles, shape.texcoords, subdivisions); - std::tie(std::ignore, subdivided.colors) = subdivide_triangles( - shape.triangles, shape.colors, subdivisions); - std::tie(std::ignore, subdivided.radius) = subdivide_triangles( - shape.triangles, shape.radius, subdivisions); - std::tie(subdivided.triangles, subdivided.positions) = subdivide_triangles( - shape.triangles, shape.positions, subdivisions); - } else if (!subdivided.quads.empty() && !catmullclark) { - std::tie(std::ignore, subdivided.normals) = subdivide_quads( - shape.quads, shape.normals, subdivisions); - std::tie(std::ignore, subdivided.texcoords) = subdivide_quads( - shape.quads, shape.texcoords, subdivisions); - std::tie(std::ignore, subdivided.colors) = subdivide_quads( - shape.quads, shape.colors, subdivisions); - std::tie(std::ignore, subdivided.radius) = subdivide_quads( - shape.quads, shape.radius, subdivisions); - std::tie(subdivided.quads, subdivided.positions) = subdivide_quads( - shape.quads, shape.positions, subdivisions); - } else if (!subdivided.quads.empty() && catmullclark) { - std::tie(std::ignore, subdivided.normals) = subdivide_catmullclark( - shape.quads, shape.normals, subdivisions); - std::tie(std::ignore, subdivided.texcoords) = subdivide_catmullclark( - shape.quads, shape.texcoords, subdivisions); - std::tie(std::ignore, subdivided.colors) = subdivide_catmullclark( - shape.quads, shape.colors, subdivisions); - std::tie(std::ignore, subdivided.radius) = subdivide_catmullclark( - shape.quads, shape.radius, subdivisions); - std::tie(subdivided.quads, subdivided.positions) = subdivide_catmullclark( - shape.quads, shape.positions, subdivisions); - } else { - // empty shape - } - return subdivided; -} - -// Interpolate vertex data -vec3f eval_position(const fvshape_data& shape, int element, const vec2f& uv) { - if (!shape.quadspos.empty()) { - auto& quad = shape.quadspos[element]; - return interpolate_quad(shape.positions[quad.x], shape.positions[quad.y], - shape.positions[quad.z], shape.positions[quad.w], uv); - } else { - return {0, 0, 0}; - } -} - -vec3f eval_normal(const fvshape_data& shape, int element, const vec2f& uv) { - if (shape.normals.empty()) return eval_element_normal(shape, element); - if (!shape.quadspos.empty()) { - auto& quad = shape.quadsnorm[element]; - return normalize( - interpolate_quad(shape.normals[quad.x], shape.normals[quad.y], - shape.normals[quad.z], shape.normals[quad.w], uv)); - } else { - return {0, 0, 1}; - } -} - -vec2f eval_texcoord(const fvshape_data& shape, int element, const vec2f& uv) { - if (shape.texcoords.empty()) return uv; - if (!shape.quadspos.empty()) { - auto& quad = shape.quadstexcoord[element]; - return interpolate_quad(shape.texcoords[quad.x], shape.texcoords[quad.y], - shape.texcoords[quad.z], shape.texcoords[quad.w], uv); - } else { - return uv; - } -} - -// Evaluate element normals -vec3f eval_element_normal(const fvshape_data& shape, int element) { - if (!shape.quadspos.empty()) { - auto& quad = shape.quadspos[element]; - return quad_normal(shape.positions[quad.x], shape.positions[quad.y], - shape.positions[quad.z], shape.positions[quad.w]); - } else { - return {0, 0, 0}; - } -} - -// Compute per-vertex normals/tangents for lines/triangles/quads. -vector compute_normals(const fvshape_data& shape) { - if (!shape.quadspos.empty()) { - return quads_normals(shape.quadspos, shape.positions); - } else { - return vector(shape.positions.size(), {0, 0, 1}); - } -} -void compute_normals(vector& normals, const fvshape_data& shape) { - if (!shape.quadspos.empty()) { - quads_normals(normals, shape.quadspos, shape.positions); - } else { - normals.assign(shape.positions.size(), {0, 0, 1}); - } -} - -// Conversions -shape_data fvshape_to_shape(const fvshape_data& fvshape, bool as_triangles) { - auto shape = shape_data{}; - split_facevarying(shape.quads, shape.positions, shape.normals, - shape.texcoords, fvshape.quadspos, fvshape.quadsnorm, - fvshape.quadstexcoord, fvshape.positions, fvshape.normals, - fvshape.texcoords); - return shape; -} -fvshape_data shape_to_fvshape(const shape_data& shape) { - if (!shape.points.empty() || !shape.lines.empty()) - throw std::invalid_argument{"cannor convert shape"}; - auto fvshape = fvshape_data{}; - fvshape.positions = shape.positions; - fvshape.normals = shape.normals; - fvshape.texcoords = shape.texcoords; - fvshape.quadspos = !shape.quads.empty() ? shape.quads - : triangles_to_quads(shape.triangles); - fvshape.quadsnorm = !shape.normals.empty() ? fvshape.quadspos - : vector{}; - fvshape.quadstexcoord = !shape.texcoords.empty() ? fvshape.quadspos - : vector{}; - return fvshape; -} - -// Subdivision -fvshape_data subdivide_fvshape( - const fvshape_data& shape, int subdivisions, bool catmullclark) { - // This should be probably re-implemeneted in a faster fashion. - if (subdivisions == 0) return shape; - auto subdivided = fvshape_data{}; - if (!catmullclark) { - std::tie(subdivided.quadspos, subdivided.positions) = subdivide_quads( - shape.quadspos, shape.positions, subdivisions); - std::tie(subdivided.quadsnorm, subdivided.normals) = subdivide_quads( - shape.quadsnorm, shape.normals, subdivisions); - std::tie(subdivided.quadstexcoord, subdivided.texcoords) = subdivide_quads( - shape.quadstexcoord, shape.texcoords, subdivisions); - } else { - std::tie(subdivided.quadspos, subdivided.positions) = - subdivide_catmullclark(shape.quadspos, shape.positions, subdivisions); - std::tie(subdivided.quadsnorm, subdivided.normals) = subdivide_catmullclark( - shape.quadsnorm, shape.normals, subdivisions); - std::tie(subdivided.quadstexcoord, subdivided.texcoords) = - subdivide_catmullclark( - shape.quadstexcoord, shape.texcoords, subdivisions, true); - } - return subdivided; -} - -vector shape_stats(const shape_data& shape, bool verbose) { - auto format = [](auto num) { - auto str = std::to_string(num); - while (str.size() < 13) str = " " + str; - return str; - }; - auto format3 = [](auto num) { - auto str = std::to_string(num.x) + " " + std::to_string(num.y) + " " + - std::to_string(num.z); - while (str.size() < 13) str = " " + str; - return str; - }; - - auto bbox = invalidb3f; - for (auto& pos : shape.positions) bbox = merge(bbox, pos); - - auto stats = vector{}; - stats.push_back("points: " + format(shape.points.size())); - stats.push_back("lines: " + format(shape.lines.size())); - stats.push_back("triangles: " + format(shape.triangles.size())); - stats.push_back("quads: " + format(shape.quads.size())); - stats.push_back("positions: " + format(shape.positions.size())); - stats.push_back("normals: " + format(shape.normals.size())); - stats.push_back("texcoords: " + format(shape.texcoords.size())); - stats.push_back("colors: " + format(shape.colors.size())); - stats.push_back("radius: " + format(shape.radius.size())); - stats.push_back("center: " + format3(center(bbox))); - stats.push_back("size: " + format3(size(bbox))); - stats.push_back("min: " + format3(bbox.min)); - stats.push_back("max: " + format3(bbox.max)); - - return stats; -} - -vector fvshape_stats(const fvshape_data& shape, bool verbose) { - auto format = [](auto num) { - auto str = std::to_string(num); - while (str.size() < 13) str = " " + str; - return str; - }; - auto format3 = [](auto num) { - auto str = std::to_string(num.x) + " " + std::to_string(num.y) + " " + - std::to_string(num.z); - while (str.size() < 13) str = " " + str; - return str; - }; - - auto bbox = invalidb3f; - for (auto& pos : shape.positions) bbox = merge(bbox, pos); - - auto stats = vector{}; - stats.push_back("fvquads: " + format(shape.quadspos.size())); - stats.push_back("positions: " + format(shape.positions.size())); - stats.push_back("normals: " + format(shape.normals.size())); - stats.push_back("texcoords: " + format(shape.texcoords.size())); - stats.push_back("center: " + format3(center(bbox))); - stats.push_back("size: " + format3(size(bbox))); - stats.push_back("min: " + format3(bbox.min)); - stats.push_back("max: " + format3(bbox.max)); - - return stats; -} - -} // namespace yocto - // ----------------------------------------------------------------------------- // INSTANCE PROPERTIES // ----------------------------------------------------------------------------- @@ -1419,344 +947,6 @@ vector scene_validation(const scene_data& scene, bool notextures) { } // namespace yocto -// ----------------------------------------------------------------------------- -// SHAPE EXAMPLES -// ----------------------------------------------------------------------------- -namespace yocto { - -// Make a plane. -shape_data make_rect( - const vec2i& steps, const vec2f& scale, const vec2f& uvscale) { - auto shape = shape_data{}; - make_rect(shape.quads, shape.positions, shape.normals, shape.texcoords, steps, - scale, uvscale); - return shape; -} -shape_data make_bulged_rect(const vec2i& steps, const vec2f& scale, - const vec2f& uvscale, float radius) { - auto shape = shape_data{}; - make_bulged_rect(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale, radius); - return shape; -} - -// Make a plane in the xz plane. -shape_data make_recty( - const vec2i& steps, const vec2f& scale, const vec2f& uvscale) { - auto shape = shape_data{}; - make_recty(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale); - return shape; -} -shape_data make_bulged_recty(const vec2i& steps, const vec2f& scale, - const vec2f& uvscale, float radius) { - auto shape = shape_data{}; - make_bulged_recty(shape.quads, shape.positions, shape.normals, - shape.texcoords, steps, scale, uvscale, radius); - return shape; -} - -// Make a box. -shape_data make_box( - const vec3i& steps, const vec3f& scale, const vec3f& uvscale) { - auto shape = shape_data{}; - make_box(shape.quads, shape.positions, shape.normals, shape.texcoords, steps, - scale, uvscale); - return shape; -} -shape_data make_rounded_box(const vec3i& steps, const vec3f& scale, - const vec3f& uvscale, float radius) { - auto shape = shape_data{}; - make_rounded_box(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale, radius); - return shape; -} - -// Make a quad stack -shape_data make_rect_stack( - const vec3i& steps, const vec3f& scale, const vec2f& uvscale) { - auto shape = shape_data{}; - make_rect_stack(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale); - return shape; -} - -// Make a floor. -shape_data make_floor( - const vec2i& steps, const vec2f& scale, const vec2f& uvscale) { - auto shape = shape_data{}; - make_floor(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale); - return shape; -} -shape_data make_bent_floor( - const vec2i& steps, const vec2f& scale, const vec2f& uvscale, float bent) { - auto shape = shape_data{}; - make_bent_floor(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale, bent); - return shape; -} - -// Make a sphere. -shape_data make_sphere(int steps, float scale, float uvscale) { - auto shape = shape_data{}; - make_sphere(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale); - return shape; -} - -// Make a sphere. -shape_data make_uvsphere( - const vec2i& steps, float scale, const vec2f& uvscale) { - auto shape = shape_data{}; - make_uvsphere(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale); - return shape; -} - -// Make a sphere. -shape_data make_uvspherey( - const vec2i& steps, float scale, const vec2f& uvscale) { - auto shape = shape_data{}; - make_uvspherey(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale); - return shape; -} - -// Make a sphere with slipped caps. -shape_data make_capped_uvsphere( - const vec2i& steps, float scale, const vec2f& uvscale, float height) { - auto shape = shape_data{}; - make_capped_uvsphere(shape.quads, shape.positions, shape.normals, - shape.texcoords, steps, scale, uvscale, height); - return shape; -} - -// Make a sphere with slipped caps. -shape_data make_capped_uvspherey( - const vec2i& steps, float scale, const vec2f& uvscale, float height) { - auto shape = shape_data{}; - make_capped_uvspherey(shape.quads, shape.positions, shape.normals, - shape.texcoords, steps, scale, uvscale, height); - return shape; -} - -// Make a disk -shape_data make_disk(int steps, float scale, float uvscale) { - auto shape = shape_data{}; - make_disk(shape.quads, shape.positions, shape.normals, shape.texcoords, steps, - scale, uvscale); - return shape; -} - -// Make a bulged disk -shape_data make_bulged_disk( - int steps, float scale, float uvscale, float height) { - auto shape = shape_data{}; - make_bulged_disk(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale, height); - return shape; -} - -// Make a uv disk -shape_data make_uvdisk(const vec2i& steps, float scale, const vec2f& uvscale) { - auto shape = shape_data{}; - make_uvdisk(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale); - return shape; -} - -// Make a uv cylinder -shape_data make_uvcylinder( - const vec3i& steps, const vec2f& scale, const vec3f& uvscale) { - auto shape = shape_data{}; - make_uvcylinder(shape.quads, shape.positions, shape.normals, shape.texcoords, - steps, scale, uvscale); - return shape; -} - -// Make a rounded uv cylinder -shape_data make_rounded_uvcylinder(const vec3i& steps, const vec2f& scale, - const vec3f& uvscale, float radius) { - auto shape = shape_data{}; - make_rounded_uvcylinder(shape.quads, shape.positions, shape.normals, - shape.texcoords, steps, scale, uvscale, radius); - return shape; -} - -// Generate lines set along a quad. Returns lines, pos, norm, texcoord, radius. -shape_data make_lines(const vec2i& steps, const vec2f& scale, - const vec2f& uvscale, const vec2f& rad) { - auto shape = shape_data{}; - make_lines(shape.lines, shape.positions, shape.normals, shape.texcoords, - shape.radius, steps, scale, uvscale, rad); - return shape; -} - -// Make point primitives. Returns points, pos, norm, texcoord, radius. -shape_data make_point(float radius) { - auto shape = shape_data{}; - make_point(shape.points, shape.positions, shape.normals, shape.texcoords, - shape.radius, radius); - return shape; -} - -shape_data make_points(int num, float uvscale, float radius) { - auto shape = shape_data{}; - make_points(shape.points, shape.positions, shape.normals, shape.texcoords, - shape.radius, num, uvscale, radius); - return shape; -} - -shape_data make_points(const vec2i& steps, const vec2f& size, - const vec2f& uvscale, const vec2f& radius) { - auto shape = shape_data{}; - make_points(shape.points, shape.positions, shape.normals, shape.texcoords, - shape.radius, steps, size, uvscale, radius); - return shape; -} - -shape_data make_random_points( - int num, const vec3f& size, float uvscale, float radius, uint64_t seed) { - auto shape = shape_data{}; - make_random_points(shape.points, shape.positions, shape.normals, - shape.texcoords, shape.radius, num, size, uvscale, radius, seed); - return shape; -} - -// Make a facevarying rect -fvshape_data make_fvrect( - const vec2i& steps, const vec2f& scale, const vec2f& uvscale) { - auto shape = fvshape_data{}; - make_fvrect(shape.quadspos, shape.quadsnorm, shape.quadstexcoord, - shape.positions, shape.normals, shape.texcoords, steps, scale, uvscale); - return shape; -} - -// Make a facevarying box -fvshape_data make_fvbox( - const vec3i& steps, const vec3f& scale, const vec3f& uvscale) { - auto shape = fvshape_data{}; - make_fvbox(shape.quadspos, shape.quadsnorm, shape.quadstexcoord, - shape.positions, shape.normals, shape.texcoords, steps, scale, uvscale); - return shape; -} - -// Make a facevarying sphere -fvshape_data make_fvsphere(int steps, float scale, float uvscale) { - auto shape = fvshape_data{}; - make_fvsphere(shape.quadspos, shape.quadsnorm, shape.quadstexcoord, - shape.positions, shape.normals, shape.texcoords, steps, scale, uvscale); - return shape; -} - -// Predefined meshes -shape_data make_monkey(float scale, int subdivisions) { - auto shape = shape_data{}; - make_monkey(shape.quads, shape.positions, scale, subdivisions); - return shape; -} -shape_data make_quad(float scale, int subdivisions) { - auto shape = shape_data{}; - make_quad(shape.quads, shape.positions, shape.normals, shape.texcoords, scale, - subdivisions); - return shape; -} -shape_data make_quady(float scale, int subdivisions) { - auto shape = shape_data{}; - make_quady(shape.quads, shape.positions, shape.normals, shape.texcoords, - scale, subdivisions); - return shape; -} -shape_data make_cube(float scale, int subdivisions) { - auto shape = shape_data{}; - make_cube(shape.quads, shape.positions, shape.normals, shape.texcoords, scale, - subdivisions); - return shape; -} -fvshape_data make_fvcube(float scale, int subdivisions) { - auto shape = fvshape_data{}; - make_fvcube(shape.quadspos, shape.quadsnorm, shape.quadstexcoord, - shape.positions, shape.normals, shape.texcoords, scale, subdivisions); - return shape; -} -shape_data make_geosphere(float scale, int subdivisions) { - auto shape = shape_data{}; - make_geosphere( - shape.triangles, shape.positions, shape.normals, scale, subdivisions); - return shape; -} - -// Make a hair ball around a shape -shape_data make_hair(const shape_data& base, const vec2i& steps, - const vec2f& length, const vec2f& radius, const vec2f& noise, - const vec2f& clump, const vec2f& rotation, int seed) { - auto shape = shape_data{}; - make_hair(shape.lines, shape.positions, shape.normals, shape.texcoords, - shape.radius, base.triangles, base.quads, base.positions, base.normals, - base.texcoords, steps, length, radius, noise, clump, rotation, seed); - return shape; -} - -// Grow hairs around a shape -shape_data make_hair2(const shape_data& base, const vec2i& steps, - const vec2f& length, const vec2f& radius, float noise, float gravity, - int seed) { - auto shape = shape_data{}; - make_hair2(shape.lines, shape.positions, shape.normals, shape.texcoords, - shape.radius, base.triangles, base.quads, base.positions, base.normals, - base.texcoords, steps, length, radius, noise, gravity, seed); - return shape; -} - -// Make a heightfield mesh. -shape_data make_heightfield(const vec2i& size, const vector& height) { - auto shape = shape_data{}; - make_heightfield(shape.quads, shape.positions, shape.normals, shape.texcoords, - size, height); - return shape; -} -shape_data make_heightfield(const vec2i& size, const vector& color) { - auto shape = shape_data{}; - make_heightfield(shape.quads, shape.positions, shape.normals, shape.texcoords, - size, color); - return shape; -} - -// Convert points to small spheres and lines to small cylinders. This is -// intended for making very small primitives for display in interactive -// applications, so the spheres are low res. -shape_data points_to_spheres( - const vector& vertices, int steps, float scale) { - auto shape = shape_data{}; - points_to_spheres(shape.quads, shape.positions, shape.normals, - shape.texcoords, vertices, steps, scale); - return shape; -} -shape_data polyline_to_cylinders( - const vector& vertices, int steps, float scale) { - auto shape = shape_data{}; - polyline_to_cylinders(shape.quads, shape.positions, shape.normals, - shape.texcoords, vertices, steps, scale); - return shape; -} -shape_data lines_to_cylinders( - const vector& vertices, int steps, float scale) { - auto shape = shape_data{}; - lines_to_cylinders(shape.quads, shape.positions, shape.normals, - shape.texcoords, vertices, steps, scale); - return shape; -} -shape_data lines_to_cylinders(const vector& lines, - const vector& positions, int steps, float scale) { - auto shape = shape_data{}; - lines_to_cylinders(shape.quads, shape.positions, shape.normals, - shape.texcoords, lines, positions, steps, scale); - return shape; -} - -} // namespace yocto - // ----------------------------------------------------------------------------- // EXAMPLE SCENES // ----------------------------------------------------------------------------- diff --git a/libs/yocto/yocto_scene.h b/libs/yocto/yocto_scene.h index b415d8108..d0ff0e430 100644 --- a/libs/yocto/yocto_scene.h +++ b/libs/yocto/yocto_scene.h @@ -137,37 +137,6 @@ struct material_data { int normal_tex = invalidid; }; -// Shape data represented as indexed meshes of elements. -// May contain either points, lines, triangles and quads. -struct shape_data { - // element data - vector points = {}; - vector lines = {}; - vector triangles = {}; - vector quads = {}; - - // vertex data - vector positions = {}; - vector normals = {}; - vector texcoords = {}; - vector colors = {}; - vector radius = {}; - vector tangents = {}; -}; - -// Shape data stored as a face-varying mesh -struct fvshape_data { - // element data - vector quadspos = {}; - vector quadsnorm = {}; - vector quadstexcoord = {}; - - // vertex data - vector positions = {}; - vector normals = {}; - vector texcoords = {}; -}; - // Instance. struct instance_data { // instance data @@ -210,21 +179,6 @@ struct subdiv_data { int shape = invalidid; }; -// Scene metadata -struct scene_metadata { - // copyright info preserve in IO - string copyright = ""; - - // element names - vector camera_names = {}; - vector texture_names = {}; - vector material_names = {}; - vector shape_names = {}; - vector instance_names = {}; - vector environment_names = {}; - vector subdiv_names = {}; -}; - // Scene comprised an array of objects whose memory is owened by the scene. // All members are optional,Scene objects (camera, instances, environments) // have transforms defined internally. A scene can optionally contain a @@ -326,75 +280,6 @@ bool is_volumetric(const scene_data& scene, const instance_data& instance); } // namespace yocto -// ----------------------------------------------------------------------------- -// SHAPE PROPERTIES -// ----------------------------------------------------------------------------- -namespace yocto { - -// Interpolate vertex data -vec3f eval_position(const shape_data& shape, int element, const vec2f& uv); -vec3f eval_normal(const shape_data& shape, int element, const vec2f& uv); -vec3f eval_tangent(const shape_data& shape, int element, const vec2f& uv); -vec2f eval_texcoord(const shape_data& shape, int element, const vec2f& uv); -vec4f eval_color(const shape_data& shape, int element, const vec2f& uv); -float eval_radius(const shape_data& shape, int element, const vec2f& uv); - -// Evaluate element normals -vec3f eval_element_normal(const shape_data& shape, int element); - -// Compute per-vertex normals/tangents for lines/triangles/quads. -vector compute_normals(const shape_data& shape); -void compute_normals(vector& normals, const shape_data& shape); - -// An unevaluated location on a shape -struct shape_point { - int element = 0; - vec2f uv = {0, 0}; -}; - -// Shape sampling -vector sample_shape_cdf(const shape_data& shape); -void sample_shape_cdf(vector& cdf, const shape_data& shape); -shape_point sample_shape(const shape_data& shape, const vector& cdf, - float rn, const vec2f& ruv); -vector sample_shape( - const shape_data& shape, int num_samples, uint64_t seed = 98729387); - -// Conversions -shape_data quads_to_triangles(const shape_data& shape); -void quads_to_triangles(shape_data& result, const shape_data& shape); - -// Subdivision -shape_data subdivide_shape( - const shape_data& shape, int subdivisions, bool catmullclark); - -// Interpolate vertex data -vec3f eval_position(const fvshape_data& shape, int element, const vec2f& uv); -vec3f eval_normal(const fvshape_data& shape, int element, const vec2f& uv); -vec2f eval_texcoord(const shape_data& shape, int element, const vec2f& uv); - -// Evaluate element normals -vec3f eval_element_normal(const fvshape_data& shape, int element); - -// Compute per-vertex normals/tangents for lines/triangles/quads. -vector compute_normals(const fvshape_data& shape); -void compute_normals(vector& normals, const fvshape_data& shape); - -// Conversions -shape_data fvshape_to_shape( - const fvshape_data& shape, bool as_triangles = false); -fvshape_data shape_to_fvshape(const shape_data& shape); - -// Subdivision -fvshape_data subdivide_fvshape( - const fvshape_data& shape, int subdivisions, bool catmullclark); - -// Shape statistics -vector shape_stats(const shape_data& shape, bool verbose = false); -vector fvshape_stats(const fvshape_data& shape, bool verbose = false); - -} // namespace yocto - // ----------------------------------------------------------------------------- // INSTANCE PROPERTIES // ----------------------------------------------------------------------------- @@ -478,134 +363,6 @@ void tesselate_subdivs(scene_data& scene); } // namespace yocto -// ----------------------------------------------------------------------------- -// EXAMPLE SHAPES -// ----------------------------------------------------------------------------- -namespace yocto { - -// Make a plane. -shape_data make_rect(const vec2i& steps = {1, 1}, const vec2f& scale = {1, 1}, - const vec2f& uvscale = {1, 1}); -shape_data make_bulged_rect(const vec2i& steps = {1, 1}, - const vec2f& scale = {1, 1}, const vec2f& uvscale = {1, 1}, - float radius = 0.3); -// Make a plane in the xz plane. -shape_data make_recty(const vec2i& steps = {1, 1}, const vec2f& scale = {1, 1}, - const vec2f& uvscale = {1, 1}); -shape_data make_bulged_recty(const vec2i& steps = {1, 1}, - const vec2f& scale = {1, 1}, const vec2f& uvscale = {1, 1}, - float radius = 0.3); -// Make a box. -shape_data make_box(const vec3i& steps = {1, 1, 1}, - const vec3f& scale = {1, 1, 1}, const vec3f& uvscale = {1, 1, 1}); -shape_data make_rounded_box(const vec3i& steps = {1, 1, 1}, - const vec3f& scale = {1, 1, 1}, const vec3f& uvscale = {1, 1, 1}, - float radius = 0.3); -// Make a quad stack -shape_data make_rect_stack(const vec3i& steps = {1, 1, 1}, - const vec3f& scale = {1, 1, 1}, const vec2f& uvscale = {1, 1}); -// Make a floor. -shape_data make_floor(const vec2i& steps = {1, 1}, - const vec2f& scale = {10, 10}, const vec2f& uvscale = {10, 10}); -shape_data make_bent_floor(const vec2i& steps = {1, 1}, - const vec2f& scale = {10, 10}, const vec2f& uvscale = {10, 10}, - float bent = 0.5); -// Make a sphere. -shape_data make_sphere(int steps = 32, float scale = 1, float uvscale = 1); -// Make a sphere. -shape_data make_uvsphere(const vec2i& steps = {32, 32}, float scale = 1, - const vec2f& uvscale = {1, 1}); -shape_data make_uvspherey(const vec2i& steps = {32, 32}, float scale = 1, - const vec2f& uvscale = {1, 1}); -// Make a sphere with slipped caps. -shape_data make_capped_uvsphere(const vec2i& steps = {32, 32}, float scale = 1, - const vec2f& uvscale = {1, 1}, float height = 0.3); -shape_data make_capped_uvspherey(const vec2i& steps = {32, 32}, float scale = 1, - const vec2f& uvscale = {1, 1}, float height = 0.3); -// Make a disk -shape_data make_disk(int steps = 32, float scale = 1, float uvscale = 1); -// Make a bulged disk -shape_data make_bulged_disk( - int steps = 32, float scale = 1, float uvscale = 1, float height = 0.3); -// Make a uv disk -shape_data make_uvdisk(const vec2i& steps = {32, 32}, float scale = 1, - const vec2f& uvscale = {1, 1}); -// Make a uv cylinder -shape_data make_uvcylinder(const vec3i& steps = {32, 32, 32}, - const vec2f& scale = {1, 1}, const vec3f& uvscale = {1, 1, 1}); -// Make a rounded uv cylinder -shape_data make_rounded_uvcylinder(const vec3i& steps = {32, 32, 32}, - const vec2f& scale = {1, 1}, const vec3f& uvscale = {1, 1, 1}, - float radius = 0.3); - -// Make a facevarying rect -fvshape_data make_fvrect(const vec2i& steps = {1, 1}, - const vec2f& scale = {1, 1}, const vec2f& uvscale = {1, 1}); -// Make a facevarying box -fvshape_data make_fvbox(const vec3i& steps = {1, 1, 1}, - const vec3f& scale = {1, 1, 1}, const vec3f& uvscale = {1, 1, 1}); -// Make a facevarying sphere -fvshape_data make_fvsphere(int steps = 32, float scale = 1, float uvscale = 1); - -// Generate lines set along a quad. Returns lines, pos, norm, texcoord, radius. -shape_data make_lines(const vec2i& steps = {4, 65536}, - const vec2f& scale = {1, 1}, const vec2f& uvscale = {1, 1}, - const vec2f& radius = {0.001f, 0.001f}); - -// Make a point primitive. Returns points, pos, norm, texcoord, radius. -shape_data make_point(float radius = 0.001f); -// Make a point set on a grid. Returns points, pos, norm, texcoord, radius. -shape_data make_points( - int num = 65536, float uvscale = 1, float radius = 0.001f); -shape_data make_points(const vec2i& steps = {256, 256}, - const vec2f& size = {1, 1}, const vec2f& uvscale = {1, 1}, - const vec2f& radius = {0.001f, 0.001f}); -// Make random points in a cube. Returns points, pos, norm, texcoord, radius. -shape_data make_random_points(int num = 65536, const vec3f& size = {1, 1, 1}, - float uvscale = 1, float radius = 0.001f, uint64_t seed = 17); - -// Predefined meshes -shape_data make_monkey(float scale = 1, int subdivisions = 0); -shape_data make_quad(float scale = 1, int subdivisions = 0); -shape_data make_quady(float scale = 1, int subdivisions = 0); -shape_data make_cube(float scale = 1, int subdivisions = 0); -fvshape_data make_fvcube(float scale = 1, int subdivisions = 0); -shape_data make_geosphere(float scale = 1, int subdivisions = 0); - -// Make a hair ball around a shape. -// length: minimum and maximum length -// rad: minimum and maximum radius from base to tip -// noise: noise added to hair (strength/scale) -// clump: clump added to hair (strength/number) -// rotation: rotation added to hair (angle/strength) -shape_data make_hair(const shape_data& shape, const vec2i& steps = {8, 65536}, - const vec2f& length = {0.1f, 0.1f}, const vec2f& radius = {0.001f, 0.001f}, - const vec2f& noise = {0, 10}, const vec2f& clump = {0, 128}, - const vec2f& rotation = {0, 0}, int seed = 7); - -// Grow hairs around a shape -shape_data make_hair2(const shape_data& shape, const vec2i& steps = {8, 65536}, - const vec2f& length = {0.1f, 0.1f}, const vec2f& radius = {0.001f, 0.001f}, - float noise = 0, float gravity = 0.001f, int seed = 7); - -// Convert points to small spheres and lines to small cylinders. This is -// intended for making very small primitives for display in interactive -// applications, so the spheres are low res. -shape_data points_to_spheres( - const vector& vertices, int steps = 2, float scale = 0.01f); -shape_data polyline_to_cylinders( - const vector& vertices, int steps = 4, float scale = 0.01f); -shape_data lines_to_cylinders( - const vector& vertices, int steps = 4, float scale = 0.01f); -shape_data lines_to_cylinders(const vector& lines, - const vector& positions, int steps = 4, float scale = 0.01f); - -// Make a heightfield mesh. -shape_data make_heightfield(const vec2i& size, const vector& height); -shape_data make_heightfield(const vec2i& size, const vector& color); - -} // namespace yocto - // ----------------------------------------------------------------------------- // EXAMPLE SCENES // ----------------------------------------------------------------------------- diff --git a/libs/yocto/yocto_sceneio.cpp b/libs/yocto/yocto_sceneio.cpp index 393e0838e..e86443a52 100644 --- a/libs/yocto/yocto_sceneio.cpp +++ b/libs/yocto/yocto_sceneio.cpp @@ -63,7 +63,7 @@ using namespace std::string_literals; } // namespace yocto // ----------------------------------------------------------------------------- -// IMAGE IO +// TEXTURE IO // ----------------------------------------------------------------------------- namespace yocto { @@ -423,358 +423,6 @@ static bool save_pfm_to_func( return true; } -// Check if an image is HDR based on filename. -bool is_hdr_filename(const string& filename) { - auto ext = path_extension(filename); - return ext == ".hdr" || ext == ".exr" || ext == ".pfm"; -} - -bool is_ldr_filename(const string& filename) { - auto ext = path_extension(filename); - return ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".bmp" || - ext == ".tga"; -} - -// Loads/saves an image. Chooses hdr or ldr based on file name. -image_data load_image(const string& filename) { - auto image = image_data{}; - load_image(filename, image); - return image; -} -void load_image(const string& filename, image_data& image) { - // conversion helpers - auto from_linear = [](const float* pixels, int width, int height) { - return vector{ - (vec4f*)pixels, (vec4f*)pixels + (size_t)width * (size_t)height}; - }; - auto from_srgb = [](const byte* pixels, int width, int height) { - auto pixelsf = vector((size_t)width * (size_t)height); - for (auto idx = (size_t)0; idx < pixelsf.size(); idx++) { - pixelsf[idx] = byte_to_float(((vec4b*)pixels)[idx]); - } - return pixelsf; - }; - - auto ext = path_extension(filename); - if (ext == ".exr" || ext == ".EXR") { - auto buffer = load_binary(filename); - auto pixels = (float*)nullptr; - if (LoadEXRFromMemory(&pixels, &image.width, &image.height, buffer.data(), - buffer.size(), nullptr) != 0) - throw io_error::read_error(filename); - image.linear = true; - image.pixels = from_linear(pixels, image.width, image.height); - free(pixels); - } else if (ext == ".pfm" || ext == ".PFM") { - auto buffer = load_binary(filename); - auto ncomp = 0; - auto pixels = load_pfm_from_memory(buffer.data(), (int)buffer.size(), - &image.width, &image.height, &ncomp, 4); - if (!pixels) throw io_error::read_error(filename); - image.linear = true; - image.pixels = from_linear(pixels, image.width, image.height); - free(pixels); - } else if (ext == ".hdr" || ext == ".HDR") { - auto buffer = load_binary(filename); - auto ncomp = 0; - auto pixels = stbi_loadf_from_memory(buffer.data(), (int)buffer.size(), - &image.width, &image.height, &ncomp, 4); - if (!pixels) throw io_error::read_error(filename); - image.linear = true; - image.pixels = from_linear(pixels, image.width, image.height); - free(pixels); - } else if (ext == ".png" || ext == ".PNG") { - auto buffer = load_binary(filename); - auto ncomp = 0; - auto pixels = stbi_load_from_memory(buffer.data(), (int)buffer.size(), - &image.width, &image.height, &ncomp, 4); - if (!pixels) throw io_error::read_error(filename); - image.linear = false; - image.pixels = from_srgb(pixels, image.width, image.height); - free(pixels); - } else if (ext == ".jpg" || ext == ".JPG" || ext == ".jpeg" || - ext == ".JPEG") { - auto buffer = load_binary(filename); - auto ncomp = 0; - auto pixels = stbi_load_from_memory(buffer.data(), (int)buffer.size(), - &image.width, &image.height, &ncomp, 4); - if (!pixels) throw io_error::read_error(filename); - image.linear = false; - image.pixels = from_srgb(pixels, image.width, image.height); - free(pixels); - } else if (ext == ".tga" || ext == ".TGA") { - auto buffer = load_binary(filename); - auto ncomp = 0; - auto pixels = stbi_load_from_memory(buffer.data(), (int)buffer.size(), - &image.width, &image.height, &ncomp, 4); - if (!pixels) throw io_error::read_error(filename); - image.linear = false; - image.pixels = from_srgb(pixels, image.width, image.height); - free(pixels); - } else if (ext == ".bmp" || ext == ".BMP") { - auto buffer = load_binary(filename); - auto ncomp = 0; - auto pixels = stbi_load_from_memory(buffer.data(), (int)buffer.size(), - &image.width, &image.height, &ncomp, 4); - if (!pixels) throw io_error::read_error(filename); - image.linear = false; - image.pixels = from_srgb(pixels, image.width, image.height); - free(pixels); - } else if (ext == ".ypreset" || ext == ".YPRESET") { - // create preset - image = make_image_preset(path_basename(filename)); - } else { - throw io_error::format_error(filename); - } -} - -// Saves an hdr image. -void save_image(const string& filename, const image_data& image) { - // conversion helpers - auto to_linear = [](const image_data& image) { - if (image.linear) return image.pixels; - auto pixelsf = vector(image.pixels.size()); - srgb_to_rgb(pixelsf, image.pixels); - return pixelsf; - }; - auto to_srgb = [](const image_data& image) { - auto pixelsb = vector(image.pixels.size()); - if (image.linear) { - rgb_to_srgb(pixelsb, image.pixels); - } else { - float_to_byte(pixelsb, image.pixels); - } - return pixelsb; - }; - - // write data - auto stbi_write_data = [](void* context, void* data, int size) { - auto& buffer = *(vector*)context; - buffer.insert(buffer.end(), (byte*)data, (byte*)data + size); - }; - auto pfm_write_data = [](void* context, const void* data, int size) { - auto& buffer = *(vector*)context; - buffer.insert(buffer.end(), (byte*)data, (byte*)data + size); - }; - - auto ext = path_extension(filename); - if (ext == ".hdr" || ext == ".HDR") { - auto buffer = vector{}; - if (!stbi_write_hdr_to_func(stbi_write_data, &buffer, (int)image.width, - (int)image.height, 4, (const float*)to_linear(image).data())) - throw io_error::write_error(filename); - save_binary(filename, buffer); - } else if (ext == ".pfm" || ext == ".PFM") { - auto buffer = vector{}; - if (!save_pfm_to_func(pfm_write_data, &buffer, image.width, image.height, 4, - (const float*)to_linear(image).data())) - throw io_error::write_error(filename); - save_binary(filename, buffer); - } else if (ext == ".exr" || ext == ".EXR") { - auto data = (byte*)nullptr; - auto size = (size_t)0; - if (SaveEXRToMemory((const float*)to_linear(image).data(), (int)image.width, - (int)image.height, 4, 1, &data, &size, nullptr) < 0) - throw io_error::write_error(filename); - auto buffer = vector{data, data + size}; - free(data); - save_binary(filename, buffer); - } else if (ext == ".png" || ext == ".PNG") { - auto buffer = vector{}; - if (!stbi_write_png_to_func(stbi_write_data, &buffer, (int)image.width, - (int)image.height, 4, (const byte*)to_srgb(image).data(), - (int)image.width * 4)) - throw io_error::write_error(filename); - save_binary(filename, buffer); - } else if (ext == ".jpg" || ext == ".JPG" || ext == ".jpeg" || - ext == ".JPEG") { - auto buffer = vector{}; - if (!stbi_write_jpg_to_func(stbi_write_data, &buffer, (int)image.width, - (int)image.height, 4, (const byte*)to_srgb(image).data(), 75)) - throw io_error::write_error(filename); - save_binary(filename, buffer); - } else if (ext == ".tga" || ext == ".TGA") { - auto buffer = vector{}; - if (!stbi_write_tga_to_func(stbi_write_data, &buffer, (int)image.width, - (int)image.height, 4, (const byte*)to_srgb(image).data())) - throw io_error::write_error(filename); - save_binary(filename, buffer); - } else if (ext == ".bmp" || ext == ".BMP") { - auto buffer = vector{}; - if (!stbi_write_bmp_to_func(stbi_write_data, &buffer, (int)image.width, - (int)image.height, 4, (const byte*)to_srgb(image).data())) - throw io_error::write_error(filename); - save_binary(filename, buffer); - } else { - throw io_error::format_error(filename); - } -} - -image_data make_image_preset(const string& type_) { - auto type = path_basename(type_); - auto width = 1024, height = 1024; - if (type.find("sky") != type.npos) width = 2048; - if (type.find("images2") != type.npos) width = 2048; - if (type == "grid") { - return make_grid(width, height); - } else if (type == "checker") { - return make_checker(width, height); - } else if (type == "bumps") { - return make_bumps(width, height); - } else if (type == "uvramp") { - return make_uvramp(width, height); - } else if (type == "gammaramp") { - return make_gammaramp(width, height); - } else if (type == "blackbodyramp") { - return make_blackbodyramp(width, height); - } else if (type == "uvgrid") { - return make_uvgrid(width, height); - } else if (type == "colormapramp") { - return make_colormapramp(width, height); - } else if (type == "sky") { - return make_sunsky(width, height, pif / 4, 3.0f, false, 1.0f, 1.0f, - vec3f{0.7f, 0.7f, 0.7f}); - } else if (type == "sunsky") { - return make_sunsky(width, height, pif / 4, 3.0f, true, 1.0f, 1.0f, - vec3f{0.7f, 0.7f, 0.7f}); - } else if (type == "noise") { - return make_noisemap(width, height, 1); - } else if (type == "fbm") { - return make_fbmmap(width, height, 1); - } else if (type == "ridge") { - return make_ridgemap(width, height, 1); - } else if (type == "turbulence") { - return make_turbulencemap(width, height, 1); - } else if (type == "bump-normal") { - return make_bumps(width, height); - // TODO(fabio): fix color space - // img = srgb_to_rgb(bump_to_normal(img, 0.05f)); - } else if (type == "images1") { - auto sub_types = vector{"grid", "uvgrid", "checker", "gammaramp", - "bumps", "bump-normal", "noise", "fbm", "blackbodyramp"}; - auto sub_images = vector(); - for (auto& sub_type : sub_types) - sub_images.push_back(make_image_preset(sub_type)); - auto montage_size = zero2i; - for (auto& sub_image : sub_images) { - montage_size.x += sub_image.width; - montage_size.y = max(montage_size.y, sub_image.height); - } - auto image = make_image( - montage_size.x, montage_size.y, sub_images[0].linear); - auto pos = 0; - for (auto& sub_image : sub_images) { - set_region(image, sub_image, pos, 0); - pos += sub_image.width; - } - return image; - } else if (type == "images2") { - auto sub_types = vector{"sky", "sunsky"}; - auto sub_images = vector(); - for (auto& sub_type : sub_types) - sub_images.push_back(make_image_preset(sub_type)); - auto montage_size = zero2i; - for (auto& sub_image : sub_images) { - montage_size.x += sub_image.width; - montage_size.y = max(montage_size.y, sub_image.height); - } - auto image = make_image( - montage_size.x, montage_size.y, sub_images[0].linear); - auto pos = 0; - for (auto& sub_image : sub_images) { - set_region(image, sub_image, pos, 0); - pos += sub_image.width; - } - return image; - } else if (type == "test-floor") { - return add_border(make_grid(width, height), 0.0025f); - } else if (type == "test-grid") { - return make_grid(width, height); - } else if (type == "test-checker") { - return make_checker(width, height); - } else if (type == "test-bumps") { - return make_bumps(width, height); - } else if (type == "test-uvramp") { - return make_uvramp(width, height); - } else if (type == "test-gammaramp") { - return make_gammaramp(width, height); - } else if (type == "test-blackbodyramp") { - return make_blackbodyramp(width, height); - } else if (type == "test-colormapramp") { - return make_colormapramp(width, height); - // TODO(fabio): fix color space - // img = srgb_to_rgb(img); - } else if (type == "test-uvgrid") { - return make_uvgrid(width, height); - } else if (type == "test-sky") { - return make_sunsky(width, height, pif / 4, 3.0f, false, 1.0f, 1.0f, - vec3f{0.7f, 0.7f, 0.7f}); - } else if (type == "test-sunsky") { - return make_sunsky(width, height, pif / 4, 3.0f, true, 1.0f, 1.0f, - vec3f{0.7f, 0.7f, 0.7f}); - } else if (type == "test-noise") { - return make_noisemap(width, height); - } else if (type == "test-fbm") { - return make_noisemap(width, height); - } else if (type == "test-bumps-normal") { - return bump_to_normal(make_bumps(width, height), 0.05f); - } else if (type == "test-bumps-displacement") { - return make_bumps(width, height); - // TODO(fabio): fix color space - // img = srgb_to_rgb(img); - } else if (type == "test-fbm-displacement") { - return make_fbmmap(width, height); - // TODO(fabio): fix color space - // img = srgb_to_rgb(img); - } else if (type == "test-checker-opacity") { - return make_checker(width, height, 1, {1, 1, 1, 1}, {0, 0, 0, 0}); - } else if (type == "test-grid-opacity") { - return make_grid(width, height, 1, {1, 1, 1, 1}, {0, 0, 0, 0}); - } else { - throw io_error::preset_error(type_); - } -} - -// Loads/saves an image. Chooses hdr or ldr based on file name. -bool load_image(const string& filename, image_data& image, string& error) { - try { - load_image(filename, image); - return true; - } catch (const io_error& exception) { - error = exception.what(); - return false; - } -} - -// Saves an hdr image. -bool save_image( - const string& filename, const image_data& image, string& error) { - try { - save_image(filename, image); - return true; - } catch (const io_error& exception) { - error = exception.what(); - return false; - } -} - -bool make_image_preset(image_data& image, const string& type, string& error) { - try { - image = make_image_preset(type); - return true; - } catch (const io_error& exception) { - error = exception.what(); - return false; - } -} - -} // namespace yocto - -// ----------------------------------------------------------------------------- -// TEXTURE IO -// ----------------------------------------------------------------------------- -namespace yocto { - // Loads/saves an image. Chooses hdr or ldr based on file name. texture_data load_texture(const string& filename) { auto texture = texture_data{}; @@ -974,736 +622,6 @@ bool make_texture_preset( } // namespace yocto -// ----------------------------------------------------------------------------- -// SHAPE IO -// ----------------------------------------------------------------------------- -namespace yocto { - -// Load ply mesh -shape_data load_shape(const string& filename, bool flip_texcoord) { - auto shape = shape_data{}; - load_shape(filename, shape, flip_texcoord); - return shape; -} -void load_shape(const string& filename, shape_data& shape, bool flip_texcoord) { - shape = {}; - - auto ext = path_extension(filename); - if (ext == ".ply" || ext == ".PLY") { - auto ply = ply_model{}; - load_ply(filename, ply); - get_positions(ply, shape.positions); - get_normals(ply, shape.normals); - get_texcoords(ply, shape.texcoords, flip_texcoord); - get_colors(ply, shape.colors); - get_radius(ply, shape.radius); - get_faces(ply, shape.triangles, shape.quads); - get_lines(ply, shape.lines); - get_points(ply, shape.points); - if (shape.points.empty() && shape.lines.empty() && - shape.triangles.empty() && shape.quads.empty()) - throw io_error::shape_error(filename); - } else if (ext == ".obj" || ext == ".OBJ") { - auto obj = obj_shape{}; - load_obj(filename, obj, false); - auto materials = vector{}; - get_positions(obj, shape.positions); - get_normals(obj, shape.normals); - get_texcoords(obj, shape.texcoords, flip_texcoord); - get_faces(obj, shape.triangles, shape.quads, materials); - get_lines(obj, shape.lines, materials); - get_points(obj, shape.points, materials); - if (shape.points.empty() && shape.lines.empty() && - shape.triangles.empty() && shape.quads.empty()) - throw io_error::shape_error(filename); - } else if (ext == ".stl" || ext == ".STL") { - auto stl = stl_model{}; - load_stl(filename, stl, true); - if (stl.shapes.size() != 1) throw io_error::shape_error(filename); - auto fnormals = vector{}; - if (!get_triangles(stl, 0, shape.triangles, shape.positions, fnormals)) - throw io_error::shape_error(filename); - } else if (ext == ".ypreset" || ext == ".YPRESET") { - shape = make_shape_preset(path_basename(filename)); - } else { - throw io_error::format_error(filename); - } -} - -// Save ply mesh -void save_shape(const string& filename, const shape_data& shape, - bool flip_texcoord, bool ascii) { - auto ext = path_extension(filename); - if (ext == ".ply" || ext == ".PLY") { - auto ply = ply_model{}; - add_positions(ply, shape.positions); - add_normals(ply, shape.normals); - add_texcoords(ply, shape.texcoords, flip_texcoord); - add_colors(ply, shape.colors); - add_radius(ply, shape.radius); - add_faces(ply, shape.triangles, shape.quads); - add_lines(ply, shape.lines); - add_points(ply, shape.points); - save_ply(filename, ply); - } else if (ext == ".obj" || ext == ".OBJ") { - auto obj = obj_shape{}; - add_positions(obj, shape.positions); - add_normals(obj, shape.normals); - add_texcoords(obj, shape.texcoords, flip_texcoord); - add_triangles(obj, shape.triangles, 0, !shape.normals.empty(), - !shape.texcoords.empty()); - add_quads( - obj, shape.quads, 0, !shape.normals.empty(), !shape.texcoords.empty()); - add_lines( - obj, shape.lines, 0, !shape.normals.empty(), !shape.texcoords.empty()); - add_points( - obj, shape.points, 0, !shape.normals.empty(), !shape.texcoords.empty()); - save_obj(filename, obj); - } else if (ext == ".stl" || ext == ".STL") { - auto stl = stl_model{}; - if (!shape.lines.empty()) throw io_error{filename, "lines not supported"}; - if (!shape.points.empty()) throw io_error{filename, "points not supported"}; - if (!shape.triangles.empty()) { - add_triangles(stl, shape.triangles, shape.positions, {}); - } else if (!shape.quads.empty()) { - add_triangles(stl, quads_to_triangles(shape.quads), shape.positions, {}); - } else { - throw io_error::shape_error(filename); - } - save_stl(filename, stl); - } else if (ext == ".cpp" || ext == ".CPP") { - auto to_cpp = [](const string& name, const string& vname, - const auto& values) -> string { - using T = typename std::remove_const_t< - std::remove_reference_t>::value_type; - if (values.empty()) return ""s; - auto str = "auto " + name + "_" + vname + " = "; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - for (auto& value : values) { - if constexpr (std::is_same_v || std::is_same_v) { - str += std::to_string(value) + ",\n"; - } else if constexpr (std::is_same_v || - std::is_same_v) { - str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + - "},\n"; - } else if constexpr (std::is_same_v || - std::is_same_v) { - str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + - "," + std::to_string(value.z) + "},\n"; - } else if constexpr (std::is_same_v || - std::is_same_v) { - str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + - "," + std::to_string(value.z) + "," + std::to_string(value.w) + - "},\n"; - } else { - throw std::invalid_argument{"cannot print this"}; - } - } - str += "};\n\n"; - return str; - }; - - auto name = string{"shape"}; - auto str = ""s; - str += to_cpp(name, "positions", shape.positions); - str += to_cpp(name, "normals", shape.normals); - str += to_cpp(name, "texcoords", shape.texcoords); - str += to_cpp(name, "colors", shape.colors); - str += to_cpp(name, "radius", shape.radius); - str += to_cpp(name, "points", shape.points); - str += to_cpp(name, "lines", shape.lines); - str += to_cpp(name, "triangles", shape.triangles); - str += to_cpp(name, "quads", shape.quads); - save_text(filename, str); - } else { - throw io_error::format_error(filename); - } -} - -// Load face-varying mesh -fvshape_data load_fvshape(const string& filename, bool flip_texcoord) { - auto shape = fvshape_data{}; - load_fvshape(filename, shape, flip_texcoord); - return shape; -} -void load_fvshape( - const string& filename, fvshape_data& shape, bool flip_texcoord) { - shape = {}; - - auto ext = path_extension(filename); - if (ext == ".ply" || ext == ".PLY") { - auto ply = ply_model{}; - load_ply(filename, ply); - get_positions(ply, shape.positions); - get_normals(ply, shape.normals); - get_texcoords(ply, shape.texcoords, flip_texcoord); - get_quads(ply, shape.quadspos); - if (!shape.normals.empty()) shape.quadsnorm = shape.quadspos; - if (!shape.texcoords.empty()) shape.quadstexcoord = shape.quadspos; - if (shape.quadspos.empty()) throw io_error::shape_error(filename); - } else if (ext == ".obj" || ext == ".OBJ") { - auto obj = obj_shape{}; - load_obj(filename, obj, true); - auto materials = vector{}; - get_positions(obj, shape.positions); - get_normals(obj, shape.normals); - get_texcoords(obj, shape.texcoords, flip_texcoord); - get_fvquads( - obj, shape.quadspos, shape.quadsnorm, shape.quadstexcoord, materials); - if (shape.quadspos.empty()) throw io_error::shape_error(filename); - } else if (ext == ".stl" || ext == ".STL") { - auto stl = stl_model{}; - load_stl(filename, stl, true); - if (stl.shapes.empty()) throw io_error::shape_error(filename); - if (stl.shapes.size() > 1) throw io_error::shape_error(filename); - auto fnormals = vector{}; - auto triangles = vector{}; - if (!get_triangles(stl, 0, triangles, shape.positions, fnormals)) - throw io_error::shape_error(filename); - shape.quadspos = triangles_to_quads(triangles); - } else if (ext == ".ypreset" || ext == ".YPRESET") { - shape = make_fvshape_preset(path_basename(filename)); - } else { - throw io_error::format_error(filename); - } -} - -// Save ply mesh -void save_fvshape(const string& filename, const fvshape_data& shape, - bool flip_texcoord, bool ascii) { - auto ext = path_extension(filename); - if (ext == ".ply" || ext == ".PLY") { - auto ply = ply_model{}; - auto split_quads = vector{}; - auto split_positions = vector{}; - auto split_normals = vector{}; - auto split_texcoords = vector{}; - split_facevarying(split_quads, split_positions, split_normals, - split_texcoords, shape.quadspos, shape.quadsnorm, shape.quadstexcoord, - shape.positions, shape.normals, shape.texcoords); - add_positions(ply, split_positions); - add_normals(ply, split_normals); - add_texcoords(ply, split_texcoords, flip_texcoord); - add_faces(ply, {}, split_quads); - save_ply(filename, ply); - } else if (ext == ".obj" || ext == ".OBJ") { - auto obj = obj_shape{}; - add_positions(obj, shape.positions); - add_normals(obj, shape.positions); - add_texcoords(obj, shape.texcoords, flip_texcoord); - add_fvquads(obj, shape.quadspos, shape.quadsnorm, shape.quadstexcoord, 0); - save_obj(filename, obj); - } else if (ext == ".stl" || ext == ".STL") { - auto stl = stl_model{}; - if (!shape.quadspos.empty()) { - auto split_quads = vector{}; - auto split_positions = vector{}; - auto split_normals = vector{}; - auto split_texcoords = vector{}; - split_facevarying(split_quads, split_positions, split_normals, - split_texcoords, shape.quadspos, shape.quadsnorm, shape.quadstexcoord, - shape.positions, shape.normals, shape.texcoords); - add_triangles(stl, quads_to_triangles(split_quads), split_positions, {}); - } else { - throw io_error::shape_error(filename); - } - save_stl(filename, stl); - } else if (ext == ".cpp" || ext == ".CPP") { - auto to_cpp = [](const string& name, const string& vname, - const auto& values) -> string { - using T = typename std::remove_const_t< - std::remove_reference_t>::value_type; - if (values.empty()) return ""s; - auto str = "auto " + name + "_" + vname + " = "; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - if constexpr (std::is_same_v) str += "vector{\n"; - for (auto& value : values) { - if constexpr (std::is_same_v || std::is_same_v) { - str += std::to_string(value) + ",\n"; - } else if constexpr (std::is_same_v || - std::is_same_v) { - str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + - "},\n"; - } else if constexpr (std::is_same_v || - std::is_same_v) { - str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + - "," + std::to_string(value.z) + "},\n"; - } else if constexpr (std::is_same_v || - std::is_same_v) { - str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + - "," + std::to_string(value.z) + "," + std::to_string(value.w) + - "},\n"; - } else { - throw std::invalid_argument{"cannot print this"}; - } - } - str += "};\n\n"; - return str; - }; - auto name = string{"shape"}; - auto str = ""s; - str += to_cpp(name, "positions", shape.positions); - str += to_cpp(name, "normals", shape.normals); - str += to_cpp(name, "texcoords", shape.texcoords); - str += to_cpp(name, "quadspos", shape.quadspos); - str += to_cpp(name, "quadsnorm", shape.quadsnorm); - str += to_cpp(name, "quadstexcoord", shape.quadstexcoord); - save_text(filename, str); - } else { - throw io_error::format_error(filename); - } -} - -// Shape presets used for testing. -shape_data make_shape_preset(const string& type) { - if (type == "default-quad") { - return make_rect(); - } else if (type == "default-quady") { - return make_recty(); - } else if (type == "default-cube") { - return make_box(); - } else if (type == "default-cube-rounded") { - return make_rounded_box(); - } else if (type == "default-sphere") { - return make_sphere(); - } else if (type == "default-matcube") { - return make_rounded_box(); - } else if (type == "default-matsphere") { - return make_uvspherey(); - } else if (type == "default-disk") { - return make_disk(); - } else if (type == "default-disk-bulged") { - return make_bulged_disk(); - } else if (type == "default-quad-bulged") { - return make_bulged_rect(); - } else if (type == "default-uvsphere") { - return make_uvsphere(); - } else if (type == "default-uvsphere-flipcap") { - return make_capped_uvsphere(); - } else if (type == "default-uvspherey") { - return make_uvspherey(); - } else if (type == "default-uvspherey-flipcap") { - return make_capped_uvspherey(); - } else if (type == "default-uvdisk") { - return make_uvdisk(); - } else if (type == "default-uvcylinder") { - return make_uvcylinder(); - } else if (type == "default-uvcylinder-rounded") { - return make_rounded_uvcylinder({32, 32, 32}); - } else if (type == "default-geosphere") { - return make_geosphere(); - } else if (type == "default-floor") { - return make_floor(); - } else if (type == "default-floor-bent") { - return make_bent_floor(); - } else if (type == "default-matball") { - return make_sphere(); - } else if (type == "default-hairball") { - auto base = make_sphere(pow2(5), 0.8f); - return make_hair(base, {4, 65536}, {0.2f, 0.2f}, {0.002f, 0.001f}); - } else if (type == "default-hairball-interior") { - return make_sphere(pow2(5), 0.8f); - } else if (type == "default-suzanne") { - return make_monkey(); - } else if (type == "default-cube-facevarying") { - return fvshape_to_shape(make_fvbox()); - } else if (type == "default-sphere-facevarying") { - return fvshape_to_shape(make_fvsphere()); - } else if (type == "default-quady-displaced") { - return make_recty({256, 256}); - } else if (type == "default-sphere-displaced") { - return make_sphere(128); - } else if (type == "test-cube") { - auto shape = make_rounded_box( - {32, 32, 32}, {0.075f, 0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-uvsphere") { - auto shape = make_uvsphere({32, 32}, 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-uvsphere-flipcap") { - auto shape = make_capped_uvsphere({32, 32}, 0.075f, {1, 1}, 0.3f * 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-uvspherey") { - auto shape = make_uvspherey({32, 32}, 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-uvspherey-flipcap") { - auto shape = make_capped_uvspherey({32, 32}, 0.075f, {1, 1}, 0.3f * 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-sphere") { - auto shape = make_sphere(32, 0.075f, 1); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-matcube") { - auto shape = make_rounded_box( - {32, 32, 32}, {0.075f, 0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-matsphere") { - auto shape = make_uvspherey({32, 32}, 0.075f, {2, 1}); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-sphere-displaced") { - auto shape = make_sphere(128, 0.075f, 1); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-smallsphere") { - auto shape = make_sphere(32, 0.015f, 1); - for (auto& p : shape.positions) p += {0, 0.015f, 0}; - return shape; - } else if (type == "test-disk") { - auto shape = make_disk(32, 0.075f, 1); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-uvcylinder") { - auto shape = make_rounded_uvcylinder( - {32, 32, 32}, {0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-floor") { - return make_floor({1, 1}, {2, 2}, {20, 20}); - } else if (type == "test-smallfloor") { - return make_floor({1, 1}, {0.5f, 0.5f}, {1, 1}); - } else if (type == "test-quad") { - return make_rect({1, 1}, {0.075f, 0.075f}, {1, 1}); - } else if (type == "test-quady") { - return make_recty({1, 1}, {0.075f, 0.075f}, {1, 1}); - } else if (type == "test-quad-displaced") { - return make_rect({256, 256}, {0.075f, 0.075f}, {1, 1}); - } else if (type == "test-quady-displaced") { - return make_recty({256, 256}, {0.075f, 0.075f}, {1, 1}); - } else if (type == "test-matball") { - auto shape = make_sphere(32, 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-geosphere") { - auto shape = make_geosphere(0.075f, 3); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-geosphere-flat") { - auto shape = make_geosphere(0.075f, 3); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - shape.normals = {}; - return shape; - } else if (type == "test-geosphere-subdivided") { - auto shape = make_geosphere(0.075f, 6); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-hairball1") { - auto base = make_sphere(32, 0.075f * 0.8f, 1); - for (auto& p : base.positions) p += {0, 0.075f, 0}; - return make_hair(base, {4, 65536}, {0.1f * 0.15f, 0.1f * 0.15f}, - {0.001f * 0.15f, 0.0005f * 0.15f}, {0.03f, 100}); - } else if (type == "test-hairball2") { - auto base = make_sphere(32, 0.075f * 0.8f, 1); - for (auto& p : base.positions) p += {0, 0.075f, 0}; - return make_hair(base, {4, 65536}, {0.1f * 0.15f, 0.1f * 0.15f}, - {0.001f * 0.15f, 0.0005f * 0.15f}); - } else if (type == "test-hairball3") { - auto base = make_sphere(32, 0.075f * 0.8f, 1); - for (auto& p : base.positions) p += {0, 0.075f, 0}; - return make_hair(base, {4, 65536}, {0.1f * 0.15f, 0.1f * 0.15f}, - {0.001f * 0.15f, 0.0005f * 0.15f}, {0, 0}, {0.5, 128}); - } else if (type == "test-hairball-interior") { - auto shape = make_sphere(32, 0.075f * 0.8f, 1); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-suzanne-subdiv") { - auto shape = make_monkey(0.075f * 0.8f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-cube-subdiv") { - auto fvshape = make_fvcube(0.075f); - auto shape = shape_data{}; - shape.quads = fvshape.quadspos; - shape.positions = fvshape.positions; - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-arealight1") { - return make_rect({1, 1}, {0.2f, 0.2f}); - } else if (type == "test-arealight2") { - return make_rect({1, 1}, {0.2f, 0.2f}); - } else if (type == "test-largearealight1") { - return make_rect({1, 1}, {0.4f, 0.4f}); - } else if (type == "test-largearealight2") { - return make_rect({1, 1}, {0.4f, 0.4f}); - } else if (type == "test-pointlight1") { - return make_point(0); - } else if (type == "test-pointlight2") { - return make_point(0); - } else if (type == "test-point") { - return make_points(1); - } else if (type == "test-points") { - return make_points(4096); - } else if (type == "test-points-random") { - auto shape = make_random_points(4096, {0.075f, 0.075f, 0.075f}); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape; - } else if (type == "test-points-grid") { - auto shape = make_points({256, 256}, {0.075f, 0.075f}); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - for (auto& r : shape.radius) r *= 0.075f; - return shape; - } else if (type == "test-lines-grid") { - auto shape = make_lines({256, 256}, {0.075f, 0.075f}); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - for (auto& r : shape.radius) r *= 0.075f; - return shape; - } else if (type == "test-thickpoints-grid") { - auto shape = make_points({16, 16}, {0.075f, 0.075f}); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - for (auto& r : shape.radius) r *= 0.075f * 10; - return shape; - } else if (type == "test-thicklines-grid") { - auto shape = make_lines({16, 16}, {0.075f, 0.075f}); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - for (auto& r : shape.radius) r *= 0.075f * 10; - return shape; - } else if (type == "test-particles") { - return make_points(4096); - } else if (type == "test-cloth") { - return make_rect({64, 64}, {0.2f, 0.2f}); - } else if (type == "test-clothy") { - return make_recty({64, 64}, {0.2f, 0.2f}); - } else { - throw io_error::preset_error(type); - } -} - -// Shape presets used for testing. -fvshape_data make_fvshape_preset(const string& type) { - if (type == "default-quad") { - return shape_to_fvshape(make_rect()); - } else if (type == "default-quady") { - return shape_to_fvshape(make_recty()); - } else if (type == "default-cube") { - return shape_to_fvshape(make_box()); - } else if (type == "default-cube-rounded") { - return shape_to_fvshape(make_rounded_box()); - } else if (type == "default-sphere") { - return shape_to_fvshape(make_sphere()); - } else if (type == "default-matcube") { - return shape_to_fvshape(make_rounded_box()); - } else if (type == "default-matsphere") { - return shape_to_fvshape(make_uvspherey()); - } else if (type == "default-disk") { - return shape_to_fvshape(make_disk()); - } else if (type == "default-disk-bulged") { - return shape_to_fvshape(make_bulged_disk()); - } else if (type == "default-quad-bulged") { - return shape_to_fvshape(make_bulged_rect()); - } else if (type == "default-uvsphere") { - return shape_to_fvshape(make_uvsphere()); - } else if (type == "default-uvsphere-flipcap") { - return shape_to_fvshape(make_capped_uvsphere()); - } else if (type == "default-uvspherey") { - return shape_to_fvshape(make_uvspherey()); - } else if (type == "default-uvspherey-flipcap") { - return shape_to_fvshape(make_capped_uvspherey()); - } else if (type == "default-uvdisk") { - return shape_to_fvshape(make_uvdisk()); - } else if (type == "default-uvcylinder") { - return shape_to_fvshape(make_uvcylinder()); - } else if (type == "default-uvcylinder-rounded") { - return shape_to_fvshape(make_rounded_uvcylinder({32, 32, 32})); - } else if (type == "default-geosphere") { - return shape_to_fvshape(make_geosphere()); - } else if (type == "default-floor") { - return shape_to_fvshape(make_floor()); - } else if (type == "default-floor-bent") { - return shape_to_fvshape(make_bent_floor()); - } else if (type == "default-matball") { - return shape_to_fvshape(make_sphere()); - } else if (type == "default-hairball-interior") { - return shape_to_fvshape(make_sphere(pow2(5), 0.8f)); - } else if (type == "default-suzanne") { - return shape_to_fvshape(make_monkey()); - } else if (type == "default-cube-facevarying") { - return make_fvbox(); - } else if (type == "default-sphere-facevarying") { - return make_fvsphere(); - } else if (type == "default-quady-displaced") { - return shape_to_fvshape(make_recty({256, 256})); - } else if (type == "default-sphere-displaced") { - return shape_to_fvshape(make_sphere(128)); - } else if (type == "test-cube") { - auto shape = make_rounded_box( - {32, 32, 32}, {0.075f, 0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-matsphere") { - auto shape = make_uvspherey({32, 32}, 0.075f, {2, 1}); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-uvsphere") { - auto shape = make_uvsphere({32, 32}, 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-uvsphere-flipcap") { - auto shape = make_capped_uvsphere({32, 32}, 0.075f, {1, 1}, 0.3f * 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-uvspherey") { - auto shape = make_uvspherey({32, 32}, 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-uvspherey-flipcap") { - auto shape = make_capped_uvspherey({32, 32}, 0.075f, {1, 1}, 0.3f * 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-sphere") { - auto shape = make_sphere(32, 0.075f, 1); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-sphere-displaced") { - auto shape = make_sphere(128, 0.075f, 1); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-matcube") { - auto shape = make_rounded_box( - {32, 32, 32}, {0.075f, 0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-disk") { - auto shape = make_disk(32, 0.075f, 1); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-uvcylinder") { - auto shape = make_rounded_uvcylinder( - {32, 32, 32}, {0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-floor") { - return shape_to_fvshape(make_floor({1, 1}, {2, 2}, {20, 20})); - } else if (type == "test-smallfloor") { - return shape_to_fvshape(make_floor({1, 1}, {0.5f, 0.5f}, {1, 1})); - } else if (type == "test-quad") { - return shape_to_fvshape(make_rect({1, 1}, {0.075f, 0.075f}, {1, 1})); - } else if (type == "test-quady") { - return shape_to_fvshape(make_recty({1, 1}, {0.075f, 0.075f}, {1, 1})); - } else if (type == "test-quad-displaced") { - return shape_to_fvshape(make_rect({256, 256}, {0.075f, 0.075f}, {1, 1})); - } else if (type == "test-quady-displaced") { - return shape_to_fvshape(make_recty({256, 256}, {0.075f, 0.075f}, {1, 1})); - } else if (type == "test-matball") { - auto shape = make_sphere(32, 0.075f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-suzanne-subdiv") { - auto shape = make_monkey(0.075f * 0.8f); - for (auto& p : shape.positions) p += {0, 0.075f, 0}; - return shape_to_fvshape(shape); - } else if (type == "test-cube-subdiv") { - auto fvshape = make_fvcube(0.075f); - for (auto& p : fvshape.positions) p += {0, 0.075f, 0}; - return fvshape; - } else if (type == "test-arealight1") { - return shape_to_fvshape(make_rect({1, 1}, {0.2f, 0.2f})); - } else if (type == "test-arealight2") { - return shape_to_fvshape(make_rect({1, 1}, {0.2f, 0.2f})); - } else if (type == "test-largearealight1") { - return shape_to_fvshape(make_rect({1, 1}, {0.4f, 0.4f})); - } else if (type == "test-largearealight2") { - return shape_to_fvshape(make_rect({1, 1}, {0.4f, 0.4f})); - } else if (type == "test-cloth") { - return shape_to_fvshape(make_rect({64, 64}, {0.2f, 0.2f})); - } else if (type == "test-clothy") { - return shape_to_fvshape(make_recty({64, 64}, {0.2f, 0.2f})); - } else { - throw io_error::preset_error(type); - } -} - -// Load ply mesh -bool load_shape(const string& filename, shape_data& shape, string& error, - bool flip_texcoord) { - try { - load_shape(filename, shape, flip_texcoord); - return true; - } catch (const io_error& exception) { - error = exception.what(); - return false; - } -} - -// Save ply mesh -bool save_shape(const string& filename, const shape_data& shape, string& error, - bool flip_texcoord, bool ascii) { - try { - save_shape(filename, shape, flip_texcoord, ascii); - return true; - } catch (const io_error& exception) { - error = exception.what(); - return false; - } -} - -// Load ply mesh -bool load_fvshape(const string& filename, fvshape_data& fvshape, string& error, - bool flip_texcoord) { - try { - load_fvshape(filename, fvshape, flip_texcoord); - return true; - } catch (const io_error& exception) { - error = exception.what(); - return false; - } -} - -// Save ply mesh -bool save_fvshape(const string& filename, const fvshape_data& fvshape, - string& error, bool flip_texcoord, bool ascii) { - try { - save_fvshape(filename, fvshape, flip_texcoord, ascii); - return true; - } catch (const io_error& exception) { - error = exception.what(); - return false; - } -} - -// Shape presets used ofr testing. -bool make_shape_preset(shape_data& shape, const string& type, string& error) { - try { - shape = make_shape_preset(type); - return true; - } catch (const io_error& exception) { - error = exception.what(); - return false; - } -} - -// Shape presets used for testing. -bool make_fvshape_preset( - fvshape_data& fvshape, const string& type, string& error) { - try { - fvshape = make_fvshape_preset(type); - return true; - } catch (const io_error& exception) { - error = exception.what(); - return false; - } -} - -} // namespace yocto - // ----------------------------------------------------------------------------- // UTILITIES // ----------------------------------------------------------------------------- diff --git a/libs/yocto/yocto_shape.cpp b/libs/yocto/yocto_shape.cpp index 48fc9d03f..2d5cbdd7e 100644 --- a/libs/yocto/yocto_shape.cpp +++ b/libs/yocto/yocto_shape.cpp @@ -55,2809 +55,3786 @@ using namespace std::string_literals; } // namespace yocto // ----------------------------------------------------------------------------- -// IMPLEMENTATION OF COMPUTATION OF PER-VERTEX PROPERTIES +// IMPLEMENTATION FO SHAPE PROPERTIES // ----------------------------------------------------------------------------- namespace yocto { -// Compute per-vertex tangents for lines. -vector lines_tangents( - const vector& lines, const vector& positions) { - auto tangents = vector{positions.size()}; - for (auto& tangent : tangents) tangent = {0, 0, 0}; - for (auto& l : lines) { - auto tangent = line_tangent(positions[l.x], positions[l.y]); - auto length = line_length(positions[l.x], positions[l.y]); - tangents[l.x] += tangent * length; - tangents[l.y] += tangent * length; +// Interpolate vertex data +vec3f eval_position(const shape_data& shape, int element, const vec2f& uv) { + if (!shape.points.empty()) { + auto& point = shape.points[element]; + return shape.positions[point]; + } else if (!shape.lines.empty()) { + auto& line = shape.lines[element]; + return interpolate_line( + shape.positions[line.x], shape.positions[line.y], uv.x); + } else if (!shape.triangles.empty()) { + auto& triangle = shape.triangles[element]; + return interpolate_triangle(shape.positions[triangle.x], + shape.positions[triangle.y], shape.positions[triangle.z], uv); + } else if (!shape.quads.empty()) { + auto& quad = shape.quads[element]; + return interpolate_quad(shape.positions[quad.x], shape.positions[quad.y], + shape.positions[quad.z], shape.positions[quad.w], uv); + } else { + return {0, 0, 0}; } - for (auto& tangent : tangents) tangent = normalize(tangent); - return tangents; } -// Compute per-vertex normals for triangles. -vector triangles_normals( - const vector& triangles, const vector& positions) { - auto normals = vector{positions.size()}; - for (auto& normal : normals) normal = {0, 0, 0}; - for (auto& t : triangles) { - auto normal = triangle_normal( - positions[t.x], positions[t.y], positions[t.z]); - auto area = triangle_area(positions[t.x], positions[t.y], positions[t.z]); - normals[t.x] += normal * area; - normals[t.y] += normal * area; - normals[t.z] += normal * area; +vec3f eval_normal(const shape_data& shape, int element, const vec2f& uv) { + if (shape.normals.empty()) return eval_element_normal(shape, element); + if (!shape.points.empty()) { + auto& point = shape.points[element]; + return normalize(shape.normals[point]); + } else if (!shape.lines.empty()) { + auto& line = shape.lines[element]; + return normalize( + interpolate_line(shape.normals[line.x], shape.normals[line.y], uv.x)); + } else if (!shape.triangles.empty()) { + auto& triangle = shape.triangles[element]; + return normalize(interpolate_triangle(shape.normals[triangle.x], + shape.normals[triangle.y], shape.normals[triangle.z], uv)); + } else if (!shape.quads.empty()) { + auto& quad = shape.quads[element]; + return normalize( + interpolate_quad(shape.normals[quad.x], shape.normals[quad.y], + shape.normals[quad.z], shape.normals[quad.w], uv)); + } else { + return {0, 0, 1}; } - for (auto& normal : normals) normal = normalize(normal); - return normals; } -// Compute per-vertex normals for quads. -vector quads_normals( - const vector& quads, const vector& positions) { - auto normals = vector{positions.size()}; - for (auto& normal : normals) normal = {0, 0, 0}; - for (auto& q : quads) { - auto normal = quad_normal( - positions[q.x], positions[q.y], positions[q.z], positions[q.w]); - auto area = quad_area( - positions[q.x], positions[q.y], positions[q.z], positions[q.w]); - normals[q.x] += normal * area; - normals[q.y] += normal * area; - normals[q.z] += normal * area; - if (q.z != q.w) normals[q.w] += normal * area; +vec3f eval_tangent(const shape_data& shape, int element, const vec2f& uv) { + return eval_normal(shape, element, uv); +} + +vec2f eval_texcoord(const shape_data& shape, int element, const vec2f& uv) { + if (shape.texcoords.empty()) return uv; + if (!shape.points.empty()) { + auto& point = shape.points[element]; + return shape.texcoords[point]; + } else if (!shape.lines.empty()) { + auto& line = shape.lines[element]; + return interpolate_line( + shape.texcoords[line.x], shape.texcoords[line.y], uv.x); + } else if (!shape.triangles.empty()) { + auto& triangle = shape.triangles[element]; + return interpolate_triangle(shape.texcoords[triangle.x], + shape.texcoords[triangle.y], shape.texcoords[triangle.z], uv); + } else if (!shape.quads.empty()) { + auto& quad = shape.quads[element]; + return interpolate_quad(shape.texcoords[quad.x], shape.texcoords[quad.y], + shape.texcoords[quad.z], shape.texcoords[quad.w], uv); + } else { + return uv; } - for (auto& normal : normals) normal = normalize(normal); - return normals; } -// Compute per-vertex tangents for lines. -void lines_tangents(vector& tangents, const vector& lines, - const vector& positions) { - if (tangents.size() != positions.size()) { - throw std::out_of_range("array should be the same length"); - } - for (auto& tangent : tangents) tangent = {0, 0, 0}; - for (auto& l : lines) { - auto tangent = line_tangent(positions[l.x], positions[l.y]); - auto length = line_length(positions[l.x], positions[l.y]); - tangents[l.x] += tangent * length; - tangents[l.y] += tangent * length; +vec4f eval_color(const shape_data& shape, int element, const vec2f& uv) { + if (shape.colors.empty()) return {1, 1, 1, 1}; + if (!shape.points.empty()) { + auto& point = shape.points[element]; + return shape.colors[point]; + } else if (!shape.lines.empty()) { + auto& line = shape.lines[element]; + return interpolate_line(shape.colors[line.x], shape.colors[line.y], uv.x); + } else if (!shape.triangles.empty()) { + auto& triangle = shape.triangles[element]; + return interpolate_triangle(shape.colors[triangle.x], + shape.colors[triangle.y], shape.colors[triangle.z], uv); + } else if (!shape.quads.empty()) { + auto& quad = shape.quads[element]; + return interpolate_quad(shape.colors[quad.x], shape.colors[quad.y], + shape.colors[quad.z], shape.colors[quad.w], uv); + } else { + return {0, 0}; } - for (auto& tangent : tangents) tangent = normalize(tangent); } -// Compute per-vertex normals for triangles. -void triangles_normals(vector& normals, const vector& triangles, - const vector& positions) { - if (normals.size() != positions.size()) { - throw std::out_of_range("array should be the same length"); +float eval_radius(const shape_data& shape, int element, const vec2f& uv) { + if (shape.radius.empty()) return 0; + if (!shape.points.empty()) { + auto& point = shape.points[element]; + return shape.radius[point]; + } else if (!shape.lines.empty()) { + auto& line = shape.lines[element]; + return interpolate_line(shape.radius[line.x], shape.radius[line.y], uv.x); + } else if (!shape.triangles.empty()) { + auto& triangle = shape.triangles[element]; + return interpolate_triangle(shape.radius[triangle.x], + shape.radius[triangle.y], shape.radius[triangle.z], uv); + } else if (!shape.quads.empty()) { + auto& quad = shape.quads[element]; + return interpolate_quad(shape.radius[quad.x], shape.radius[quad.y], + shape.radius[quad.z], shape.radius[quad.w], uv); + } else { + return 0; } - for (auto& normal : normals) normal = {0, 0, 0}; - for (auto& t : triangles) { - auto normal = triangle_normal( - positions[t.x], positions[t.y], positions[t.z]); - auto area = triangle_area(positions[t.x], positions[t.y], positions[t.z]); - normals[t.x] += normal * area; - normals[t.y] += normal * area; - normals[t.z] += normal * area; +} + +// Evaluate element normals +vec3f eval_element_normal(const shape_data& shape, int element) { + if (!shape.points.empty()) { + return {0, 0, 1}; + } else if (!shape.lines.empty()) { + auto& line = shape.lines[element]; + return line_tangent(shape.positions[line.x], shape.positions[line.y]); + } else if (!shape.triangles.empty()) { + auto& triangle = shape.triangles[element]; + return triangle_normal(shape.positions[triangle.x], + shape.positions[triangle.y], shape.positions[triangle.z]); + } else if (!shape.quads.empty()) { + auto& quad = shape.quads[element]; + return quad_normal(shape.positions[quad.x], shape.positions[quad.y], + shape.positions[quad.z], shape.positions[quad.w]); + } else { + return {0, 0, 0}; } - for (auto& normal : normals) normal = normalize(normal); } -// Compute per-vertex normals for quads. -void quads_normals(vector& normals, const vector& quads, - const vector& positions) { - if (normals.size() != positions.size()) { - throw std::out_of_range("array should be the same length"); +// Compute per-vertex normals/tangents for lines/triangles/quads. +vector compute_normals(const shape_data& shape) { + if (!shape.points.empty()) { + return vector(shape.positions.size(), {0, 0, 1}); + } else if (!shape.lines.empty()) { + return lines_tangents(shape.lines, shape.positions); + } else if (!shape.triangles.empty()) { + return triangles_normals(shape.triangles, shape.positions); + } else if (!shape.quads.empty()) { + return quads_normals(shape.quads, shape.positions); + } else { + return vector(shape.positions.size(), {0, 0, 1}); } - for (auto& normal : normals) normal = {0, 0, 0}; - for (auto& q : quads) { - auto normal = quad_normal( - positions[q.x], positions[q.y], positions[q.z], positions[q.w]); - auto area = quad_area( - positions[q.x], positions[q.y], positions[q.z], positions[q.w]); - normals[q.x] += normal * area; - normals[q.y] += normal * area; - normals[q.z] += normal * area; - if (q.z != q.w) normals[q.w] += normal * area; +} +void compute_normals(vector& normals, const shape_data& shape) { + if (!shape.points.empty()) { + normals.assign(shape.positions.size(), {0, 0, 1}); + } else if (!shape.lines.empty()) { + lines_tangents(normals, shape.lines, shape.positions); + } else if (!shape.triangles.empty()) { + triangles_normals(normals, shape.triangles, shape.positions); + } else if (!shape.quads.empty()) { + quads_normals(normals, shape.quads, shape.positions); + } else { + normals.assign(shape.positions.size(), {0, 0, 1}); } - for (auto& normal : normals) normal = normalize(normal); } -// Compute per-vertex tangent frame for triangle meshes. -// Tangent space is defined by a four component vector. -// The first three components are the tangent with respect to the U texcoord. -// The fourth component is the sign of the tangent wrt the V texcoord. -// Tangent frame is useful in normal mapping. -vector triangles_tangent_spaces(const vector& triangles, - const vector& positions, const vector& normals, - const vector& texcoords) { - auto tangu = vector(positions.size(), vec3f{0, 0, 0}); - auto tangv = vector(positions.size(), vec3f{0, 0, 0}); - for (auto t : triangles) { - auto tutv = triangle_tangents_fromuv(positions[t.x], positions[t.y], - positions[t.z], texcoords[t.x], texcoords[t.y], texcoords[t.z]); - for (auto vid : {t.x, t.y, t.z}) tangu[vid] += normalize(tutv.first); - for (auto vid : {t.x, t.y, t.z}) tangv[vid] += normalize(tutv.second); +// Shape sampling +vector sample_shape_cdf(const shape_data& shape) { + if (!shape.points.empty()) { + return sample_points_cdf((int)shape.points.size()); + } else if (!shape.lines.empty()) { + return sample_lines_cdf(shape.lines, shape.positions); + } else if (!shape.triangles.empty()) { + return sample_triangles_cdf(shape.triangles, shape.positions); + } else if (!shape.quads.empty()) { + return sample_quads_cdf(shape.quads, shape.positions); + } else { + return sample_points_cdf((int)shape.positions.size()); } - for (auto& t : tangu) t = normalize(t); - for (auto& t : tangv) t = normalize(t); +} - auto tangent_spaces = vector(positions.size()); - for (auto& tangent : tangent_spaces) tangent = zero4f; - for (auto i = 0; i < positions.size(); i++) { - tangu[i] = orthonormalize(tangu[i], normals[i]); - auto s = (dot(cross(normals[i], tangu[i]), tangv[i]) < 0) ? -1.0f : 1.0f; - tangent_spaces[i] = {tangu[i].x, tangu[i].y, tangu[i].z, s}; +void sample_shape_cdf(vector& cdf, const shape_data& shape) { + if (!shape.points.empty()) { + sample_points_cdf(cdf, (int)shape.points.size()); + } else if (!shape.lines.empty()) { + sample_lines_cdf(cdf, shape.lines, shape.positions); + } else if (!shape.triangles.empty()) { + sample_triangles_cdf(cdf, shape.triangles, shape.positions); + } else if (!shape.quads.empty()) { + sample_quads_cdf(cdf, shape.quads, shape.positions); + } else { + sample_points_cdf(cdf, (int)shape.positions.size()); } - return tangent_spaces; } -// Apply skinning -pair, vector> skin_vertices(const vector& positions, - const vector& normals, const vector& weights, - const vector& joints, const vector& xforms) { - auto skinned_positions = vector{positions.size()}; - auto skinned_normals = vector{positions.size()}; - for (auto i = 0; i < positions.size(); i++) { - skinned_positions[i] = - transform_point(xforms[joints[i].x], positions[i]) * weights[i].x + - transform_point(xforms[joints[i].y], positions[i]) * weights[i].y + - transform_point(xforms[joints[i].z], positions[i]) * weights[i].z + - transform_point(xforms[joints[i].w], positions[i]) * weights[i].w; - } - for (auto i = 0; i < normals.size(); i++) { - skinned_normals[i] = normalize( - transform_direction(xforms[joints[i].x], normals[i]) * weights[i].x + - transform_direction(xforms[joints[i].y], normals[i]) * weights[i].y + - transform_direction(xforms[joints[i].z], normals[i]) * weights[i].z + - transform_direction(xforms[joints[i].w], normals[i]) * weights[i].w); +shape_point sample_shape(const shape_data& shape, const vector& cdf, + float rn, const vec2f& ruv) { + if (!shape.points.empty()) { + auto element = sample_points(cdf, rn); + return {element, {0, 0}}; + } else if (!shape.lines.empty()) { + auto [element, u] = sample_lines(cdf, rn, ruv.x); + return {element, {u, 0}}; + } else if (!shape.triangles.empty()) { + auto [element, uv] = sample_triangles(cdf, rn, ruv); + return {element, uv}; + } else if (!shape.quads.empty()) { + auto [element, uv] = sample_quads(cdf, rn, ruv); + return {element, uv}; + } else { + auto element = sample_points(cdf, rn); + return {element, {0, 0}}; } - return {skinned_positions, skinned_normals}; } -// Apply skinning as specified in Khronos glTF -pair, vector> skin_matrices(const vector& positions, - const vector& normals, const vector& weights, - const vector& joints, const vector& xforms) { - auto skinned_positions = vector{positions.size()}; - auto skinned_normals = vector{positions.size()}; - for (auto i = 0; i < positions.size(); i++) { - auto xform = xforms[joints[i].x] * weights[i].x + - xforms[joints[i].y] * weights[i].y + - xforms[joints[i].z] * weights[i].z + - xforms[joints[i].w] * weights[i].w; - skinned_positions[i] = transform_point(xform, positions[i]); - skinned_normals[i] = normalize(transform_direction(xform, normals[i])); +vector sample_shape( + const shape_data& shape, int num_samples, uint64_t seed) { + auto cdf = sample_shape_cdf(shape); + auto points = vector(num_samples); + auto rng = make_rng(seed); + for (auto& point : points) { + point = sample_shape(shape, cdf, rand1f(rng), rand2f(rng)); } - return {skinned_positions, skinned_normals}; + return points; } -// Apply skinning -void skin_vertices(vector& skinned_positions, - vector& skinned_normals, const vector& positions, - const vector& normals, const vector& weights, - const vector& joints, const vector& xforms) { - if (skinned_positions.size() != positions.size() || - skinned_normals.size() != normals.size()) { - throw std::out_of_range("arrays should be the same size"); - } - for (auto i = 0; i < positions.size(); i++) { - skinned_positions[i] = - transform_point(xforms[joints[i].x], positions[i]) * weights[i].x + - transform_point(xforms[joints[i].y], positions[i]) * weights[i].y + - transform_point(xforms[joints[i].z], positions[i]) * weights[i].z + - transform_point(xforms[joints[i].w], positions[i]) * weights[i].w; - } - for (auto i = 0; i < normals.size(); i++) { - skinned_normals[i] = normalize( - transform_direction(xforms[joints[i].x], normals[i]) * weights[i].x + - transform_direction(xforms[joints[i].y], normals[i]) * weights[i].y + - transform_direction(xforms[joints[i].z], normals[i]) * weights[i].z + - transform_direction(xforms[joints[i].w], normals[i]) * weights[i].w); +// Conversions +shape_data quads_to_triangles(const shape_data& shape) { + auto result = shape; + quads_to_triangles(result, result); + return result; +} +void quads_to_triangles(shape_data& result, const shape_data& shape) { + result.triangles = quads_to_triangles(shape.quads); + result.quads = {}; +} + +// Subdivision +shape_data subdivide_shape( + const shape_data& shape, int subdivisions, bool catmullclark) { + // This should probably be re-implemented in a faster fashion, + // but how it is not obvious + if (subdivisions == 0) return shape; + auto subdivided = shape_data{}; + if (!subdivided.points.empty()) { + subdivided = shape; + } else if (!subdivided.lines.empty()) { + std::tie(std::ignore, subdivided.normals) = subdivide_lines( + shape.lines, shape.normals, subdivisions); + std::tie(std::ignore, subdivided.texcoords) = subdivide_lines( + shape.lines, shape.texcoords, subdivisions); + std::tie(std::ignore, subdivided.colors) = subdivide_lines( + shape.lines, shape.colors, subdivisions); + std::tie(std::ignore, subdivided.radius) = subdivide_lines( + subdivided.lines, shape.radius, subdivisions); + std::tie(subdivided.lines, subdivided.positions) = subdivide_lines( + shape.lines, shape.positions, subdivisions); + } else if (!subdivided.triangles.empty()) { + std::tie(std::ignore, subdivided.normals) = subdivide_triangles( + shape.triangles, shape.normals, subdivisions); + std::tie(std::ignore, subdivided.texcoords) = subdivide_triangles( + shape.triangles, shape.texcoords, subdivisions); + std::tie(std::ignore, subdivided.colors) = subdivide_triangles( + shape.triangles, shape.colors, subdivisions); + std::tie(std::ignore, subdivided.radius) = subdivide_triangles( + shape.triangles, shape.radius, subdivisions); + std::tie(subdivided.triangles, subdivided.positions) = subdivide_triangles( + shape.triangles, shape.positions, subdivisions); + } else if (!subdivided.quads.empty() && !catmullclark) { + std::tie(std::ignore, subdivided.normals) = subdivide_quads( + shape.quads, shape.normals, subdivisions); + std::tie(std::ignore, subdivided.texcoords) = subdivide_quads( + shape.quads, shape.texcoords, subdivisions); + std::tie(std::ignore, subdivided.colors) = subdivide_quads( + shape.quads, shape.colors, subdivisions); + std::tie(std::ignore, subdivided.radius) = subdivide_quads( + shape.quads, shape.radius, subdivisions); + std::tie(subdivided.quads, subdivided.positions) = subdivide_quads( + shape.quads, shape.positions, subdivisions); + } else if (!subdivided.quads.empty() && catmullclark) { + std::tie(std::ignore, subdivided.normals) = subdivide_catmullclark( + shape.quads, shape.normals, subdivisions); + std::tie(std::ignore, subdivided.texcoords) = subdivide_catmullclark( + shape.quads, shape.texcoords, subdivisions); + std::tie(std::ignore, subdivided.colors) = subdivide_catmullclark( + shape.quads, shape.colors, subdivisions); + std::tie(std::ignore, subdivided.radius) = subdivide_catmullclark( + shape.quads, shape.radius, subdivisions); + std::tie(subdivided.quads, subdivided.positions) = subdivide_catmullclark( + shape.quads, shape.positions, subdivisions); + } else { + // empty shape } + return subdivided; } -// Apply skinning as specified in Khronos glTF -void skin_matrices(vector& skinned_positions, - vector& skinned_normals, const vector& positions, - const vector& normals, const vector& weights, - const vector& joints, const vector& xforms) { - if (skinned_positions.size() != positions.size() || - skinned_normals.size() != normals.size()) { - throw std::out_of_range("arrays should be the same size"); - } - for (auto i = 0; i < positions.size(); i++) { - auto xform = xforms[joints[i].x] * weights[i].x + - xforms[joints[i].y] * weights[i].y + - xforms[joints[i].z] * weights[i].z + - xforms[joints[i].w] * weights[i].w; - skinned_positions[i] = transform_point(xform, positions[i]); - skinned_normals[i] = normalize(transform_direction(xform, normals[i])); - } +vector shape_stats(const shape_data& shape, bool verbose) { + auto format = [](auto num) { + auto str = std::to_string(num); + while (str.size() < 13) str = " " + str; + return str; + }; + auto format3 = [](auto num) { + auto str = std::to_string(num.x) + " " + std::to_string(num.y) + " " + + std::to_string(num.z); + while (str.size() < 13) str = " " + str; + return str; + }; + + auto bbox = invalidb3f; + for (auto& pos : shape.positions) bbox = merge(bbox, pos); + + auto stats = vector{}; + stats.push_back("points: " + format(shape.points.size())); + stats.push_back("lines: " + format(shape.lines.size())); + stats.push_back("triangles: " + format(shape.triangles.size())); + stats.push_back("quads: " + format(shape.quads.size())); + stats.push_back("positions: " + format(shape.positions.size())); + stats.push_back("normals: " + format(shape.normals.size())); + stats.push_back("texcoords: " + format(shape.texcoords.size())); + stats.push_back("colors: " + format(shape.colors.size())); + stats.push_back("radius: " + format(shape.radius.size())); + stats.push_back("center: " + format3(center(bbox))); + stats.push_back("size: " + format3(size(bbox))); + stats.push_back("min: " + format3(bbox.min)); + stats.push_back("max: " + format3(bbox.max)); + + return stats; } } // namespace yocto // ----------------------------------------------------------------------------- -// COMPUTATION OF PER VERTEX PROPETIES +// IMPLEMENTATION FOR FVSHAPE PROPERTIES // ----------------------------------------------------------------------------- namespace yocto { -// Flip vertex normals -vector flip_normals(const vector& normals) { - auto flipped = normals; - for (auto& n : flipped) n = -n; - return flipped; -} -// Flip face orientation -vector flip_triangles(const vector& triangles) { - auto flipped = triangles; - for (auto& t : flipped) swap(t.y, t.z); - return flipped; -} -vector flip_quads(const vector& quads) { - auto flipped = quads; - for (auto& q : flipped) { - if (q.z != q.w) { - swap(q.y, q.w); - } else { - swap(q.y, q.z); - q.w = q.z; - } +// Interpolate vertex data +vec3f eval_position(const fvshape_data& shape, int element, const vec2f& uv) { + if (!shape.quadspos.empty()) { + auto& quad = shape.quadspos[element]; + return interpolate_quad(shape.positions[quad.x], shape.positions[quad.y], + shape.positions[quad.z], shape.positions[quad.w], uv); + } else { + return {0, 0, 0}; } - return flipped; } -// Align vertex positions. Alignment is 0: none, 1: min, 2: max, 3: center. -vector align_vertices( - const vector& positions, const vec3i& alignment) { - auto bounds = invalidb3f; - for (auto& p : positions) bounds = merge(bounds, p); - auto offset = vec3f{0, 0, 0}; - switch (alignment.x) { - case 1: offset.x = bounds.min.x; break; - case 2: offset.x = (bounds.min.x + bounds.max.x) / 2; break; - case 3: offset.x = bounds.max.x; break; - } - switch (alignment.y) { - case 1: offset.y = bounds.min.y; break; - case 2: offset.y = (bounds.min.y + bounds.max.y) / 2; break; - case 3: offset.y = bounds.max.y; break; +vec3f eval_normal(const fvshape_data& shape, int element, const vec2f& uv) { + if (shape.normals.empty()) return eval_element_normal(shape, element); + if (!shape.quadspos.empty()) { + auto& quad = shape.quadsnorm[element]; + return normalize( + interpolate_quad(shape.normals[quad.x], shape.normals[quad.y], + shape.normals[quad.z], shape.normals[quad.w], uv)); + } else { + return {0, 0, 1}; } - switch (alignment.z) { - case 1: offset.z = bounds.min.z; break; - case 2: offset.z = (bounds.min.z + bounds.max.z) / 2; break; - case 3: offset.z = bounds.max.z; break; +} + +vec2f eval_texcoord(const fvshape_data& shape, int element, const vec2f& uv) { + if (shape.texcoords.empty()) return uv; + if (!shape.quadspos.empty()) { + auto& quad = shape.quadstexcoord[element]; + return interpolate_quad(shape.texcoords[quad.x], shape.texcoords[quad.y], + shape.texcoords[quad.z], shape.texcoords[quad.w], uv); + } else { + return uv; } - auto aligned = positions; - for (auto& p : aligned) p -= offset; - return aligned; } -} // namespace yocto +// Evaluate element normals +vec3f eval_element_normal(const fvshape_data& shape, int element) { + if (!shape.quadspos.empty()) { + auto& quad = shape.quadspos[element]; + return quad_normal(shape.positions[quad.x], shape.positions[quad.y], + shape.positions[quad.z], shape.positions[quad.w]); + } else { + return {0, 0, 0}; + } +} + +// Compute per-vertex normals/tangents for lines/triangles/quads. +vector compute_normals(const fvshape_data& shape) { + if (!shape.quadspos.empty()) { + return quads_normals(shape.quadspos, shape.positions); + } else { + return vector(shape.positions.size(), {0, 0, 1}); + } +} +void compute_normals(vector& normals, const fvshape_data& shape) { + if (!shape.quadspos.empty()) { + quads_normals(normals, shape.quadspos, shape.positions); + } else { + normals.assign(shape.positions.size(), {0, 0, 1}); + } +} + +// Conversions +shape_data fvshape_to_shape(const fvshape_data& fvshape, bool as_triangles) { + auto shape = shape_data{}; + split_facevarying(shape.quads, shape.positions, shape.normals, + shape.texcoords, fvshape.quadspos, fvshape.quadsnorm, + fvshape.quadstexcoord, fvshape.positions, fvshape.normals, + fvshape.texcoords); + return shape; +} +fvshape_data shape_to_fvshape(const shape_data& shape) { + if (!shape.points.empty() || !shape.lines.empty()) + throw std::invalid_argument{"cannor convert shape"}; + auto fvshape = fvshape_data{}; + fvshape.positions = shape.positions; + fvshape.normals = shape.normals; + fvshape.texcoords = shape.texcoords; + fvshape.quadspos = !shape.quads.empty() ? shape.quads + : triangles_to_quads(shape.triangles); + fvshape.quadsnorm = !shape.normals.empty() ? fvshape.quadspos + : vector{}; + fvshape.quadstexcoord = !shape.texcoords.empty() ? fvshape.quadspos + : vector{}; + return fvshape; +} + +// Subdivision +fvshape_data subdivide_fvshape( + const fvshape_data& shape, int subdivisions, bool catmullclark) { + // This should be probably re-implemeneted in a faster fashion. + if (subdivisions == 0) return shape; + auto subdivided = fvshape_data{}; + if (!catmullclark) { + std::tie(subdivided.quadspos, subdivided.positions) = subdivide_quads( + shape.quadspos, shape.positions, subdivisions); + std::tie(subdivided.quadsnorm, subdivided.normals) = subdivide_quads( + shape.quadsnorm, shape.normals, subdivisions); + std::tie(subdivided.quadstexcoord, subdivided.texcoords) = subdivide_quads( + shape.quadstexcoord, shape.texcoords, subdivisions); + } else { + std::tie(subdivided.quadspos, subdivided.positions) = + subdivide_catmullclark(shape.quadspos, shape.positions, subdivisions); + std::tie(subdivided.quadsnorm, subdivided.normals) = subdivide_catmullclark( + shape.quadsnorm, shape.normals, subdivisions); + std::tie(subdivided.quadstexcoord, subdivided.texcoords) = + subdivide_catmullclark( + shape.quadstexcoord, shape.texcoords, subdivisions, true); + } + return subdivided; +} + +vector fvshape_stats(const fvshape_data& shape, bool verbose) { + auto format = [](auto num) { + auto str = std::to_string(num); + while (str.size() < 13) str = " " + str; + return str; + }; + auto format3 = [](auto num) { + auto str = std::to_string(num.x) + " " + std::to_string(num.y) + " " + + std::to_string(num.z); + while (str.size() < 13) str = " " + str; + return str; + }; + + auto bbox = invalidb3f; + for (auto& pos : shape.positions) bbox = merge(bbox, pos); + + auto stats = vector{}; + stats.push_back("fvquads: " + format(shape.quadspos.size())); + stats.push_back("positions: " + format(shape.positions.size())); + stats.push_back("normals: " + format(shape.normals.size())); + stats.push_back("texcoords: " + format(shape.texcoords.size())); + stats.push_back("center: " + format3(center(bbox))); + stats.push_back("size: " + format3(size(bbox))); + stats.push_back("min: " + format3(bbox.min)); + stats.push_back("max: " + format3(bbox.max)); + + return stats; +} + +} // namespace yocto // ----------------------------------------------------------------------------- -// EDGEA AND ADJACENCIES +// IMPLEMENTATION FOR SHAPE EXAMPLES // ----------------------------------------------------------------------------- namespace yocto { -// Initialize an edge map with elements. -edge_map make_edge_map(const vector& triangles) { - auto emap = edge_map{}; - for (auto& t : triangles) { - insert_edge(emap, {t.x, t.y}); - insert_edge(emap, {t.y, t.z}); - insert_edge(emap, {t.z, t.x}); +// Make a tesselated rectangle. Useful in other subdivisions. +static shape_data make_quads( + const vec2i& steps, const vec2f& scale, const vec2f& uvscale) { + auto shape = shape_data{}; + + shape.positions.resize((steps.x + 1) * (steps.y + 1)); + shape.normals.resize((steps.x + 1) * (steps.y + 1)); + shape.texcoords.resize((steps.x + 1) * (steps.y + 1)); + for (auto j = 0; j <= steps.y; j++) { + for (auto i = 0; i <= steps.x; i++) { + auto uv = vec2f{i / (float)steps.x, j / (float)steps.y}; + shape.positions[j * (steps.x + 1) + i] = { + (2 * uv.x - 1) * scale.x, (2 * uv.y - 1) * scale.y, 0}; + shape.normals[j * (steps.x + 1) + i] = {0, 0, 1}; + shape.texcoords[j * (steps.x + 1) + i] = vec2f{uv.x, 1 - uv.y} * uvscale; + } } - return emap; -} -edge_map make_edge_map(const vector& quads) { - auto emap = edge_map{}; - for (auto& q : quads) { - insert_edge(emap, {q.x, q.y}); - insert_edge(emap, {q.y, q.z}); - if (q.z != q.w) insert_edge(emap, {q.z, q.w}); - insert_edge(emap, {q.w, q.x}); + + shape.quads.resize(steps.x * steps.y); + for (auto j = 0; j < steps.y; j++) { + for (auto i = 0; i < steps.x; i++) { + shape.quads[j * steps.x + i] = {j * (steps.x + 1) + i, + j * (steps.x + 1) + i + 1, (j + 1) * (steps.x + 1) + i + 1, + (j + 1) * (steps.x + 1) + i}; + } } - return emap; + + return shape; } -void insert_edges(edge_map& emap, const vector& triangles) { - for (auto& t : triangles) { - insert_edge(emap, {t.x, t.y}); - insert_edge(emap, {t.y, t.z}); - insert_edge(emap, {t.z, t.x}); + +// Merge shape elements +void merge_shape_inplace(shape_data& shape, const shape_data& merge) { + auto offset = (int)shape.positions.size(); + for (auto& p : merge.points) shape.points.push_back(p + offset); + for (auto& l : merge.lines) + shape.lines.push_back({l.x + offset, l.y + offset}); + for (auto& t : merge.triangles) + shape.triangles.push_back({t.x + offset, t.y + offset, t.z + offset}); + for (auto& q : merge.quads) + shape.quads.push_back( + {q.x + offset, q.y + offset, q.z + offset, q.w + offset}); + shape.positions.insert( + shape.positions.end(), merge.positions.begin(), merge.positions.end()); + shape.tangents.insert( + shape.tangents.end(), merge.tangents.begin(), merge.tangents.end()); + shape.texcoords.insert( + shape.texcoords.end(), merge.texcoords.begin(), merge.texcoords.end()); + shape.colors.insert( + shape.colors.end(), merge.colors.begin(), merge.colors.end()); + shape.radius.insert( + shape.radius.end(), merge.radius.begin(), merge.radius.end()); +} + +// Make a plane. +shape_data make_rect( + const vec2i& steps, const vec2f& scale, const vec2f& uvscale) { + return make_quads(steps, scale, uvscale); +} +shape_data make_bulged_rect(const vec2i& steps, const vec2f& scale, + const vec2f& uvscale, float height) { + auto shape = make_rect(steps, scale, uvscale); + if (height != 0) { + height = min(height, min(scale)); + auto radius = (1 + height * height) / (2 * height); + auto center = vec3f{0, 0, -radius + height}; + for (auto i = 0; i < shape.positions.size(); i++) { + auto pn = normalize(shape.positions[i] - center); + shape.positions[i] = center + pn * radius; + shape.normals[i] = pn; + } } -} -void insert_edges(edge_map& emap, const vector& quads) { - for (auto& q : quads) { - insert_edge(emap, {q.x, q.y}); - insert_edge(emap, {q.y, q.z}); - if (q.z != q.w) insert_edge(emap, {q.z, q.w}); - insert_edge(emap, {q.w, q.x}); + return shape; +} + +// Make a plane in the xz plane. +shape_data make_recty( + const vec2i& steps, const vec2f& scale, const vec2f& uvscale) { + auto shape = make_rect(steps, scale, uvscale); + for (auto& position : shape.positions) + position = {position.x, position.z, -position.y}; + for (auto& normal : shape.normals) normal = {normal.x, normal.z, normal.y}; + return shape; +} +shape_data make_bulged_recty(const vec2i& steps, const vec2f& scale, + const vec2f& uvscale, float height) { + auto shape = make_bulged_rect(steps, scale, uvscale, height); + for (auto& position : shape.positions) + position = {position.x, position.z, -position.y}; + for (auto& normal : shape.normals) normal = {normal.x, normal.z, normal.y}; + return shape; +} + +// Make a box. +shape_data make_box( + const vec3i& steps, const vec3f& scale, const vec3f& uvscale) { + auto shape = shape_data{}; + auto qshape = shape_data{}; + // + z + qshape = make_rect( + {steps.x, steps.y}, {scale.x, scale.y}, {uvscale.x, uvscale.y}); + for (auto& p : qshape.positions) p = {p.x, p.y, scale.z}; + for (auto& n : qshape.normals) n = {0, 0, 1}; + merge_shape_inplace(shape, qshape); + // - z + qshape = make_rect( + {steps.x, steps.y}, {scale.x, scale.y}, {uvscale.x, uvscale.y}); + for (auto& p : qshape.positions) p = {-p.x, p.y, -scale.z}; + for (auto& n : qshape.normals) n = {0, 0, -1}; + merge_shape_inplace(shape, qshape); + // + x + qshape = make_rect( + {steps.z, steps.y}, {scale.z, scale.y}, {uvscale.z, uvscale.y}); + for (auto& p : qshape.positions) p = {scale.x, p.y, -p.x}; + for (auto& n : qshape.normals) n = {1, 0, 0}; + merge_shape_inplace(shape, qshape); + // - x + qshape = make_rect( + {steps.z, steps.y}, {scale.z, scale.y}, {uvscale.z, uvscale.y}); + for (auto& p : qshape.positions) p = {-scale.x, p.y, p.x}; + for (auto& n : qshape.normals) n = {-1, 0, 0}; + merge_shape_inplace(shape, qshape); + // + y + qshape = make_rect( + {steps.x, steps.z}, {scale.x, scale.z}, {uvscale.x, uvscale.z}); + for (auto i = 0; i < qshape.positions.size(); i++) { + qshape.positions[i] = { + qshape.positions[i].x, scale.y, -qshape.positions[i].y}; + qshape.normals[i] = {0, 1, 0}; + } + merge_shape_inplace(shape, qshape); + // - y + qshape = make_rect( + {steps.x, steps.z}, {scale.x, scale.z}, {uvscale.x, uvscale.z}); + for (auto i = 0; i < qshape.positions.size(); i++) { + qshape.positions[i] = { + qshape.positions[i].x, -scale.y, qshape.positions[i].y}; + qshape.normals[i] = {0, -1, 0}; } + merge_shape_inplace(shape, qshape); + return shape; } -// Insert an edge and return its index -int insert_edge(edge_map& emap, const vec2i& edge) { - auto es = edge.x < edge.y ? edge : vec2i{edge.y, edge.x}; - auto it = emap.edges.find(es); - if (it == emap.edges.end()) { - auto data = edge_map::edge_data{(int)emap.edges.size(), 1}; - emap.edges.insert(it, {es, data}); - return data.index; - } else { - auto& data = it->second; - data.nfaces += 1; - return data.index; +shape_data make_rounded_box(const vec3i& steps, const vec3f& scale, + const vec3f& uvscale, float radius) { + auto shape = make_box(steps, scale, uvscale); + if (radius != 0) { + radius = min(radius, min(scale)); + auto c = scale - radius; + for (auto i = 0; i < shape.positions.size(); i++) { + auto pc = vec3f{abs(shape.positions[i].x), abs(shape.positions[i].y), + abs(shape.positions[i].z)}; + auto ps = vec3f{shape.positions[i].x < 0 ? -1.0f : 1.0f, + shape.positions[i].y < 0 ? -1.0f : 1.0f, + shape.positions[i].z < 0 ? -1.0f : 1.0f}; + if (pc.x >= c.x && pc.y >= c.y && pc.z >= c.z) { + auto pn = normalize(pc - c); + shape.positions[i] = c + radius * pn; + shape.normals[i] = pn; + } else if (pc.x >= c.x && pc.y >= c.y) { + auto pn = normalize((pc - c) * vec3f{1, 1, 0}); + shape.positions[i] = {c.x + radius * pn.x, c.y + radius * pn.y, pc.z}; + shape.normals[i] = pn; + } else if (pc.x >= c.x && pc.z >= c.z) { + auto pn = normalize((pc - c) * vec3f{1, 0, 1}); + shape.positions[i] = {c.x + radius * pn.x, pc.y, c.z + radius * pn.z}; + shape.normals[i] = pn; + } else if (pc.y >= c.y && pc.z >= c.z) { + auto pn = normalize((pc - c) * vec3f{0, 1, 1}); + shape.positions[i] = {pc.x, c.y + radius * pn.y, c.z + radius * pn.z}; + shape.normals[i] = pn; + } else { + continue; + } + shape.positions[i] *= ps; + shape.normals[i] *= ps; + } } + return shape; } -// Get number of edges -int num_edges(const edge_map& emap) { return (int)emap.edges.size(); } -// Get the edge index -int edge_index(const edge_map& emap, const vec2i& edge) { - auto es = edge.x < edge.y ? edge : vec2i{edge.y, edge.x}; - auto iterator = emap.edges.find(es); - if (iterator == emap.edges.end()) return -1; - return iterator->second.index; + +// Make a quad stack +shape_data make_rect_stack( + const vec3i& steps, const vec3f& scale, const vec2f& uvscale) { + auto shape = shape_data{}; + auto qshape = shape_data{}; + for (auto i = 0; i <= steps.z; i++) { + qshape = make_rect({steps.x, steps.y}, {scale.x, scale.y}, uvscale); + for (auto& p : qshape.positions) + p.z = (-1 + 2 * (float)i / steps.z) * scale.z; + merge_shape_inplace(shape, qshape); + } + return shape; } -// Get a list of edges, boundary edges, boundary vertices -vector get_edges(const edge_map& emap) { - auto edges = vector(emap.edges.size()); - for (auto& [edge, data] : emap.edges) edges[data.index] = edge; - return edges; + +// Make a floor. +shape_data make_floor( + const vec2i& steps, const vec2f& scale, const vec2f& uvscale) { + auto shape = make_rect(steps, scale, uvscale); + for (auto& position : shape.positions) + position = {position.x, position.z, -position.y}; + for (auto& normal : shape.normals) normal = {normal.x, normal.z, normal.y}; + return shape; } -vector get_boundary(const edge_map& emap) { - auto boundary = vector{}; - for (auto& [edge, data] : emap.edges) { - if (data.nfaces < 2) boundary.push_back(edge); +shape_data make_bent_floor(const vec2i& steps, const vec2f& scale, + const vec2f& uvscale, float radius) { + auto shape = make_floor(steps, scale, uvscale); + if (radius != 0) { + radius = min(radius, scale.y); + auto start = (scale.y - radius) / 2; + auto end = start + radius; + for (auto i = 0; i < shape.positions.size(); i++) { + if (shape.positions[i].z < -end) { + shape.positions[i] = { + shape.positions[i].x, -shape.positions[i].z - end + radius, -end}; + shape.normals[i] = {0, 0, 1}; + } else if (shape.positions[i].z < -start && + shape.positions[i].z >= -end) { + auto phi = (pif / 2) * (-shape.positions[i].z - start) / radius; + shape.positions[i] = {shape.positions[i].x, -cos(phi) * radius + radius, + -sin(phi) * radius - start}; + shape.normals[i] = {0, cos(phi), sin(phi)}; + } else { + } + } } - return boundary; -} -vector get_edges(const vector& triangles) { - return get_edges(make_edge_map(triangles)); + return shape; +} + +// Make a sphere. +shape_data make_sphere(int steps, float scale, float uvscale) { + auto shape = make_box({steps, steps, steps}, {scale, scale, scale}, + {uvscale, uvscale, uvscale}); + for (auto& p : shape.positions) p = normalize(p) * scale; + shape.normals = shape.positions; + for (auto& n : shape.normals) n = normalize(n); + return shape; +} + +// Make a sphere. +shape_data make_uvsphere( + const vec2i& steps, float scale, const vec2f& uvscale) { + auto shape = make_rect({1, 1}, {1, 1}); + for (auto i = 0; i < shape.positions.size(); i++) { + auto uv = shape.texcoords[i]; + auto a = vec2f{2 * pif * uv.x, pif * (1 - uv.y)}; + shape.positions[i] = + vec3f{cos(a.x) * sin(a.y), sin(a.x) * sin(a.y), cos(a.y)} * scale; + shape.normals[i] = normalize(shape.positions[i]); + shape.texcoords[i] = uv * uvscale; + } + return shape; +} + +// Make a sphere. +shape_data make_uvspherey( + const vec2i& steps, float scale, const vec2f& uvscale) { + auto shape = make_uvsphere(steps, scale, uvscale); + for (auto& position : shape.positions) + position = {position.x, position.z, position.y}; + for (auto& normal : shape.normals) normal = {normal.x, normal.z, normal.y}; + for (auto& texcoord : shape.texcoords) + texcoord = {texcoord.x, 1 - texcoord.y}; + for (auto& quad : shape.quads) quad = {quad.x, quad.w, quad.z, quad.y}; + return shape; +} + +// Make a sphere with slipped caps. +shape_data make_capped_uvsphere( + const vec2i& steps, float scale, const vec2f& uvscale, float cap) { + auto shape = make_uvsphere(steps, scale, uvscale); + if (cap != 0) { + cap = min(cap, scale / 2); + auto zflip = (scale - cap); + for (auto i = 0; i < shape.positions.size(); i++) { + if (shape.positions[i].z > zflip) { + shape.positions[i].z = 2 * zflip - shape.positions[i].z; + shape.normals[i].x = -shape.normals[i].x; + shape.normals[i].y = -shape.normals[i].y; + } else if (shape.positions[i].z < -zflip) { + shape.positions[i].z = 2 * (-zflip) - shape.positions[i].z; + shape.normals[i].x = -shape.normals[i].x; + shape.normals[i].y = -shape.normals[i].y; + } + } + } + return shape; +} + +// Make a sphere with slipped caps. +shape_data make_capped_uvspherey( + const vec2i& steps, float scale, const vec2f& uvscale, float cap) { + auto shape = make_capped_uvsphere(steps, scale, uvscale, cap); + for (auto& position : shape.positions) + position = {position.x, position.z, position.y}; + for (auto& normal : shape.normals) normal = {normal.x, normal.z, normal.y}; + for (auto& texcoord : shape.texcoords) + texcoord = {texcoord.x, 1 - texcoord.y}; + for (auto& quad : shape.quads) quad = {quad.x, quad.w, quad.z, quad.y}; + return shape; +} + +// Make a disk +shape_data make_disk(int steps, float scale, float uvscale) { + auto shape = make_rect({steps, steps}, {1, 1}, {uvscale, uvscale}); + for (auto& position : shape.positions) { + // Analytical Methods for Squaring the Disc, by C. Fong + // https://arxiv.org/abs/1509.06344 + auto xy = vec2f{position.x, position.y}; + auto uv = vec2f{ + xy.x * sqrt(1 - xy.y * xy.y / 2), xy.y * sqrt(1 - xy.x * xy.x / 2)}; + position = vec3f{uv.x, uv.y, 0} * scale; + } + return shape; } -vector get_edges(const vector& quads) { - return get_edges(make_edge_map(quads)); + +// Make a bulged disk +shape_data make_bulged_disk( + int steps, float scale, float uvscale, float height) { + auto shape = make_disk(steps, scale, uvscale); + if (height != 0) { + height = min(height, scale); + auto radius = (1 + height * height) / (2 * height); + auto center = vec3f{0, 0, -radius + height}; + for (auto i = 0; i < shape.positions.size(); i++) { + auto pn = normalize(shape.positions[i] - center); + shape.positions[i] = center + pn * radius; + shape.normals[i] = pn; + } + } + return shape; +} + +// Make a uv disk +shape_data make_uvdisk(const vec2i& steps, float scale, const vec2f& uvscale) { + auto shape = make_rect(steps, {1, 1}, {1, 1}); + for (auto i = 0; i < shape.positions.size(); i++) { + auto uv = shape.texcoords[i]; + auto phi = 2 * pif * uv.x; + shape.positions[i] = vec3f{cos(phi) * uv.y, sin(phi) * uv.y, 0} * scale; + shape.normals[i] = {0, 0, 1}; + shape.texcoords[i] = uv * uvscale; + } + return shape; } -vector get_edges( - const vector& triangles, const vector& quads) { - auto edges = get_edges(triangles); - auto more_edges = get_edges(quads); - edges.insert(edges.end(), more_edges.begin(), more_edges.end()); - return edges; + +// Make a uv cylinder +shape_data make_uvcylinder( + const vec3i& steps, const vec2f& scale, const vec3f& uvscale) { + auto shape = shape_data{}; + auto qshape = shape_data{}; + // side + qshape = make_rect({steps.x, steps.y}, {1, 1}, {1, 1}); + for (auto i = 0; i < qshape.positions.size(); i++) { + auto uv = qshape.texcoords[i]; + auto phi = 2 * pif * uv.x; + qshape.positions[i] = { + cos(phi) * scale.x, sin(phi) * scale.x, (2 * uv.y - 1) * scale.y}; + qshape.normals[i] = {cos(phi), sin(phi), 0}; + qshape.texcoords[i] = uv * vec2f{uvscale.x, uvscale.y}; + } + for (auto& quad : qshape.quads) quad = {quad.x, quad.w, quad.z, quad.y}; + merge_shape_inplace(shape, qshape); + // top + qshape = make_rect({steps.x, steps.z}, {1, 1}, {1, 1}); + for (auto i = 0; i < qshape.positions.size(); i++) { + auto uv = qshape.texcoords[i]; + auto phi = 2 * pif * uv.x; + qshape.positions[i] = { + cos(phi) * uv.y * scale.x, sin(phi) * uv.y * scale.x, 0}; + qshape.normals[i] = {0, 0, 1}; + qshape.texcoords[i] = uv * vec2f{uvscale.x, uvscale.z}; + qshape.positions[i].z = scale.y; + } + merge_shape_inplace(shape, qshape); + // bottom + qshape = make_rect({steps.x, steps.z}, {1, 1}, {1, 1}); + for (auto i = 0; i < qshape.positions.size(); i++) { + auto uv = qshape.texcoords[i]; + auto phi = 2 * pif * uv.x; + qshape.positions[i] = { + cos(phi) * uv.y * scale.x, sin(phi) * uv.y * scale.x, 0}; + qshape.normals[i] = {0, 0, 1}; + qshape.texcoords[i] = uv * vec2f{uvscale.x, uvscale.z}; + qshape.positions[i].z = -scale.y; + qshape.normals[i] = -qshape.normals[i]; + } + for (auto& qquad : qshape.quads) swap(qquad.x, qquad.z); + merge_shape_inplace(shape, qshape); + return shape; } -// Build adjacencies between faces (sorted counter-clockwise) -vector face_adjacencies(const vector& triangles) { - auto get_edge = [](const vec3i& triangle, int i) -> vec2i { - auto x = triangle[i], y = triangle[i < 2 ? i + 1 : 0]; - return x < y ? vec2i{x, y} : vec2i{y, x}; - }; - auto adjacencies = vector{triangles.size(), vec3i{-1, -1, -1}}; - auto edge_map = unordered_map(); - edge_map.reserve((size_t)(triangles.size() * 1.5)); - for (auto i = 0; i < (int)triangles.size(); ++i) { - for (auto k = 0; k < 3; ++k) { - auto edge = get_edge(triangles[i], k); - auto it = edge_map.find(edge); - if (it == edge_map.end()) { - edge_map.insert(it, {edge, i}); +// Make a rounded uv cylinder +shape_data make_rounded_uvcylinder(const vec3i& steps, const vec2f& scale, + const vec3f& uvscale, float radius) { + auto shape = make_uvcylinder(steps, scale, uvscale); + if (radius != 0) { + radius = min(radius, min(scale)); + auto c = scale - radius; + for (auto i = 0; i < shape.positions.size(); i++) { + auto phi = atan2(shape.positions[i].y, shape.positions[i].x); + auto r = length(vec2f{shape.positions[i].x, shape.positions[i].y}); + auto z = shape.positions[i].z; + auto pc = vec2f{r, abs(z)}; + auto ps = (z < 0) ? -1.0f : 1.0f; + if (pc.x >= c.x && pc.y >= c.y) { + auto pn = normalize(pc - c); + shape.positions[i] = {cos(phi) * (c.x + radius * pn.x), + sin(phi) * (c.x + radius * pn.x), ps * (c.y + radius * pn.y)}; + shape.normals[i] = {cos(phi) * pn.x, sin(phi) * pn.x, ps * pn.y}; } else { - auto neighbor = it->second; - adjacencies[i][k] = neighbor; - for (auto kk = 0; kk < 3; ++kk) { - auto edge2 = get_edge(triangles[neighbor], kk); - if (edge2 == edge) { - adjacencies[neighbor][kk] = i; - break; - } - } + continue; } } } - return adjacencies; + return shape; } -// Build adjacencies between vertices (sorted counter-clockwise) -vector> vertex_adjacencies( - const vector& triangles, const vector& adjacencies) { - auto find_index = [](const vec3i& v, int x) { - if (v.x == x) return 0; - if (v.y == x) return 1; - if (v.z == x) return 2; - return -1; - }; - - // For each vertex, find any adjacent face. - auto num_vertices = 0; - auto face_from_vertex = vector(triangles.size() * 3, -1); - - for (auto i = 0; i < (int)triangles.size(); ++i) { - for (auto k = 0; k < 3; k++) { - face_from_vertex[triangles[i][k]] = i; - num_vertices = max(num_vertices, triangles[i][k]); +// Generate lines set along a quad. Returns lines, pos, norm, texcoord, radius. +shape_data make_lines(const vec2i& steps, const vec2f& scale, + const vec2f& uvscale, const vec2f& rad) { + auto shape = shape_data{}; + shape.positions.resize((steps.x + 1) * steps.y); + shape.normals.resize((steps.x + 1) * steps.y); + shape.texcoords.resize((steps.x + 1) * steps.y); + shape.radius.resize((steps.x + 1) * steps.y); + if (steps.y > 1) { + for (auto j = 0; j < steps.y; j++) { + for (auto i = 0; i <= steps.x; i++) { + auto uv = vec2f{i / (float)steps.x, j / (float)(steps.y - 1)}; + shape.positions[j * (steps.x + 1) + i] = { + (uv.x - 0.5f) * scale.x, (uv.y - 0.5f) * scale.y, 0}; + shape.normals[j * (steps.x + 1) + i] = {1, 0, 0}; + shape.texcoords[j * (steps.x + 1) + i] = uv * uvscale; + shape.radius[j * (steps.x + 1) + i] = lerp(rad.x, rad.y, uv.x); + } + } + } else { + for (auto i = 0; i <= steps.x; i++) { + auto uv = vec2f{i / (float)steps.x, 0}; + shape.positions[i] = {(uv.x - 0.5f) * scale.x, 0, 0}; + shape.normals[i] = {1, 0, 0}; + shape.texcoords[i] = uv * uvscale; + shape.radius[i] = lerp(rad.x, rad.y, uv.x); } } - // Init result. - auto result = vector>(num_vertices); - - // For each vertex, loop around it and build its adjacency. - for (auto i = 0; i < num_vertices; ++i) { - result[i].reserve(6); - auto first_face = face_from_vertex[i]; - if (first_face == -1) continue; - - auto face = first_face; - while (true) { - auto k = find_index(triangles[face], i); - k = k != 0 ? k - 1 : 2; - result[i].push_back(triangles[face][k]); - face = adjacencies[face][k]; - if (face == -1) break; - if (face == first_face) break; + shape.lines.resize(steps.x * steps.y); + for (int j = 0; j < steps.y; j++) { + for (int i = 0; i < steps.x; i++) { + shape.lines[j * steps.x + i] = { + j * (steps.x + 1) + i, j * (steps.x + 1) + i + 1}; } } - return result; + return shape; } -// Build adjacencies between each vertex and its adjacent faces. -// Adjacencies are sorted counter-clockwise and have same starting points as -// vertex_adjacencies() -vector> vertex_to_faces_adjacencies( - const vector& triangles, const vector& adjacencies) { - auto find_index = [](const vec3i& v, int x) { - if (v.x == x) return 0; - if (v.y == x) return 1; - if (v.z == x) return 2; - return -1; - }; - - // For each vertex, find any adjacent face. - auto num_vertices = 0; - auto face_from_vertex = vector(triangles.size() * 3, -1); +// Make point primitives. Returns points, pos, norm, texcoord, radius. +shape_data make_point(float radius) { + auto shape = shape_data{}; + shape.points = {0}; + shape.positions = {{0, 0, 0}}; + shape.normals = {{0, 0, 1}}; + shape.texcoords = {{0, 0}}; + shape.radius = {radius}; + return shape; +} - for (auto i = 0; i < (int)triangles.size(); ++i) { - for (auto k = 0; k < 3; k++) { - face_from_vertex[triangles[i][k]] = i; - num_vertices = max(num_vertices, triangles[i][k]); - } +// Generate a point set with points placed at the origin with texcoords +// varying along u. +shape_data make_points(int num, float uvscale, float radius) { + auto shape = shape_data{}; + shape.points.resize(num); + for (auto i = 0; i < num; i++) shape.points[i] = i; + shape.positions.assign(num, {0, 0, 0}); + shape.normals.assign(num, {0, 0, 1}); + shape.texcoords.assign(num, {0, 0}); + shape.radius.assign(num, radius); + for (auto i = 0; i < shape.texcoords.size(); i++) + shape.texcoords[i] = {(float)i / (float)num, 0}; + return shape; +} + +shape_data make_points(const vec2i& steps, const vec2f& size, + const vec2f& uvscale, const vec2f& radius) { + auto shape = make_rect(steps, size, uvscale); + shape.quads = {}; + shape.points.resize(shape.positions.size()); + for (auto i = 0; i < (int)shape.positions.size(); i++) shape.points[i] = i; + shape.radius.resize(shape.positions.size()); + for (auto i = 0; i < (int)shape.texcoords.size(); i++) { + shape.radius[i] = lerp( + radius.x, radius.y, shape.texcoords[i].y / uvscale.y); } + return shape; +} + +shape_data make_random_points( + int num, const vec3f& size, float uvscale, float radius, uint64_t seed) { + auto shape = make_points(num, uvscale, radius); + auto rng = make_rng(seed); + for (auto& position : shape.positions) + position = (2 * rand3f(rng) - 1) * size; + for (auto& texcoord : shape.texcoords) texcoord = rand2f(rng); + return shape; +} + +// Make a facevarying rect +fvshape_data make_fvrect( + const vec2i& steps, const vec2f& scale, const vec2f& uvscale) { + auto rect = make_rect(steps, scale, uvscale); + auto shape = fvshape_data{}; + shape.positions = rect.positions; + shape.normals = rect.normals; + shape.texcoords = rect.texcoords; + shape.quadspos = rect.quads; + shape.quadsnorm = rect.quads; + shape.quadstexcoord = rect.quads; + return shape; +} + +// Make a facevarying box +fvshape_data make_fvbox( + const vec3i& steps, const vec3f& scale, const vec3f& uvscale) { + auto shape = fvshape_data{}; + make_fvbox(shape.quadspos, shape.quadsnorm, shape.quadstexcoord, + shape.positions, shape.normals, shape.texcoords, steps, scale, uvscale); + return shape; +} + +// Make a facevarying sphere +fvshape_data make_fvsphere(int steps, float scale, float uvscale) { + auto shape = fvshape_data{}; + make_fvsphere(shape.quadspos, shape.quadsnorm, shape.quadstexcoord, + shape.positions, shape.normals, shape.texcoords, steps, scale, uvscale); + return shape; +} - // Init result. - auto result = vector>(num_vertices); - - // For each vertex, loop around it and build its adjacency. - for (auto i = 0; i < num_vertices; ++i) { - result[i].reserve(6); - auto first_face = face_from_vertex[i]; - if (first_face == -1) continue; +// Predefined meshes +shape_data make_monkey(float scale, int subdivisions) { + extern vector suzanne_positions; + extern vector suzanne_quads; - auto face = first_face; - while (true) { - auto k = find_index(triangles[face], i); - k = k != 0 ? k - 1 : 2; - face = adjacencies[face][k]; - result[i].push_back(face); - if (face == -1) break; - if (face == first_face) break; - } + auto shape = shape_data{}; + if (subdivisions == 0) { + shape.quads = suzanne_quads; + shape.positions = suzanne_positions; + } else { + std::tie(shape.quads, shape.positions) = subdivide_quads( + suzanne_quads, suzanne_positions, subdivisions); + } + if (scale != 1) { + for (auto& p : shape.positions) p *= scale; + } + return shape; +} +shape_data make_quad(float scale, int subdivisions) { + static const auto quad_positions = vector{ + {-1, -1, 0}, {+1, -1, 0}, {+1, +1, 0}, {-1, +1, 0}}; + static const auto quad_normals = vector{ + {0, 0, 1}, {0, 0, 1}, {0, 0, 1}, {0, 0, 1}}; + static const auto quad_texcoords = vector{ + {0, 1}, {1, 1}, {1, 0}, {0, 0}}; + static const auto quad_quads = vector{{0, 1, 2, 3}}; + auto shape = shape_data{}; + if (subdivisions == 0) { + shape.quads = quad_quads; + shape.positions = quad_positions; + shape.normals = quad_normals; + shape.texcoords = quad_texcoords; + } else { + std::tie(shape.quads, shape.positions) = subdivide_quads( + quad_quads, quad_positions, subdivisions); + std::tie(shape.quads, shape.normals) = subdivide_quads( + quad_quads, quad_normals, subdivisions); + std::tie(shape.quads, shape.texcoords) = subdivide_quads( + quad_quads, quad_texcoords, subdivisions); + } + if (scale != 1) { + for (auto& p : shape.positions) p *= scale; + } + return shape; +} +shape_data make_quady(float scale, int subdivisions) { + static const auto quady_positions = vector{ + {-1, 0, -1}, {-1, 0, +1}, {+1, 0, +1}, {+1, 0, -1}}; + static const auto quady_normals = vector{ + {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0}}; + static const auto quady_texcoords = vector{ + {0, 0}, {1, 0}, {1, 1}, {0, 1}}; + static const auto quady_quads = vector{{0, 1, 2, 3}}; + auto shape = shape_data{}; + if (subdivisions == 0) { + shape.quads = quady_quads; + shape.positions = quady_positions; + shape.normals = quady_normals; + shape.texcoords = quady_texcoords; + } else { + std::tie(shape.quads, shape.positions) = subdivide_quads( + quady_quads, quady_positions, subdivisions); + std::tie(shape.quads, shape.normals) = subdivide_quads( + quady_quads, quady_normals, subdivisions); + std::tie(shape.quads, shape.texcoords) = subdivide_quads( + quady_quads, quady_texcoords, subdivisions); } + if (scale != 1) { + for (auto& p : shape.positions) p *= scale; + } + return shape; +} +shape_data make_cube(float scale, int subdivisions) { + static const auto cube_positions = vector{{-1, -1, +1}, {+1, -1, +1}, + {+1, +1, +1}, {-1, +1, +1}, {+1, -1, -1}, {-1, -1, -1}, {-1, +1, -1}, + {+1, +1, -1}, {+1, -1, +1}, {+1, -1, -1}, {+1, +1, -1}, {+1, +1, +1}, + {-1, -1, -1}, {-1, -1, +1}, {-1, +1, +1}, {-1, +1, -1}, {-1, +1, +1}, + {+1, +1, +1}, {+1, +1, -1}, {-1, +1, -1}, {+1, -1, +1}, {-1, -1, +1}, + {-1, -1, -1}, {+1, -1, -1}}; + static const auto cube_normals = vector{{0, 0, +1}, {0, 0, +1}, + {0, 0, +1}, {0, 0, +1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + {+1, 0, 0}, {+1, 0, 0}, {+1, 0, 0}, {+1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, + {-1, 0, 0}, {-1, 0, 0}, {0, +1, 0}, {0, +1, 0}, {0, +1, 0}, {0, +1, 0}, + {0, -1, 0}, {0, -1, 0}, {0, -1, 0}, {0, -1, 0}}; + static const auto cube_texcoords = vector{{0, 1}, {1, 1}, {1, 0}, + {0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}, + {0, 1}, {1, 1}, {1, 0}, {0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}, {0, 1}, + {1, 1}, {1, 0}, {0, 0}}; + static const auto cube_quads = vector{{0, 1, 2, 3}, {4, 5, 6, 7}, + {8, 9, 10, 11}, {12, 13, 14, 15}, {16, 17, 18, 19}, {20, 21, 22, 23}}; - return result; + auto shape = shape_data{}; + if (subdivisions == 0) { + shape.quads = cube_quads; + shape.positions = cube_positions; + shape.normals = cube_normals; + shape.texcoords = cube_texcoords; + } else { + std::tie(shape.quads, shape.positions) = subdivide_quads( + cube_quads, cube_positions, subdivisions); + std::tie(shape.quads, shape.normals) = subdivide_quads( + cube_quads, cube_normals, subdivisions); + std::tie(shape.quads, shape.texcoords) = subdivide_quads( + cube_quads, cube_texcoords, subdivisions); + } + if (scale != 1) { + for (auto& p : shape.positions) p *= scale; + } + return shape; } +fvshape_data make_fvcube(float scale, int subdivisions) { + static const auto fvcube_positions = vector{{-1, -1, +1}, {+1, -1, +1}, + {+1, +1, +1}, {-1, +1, +1}, {+1, -1, -1}, {-1, -1, -1}, {-1, +1, -1}, + {+1, +1, -1}}; + static const auto fvcube_normals = vector{{0, 0, +1}, {0, 0, +1}, + {0, 0, +1}, {0, 0, +1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, + {+1, 0, 0}, {+1, 0, 0}, {+1, 0, 0}, {+1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, + {-1, 0, 0}, {-1, 0, 0}, {0, +1, 0}, {0, +1, 0}, {0, +1, 0}, {0, +1, 0}, + {0, -1, 0}, {0, -1, 0}, {0, -1, 0}, {0, -1, 0}}; + static const auto fvcube_texcoords = vector{{0, 1}, {1, 1}, {1, 0}, + {0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}, + {0, 1}, {1, 1}, {1, 0}, {0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}, {0, 1}, + {1, 1}, {1, 0}, {0, 0}}; + static const auto fvcube_quadspos = vector{{0, 1, 2, 3}, {4, 5, 6, 7}, + {1, 4, 7, 2}, {5, 0, 3, 6}, {3, 2, 7, 6}, {1, 0, 5, 4}}; + static const auto fvcube_quadsnorm = vector{{0, 1, 2, 3}, {4, 5, 6, 7}, + {8, 9, 10, 11}, {12, 13, 14, 15}, {16, 17, 18, 19}, {20, 21, 22, 23}}; + static const auto fvcube_quadstexcoord = vector{{0, 1, 2, 3}, + {4, 5, 6, 7}, {8, 9, 10, 11}, {12, 13, 14, 15}, {16, 17, 18, 19}, + {20, 21, 22, 23}}; -// Compute boundaries as a list of loops (sorted counter-clockwise) -vector> ordered_boundaries(const vector& triangles, - const vector& adjacency, int num_vertices) { - // map every boundary vertex to its next one - auto next_vert = vector(num_vertices, -1); - for (auto i = 0; i < (int)triangles.size(); ++i) { - for (auto k = 0; k < 3; ++k) { - if (adjacency[i][k] == -1) - next_vert[triangles[i][k]] = triangles[i][(k + 1) % 3]; - } + auto shape = fvshape_data{}; + if (subdivisions == 0) { + shape.quadspos = fvcube_quadspos; + shape.quadsnorm = fvcube_quadsnorm; + shape.quadstexcoord = fvcube_quadstexcoord; + shape.positions = fvcube_positions; + shape.normals = fvcube_normals; + shape.texcoords = fvcube_texcoords; + } else { + std::tie(shape.quadspos, shape.positions) = subdivide_quads( + fvcube_quadspos, fvcube_positions, subdivisions); + std::tie(shape.quadsnorm, shape.normals) = subdivide_quads( + fvcube_quadsnorm, fvcube_normals, subdivisions); + std::tie(shape.quadstexcoord, shape.texcoords) = subdivide_quads( + fvcube_quadstexcoord, fvcube_texcoords, subdivisions); + } + if (scale != 1) { + for (auto& p : shape.positions) p *= scale; } + return shape; +} +shape_data make_geosphere(float scale, int subdivisions) { + // https://stackoverflow.com/questions/17705621/algorithm-for-a-geodesic-sphere + const float X = 0.525731112119133606f; + const float Z = 0.850650808352039932f; + static auto geosphere_positions = vector{{-X, 0.0, Z}, {X, 0.0, Z}, + {-X, 0.0, -Z}, {X, 0.0, -Z}, {0.0, Z, X}, {0.0, Z, -X}, {0.0, -Z, X}, + {0.0, -Z, -X}, {Z, X, 0.0}, {-Z, X, 0.0}, {Z, -X, 0.0}, {-Z, -X, 0.0}}; + static auto geosphere_triangles = vector{{0, 1, 4}, {0, 4, 9}, + {9, 4, 5}, {4, 8, 5}, {4, 1, 8}, {8, 1, 10}, {8, 10, 3}, {5, 8, 3}, + {5, 3, 2}, {2, 3, 7}, {7, 3, 10}, {7, 10, 6}, {7, 6, 11}, {11, 6, 0}, + {0, 6, 1}, {6, 10, 1}, {9, 11, 0}, {9, 2, 11}, {9, 5, 2}, {7, 11, 2}}; - // result - auto boundaries = vector>(); + auto shape = shape_data{}; + if (subdivisions == 0) { + shape.triangles = geosphere_triangles; + shape.positions = geosphere_positions; + shape.normals = geosphere_positions; + } else { + std::tie(shape.triangles, shape.positions) = subdivide_triangles( + geosphere_triangles, geosphere_positions, subdivisions); + for (auto& position : shape.positions) position = normalize(position); + shape.normals = shape.positions; + } + if (scale != 1) { + for (auto& p : shape.positions) p *= scale; + } + return shape; +} - // arrange boundary vertices in loops - for (auto i = 0; i < (int)next_vert.size(); i++) { - if (next_vert[i] == -1) continue; +// Make a hair ball around a shape +shape_data make_hair(const shape_data& base, const vec2i& steps, + const vec2f& len, const vec2f& rad, const vec2f& noise, const vec2f& clump, + const vec2f& rotation, int seed) { + auto points = sample_shape(base, steps.y, seed); + auto bpos = vector{}; + auto bnorm = vector{}; + auto btexcoord = vector{}; + for (auto& point : points) { + bpos.push_back(eval_position(base, point.element, point.uv)); + bnorm.push_back(eval_normal(base, point.element, point.uv)); + btexcoord.push_back(eval_texcoord(base, point.element, point.uv)); + } - // add new empty boundary - boundaries.emplace_back(); - auto current = i; + auto rng = make_rng(seed, 3); + auto blen = vector(bpos.size()); + for (auto& l : blen) { + l = lerp(len.x, len.y, rand1f(rng)); + } - while (true) { - auto next = next_vert[current]; - if (next == -1) { - return {}; + auto cidx = vector(); + if (clump.x > 0) { + for (auto bidx = 0; bidx < bpos.size(); bidx++) { + cidx.push_back(0); + auto cdist = flt_max; + for (auto c = 0; c < clump.y; c++) { + auto d = length(bpos[bidx] - bpos[c]); + if (d < cdist) { + cdist = d; + cidx.back() = c; + } } - next_vert[current] = -1; - boundaries.back().push_back(current); + } + } - // close loop if necessary - if (next == i) - break; - else - current = next; + auto shape = make_lines(steps, {1, 1}, {1, 1}, {1, 1}); + for (auto i = 0; i < shape.positions.size(); i++) { + auto u = shape.texcoords[i].x; + auto bidx = i / (steps.x + 1); + shape.positions[i] = bpos[bidx] + bnorm[bidx] * u * blen[bidx]; + shape.normals[i] = bnorm[bidx]; + shape.radius[i] = lerp(rad.x, rad.y, u); + if (clump.x > 0) { + shape.positions[i] = + shape.positions[i] + + (shape.positions[i + (cidx[bidx] - bidx) * (steps.x + 1)] - + shape.positions[i]) * + u * clump.x; + } + if (noise.x > 0) { + auto nx = + (perlin_noise(shape.positions[i] * noise.y + vec3f{0, 0, 0}) * 2 - + 1) * + noise.x; + auto ny = + (perlin_noise(shape.positions[i] * noise.y + vec3f{3, 7, 11}) * 2 - + 1) * + noise.x; + auto nz = + (perlin_noise(shape.positions[i] * noise.y + vec3f{13, 17, 19}) * 2 - + 1) * + noise.x; + shape.positions[i] += {nx, ny, nz}; } } - return boundaries; -} + if (clump.x > 0 || noise.x > 0 || rotation.x > 0) { + shape.normals = lines_tangents(shape.lines, shape.positions); + } -} // namespace yocto + return shape; +} -// ----------------------------------------------------------------------------- -// IMPLEMENTATION FOR BVH -// ----------------------------------------------------------------------------- -namespace yocto { +// Grow hairs around a shape +shape_data make_hair2(const shape_data& base, const vec2i& steps, + const vec2f& len, const vec2f& radius, float noise, float gravity, + int seed) { + auto points = sample_shape(base, steps.y, seed); + auto bpositions = vector{}; + auto bnormals = vector{}; + auto btexcoord = vector{}; + for (auto& point : points) { + bpositions.push_back(eval_position(base, point.element, point.uv)); + bnormals.push_back(eval_normal(base, point.element, point.uv)); + btexcoord.push_back(eval_texcoord(base, point.element, point.uv)); + } -// Splits a BVH node using the middle heuristic. Returns split position and -// axis. -static pair split_middle(vector& primitives, - const vector& bboxes, const vector& centers, int start, - int end) { - // initialize split axis and position - auto axis = 0; - auto mid = (start + end) / 2; + auto shape = make_lines(steps, {1, 1}, {1, 1}, radius); + auto rng = make_rng(seed); + for (auto idx = 0; idx < steps.y; idx++) { + auto offset = idx * (steps.x + 1); + auto position = bpositions[idx]; + auto direction = bnormals[idx]; + auto length = rand1f(rng) * (len.y - len.x) + len.x; + shape.positions[offset] = position; + for (auto iidx = 1; iidx <= steps.x; iidx++) { + shape.positions[offset + iidx] = position; + shape.positions[offset + iidx] += direction * length / steps.x; + shape.positions[offset + iidx] += (2 * rand3f(rng) - 1) * noise; + shape.positions[offset + iidx] += vec3f{0, -gravity, 0}; + direction = normalize(shape.positions[offset + iidx] - position); + position = shape.positions[offset + iidx]; + } + } - // compute primintive bounds and size - auto cbbox = invalidb3f; - for (auto i = start; i < end; i++) - cbbox = merge(cbbox, centers[primitives[i]]); - auto csize = cbbox.max - cbbox.min; - if (csize == vec3f{0, 0, 0}) return {mid, axis}; + shape.normals = lines_tangents(shape.lines, shape.positions); - // split along largest - if (csize.x >= csize.y && csize.x >= csize.z) axis = 0; - if (csize.y >= csize.x && csize.y >= csize.z) axis = 1; - if (csize.z >= csize.x && csize.z >= csize.y) axis = 2; + return shape; +} - // split the space in the middle along the largest axis - auto cmiddle = (cbbox.max + cbbox.min) / 2; - auto middle = cmiddle[axis]; - mid = (int)(std::partition(primitives.data() + start, primitives.data() + end, - [axis, middle, ¢ers]( - auto a) { return centers[a][axis] < middle; }) - - primitives.data()); - - // if we were not able to split, just break the primitives in half - if (mid == start || mid == end) { - axis = 0; - mid = (start + end) / 2; - // throw std::runtime_error("bad bvh split"); - } - - return {mid, axis}; +// Make a heightfield mesh. +shape_data make_heightfield(const vec2i& size, const vector& height) { + auto shape = make_recty({size.x - 1, size.y - 1}, + vec2f{(float)size.x, (float)size.y} / (float)max(size), {1, 1}); + for (auto j = 0; j < size.y; j++) + for (auto i = 0; i < size.x; i++) + shape.positions[j * size.x + i].y = height[j * size.x + i]; + shape.normals = quads_normals(shape.quads, shape.positions); + return shape; } - -// Maximum number of primitives per BVH node. -const int bvh_max_prims = 4; - -// Build BVH nodes -static void build_bvh(shape_bvh& bvh, vector& bboxes) { - // get values - auto& nodes = bvh.nodes; - auto& primitives = bvh.primitives; - - // prepare to build nodes - nodes.clear(); - nodes.reserve(bboxes.size() * 2); - - // prepare primitives - bvh.primitives.resize(bboxes.size()); - for (auto idx = 0; idx < bboxes.size(); idx++) bvh.primitives[idx] = idx; - - // prepare centers - auto centers = vector(bboxes.size()); - for (auto idx = 0; idx < bboxes.size(); idx++) - centers[idx] = center(bboxes[idx]); - - // queue up first node - auto queue = deque{{0, 0, (int)bboxes.size()}}; - nodes.emplace_back(); - - // create nodes until the queue is empty - while (!queue.empty()) { - // grab node to work on - auto next = queue.front(); - queue.pop_front(); - auto nodeid = next.x, start = next.y, end = next.z; - - // grab node - auto& node = nodes[nodeid]; - - // compute bounds - node.bbox = invalidb3f; - for (auto i = start; i < end; i++) - node.bbox = merge(node.bbox, bboxes[primitives[i]]); - - // split into two children - if (end - start > bvh_max_prims) { - // get split - auto [mid, axis] = split_middle(primitives, bboxes, centers, start, end); - - // make an internal node - node.internal = true; - node.axis = (int8_t)axis; - node.num = 2; - node.start = (int)nodes.size(); - nodes.emplace_back(); - nodes.emplace_back(); - queue.push_back({node.start + 0, start, mid}); - queue.push_back({node.start + 1, mid, end}); - } else { - // Make a leaf node - node.internal = false; - node.num = (int16_t)(end - start); - node.start = start; - } - } - - // cleanup - nodes.shrink_to_fit(); +shape_data make_heightfield(const vec2i& size, const vector& color) { + auto shape = make_recty({size.x - 1, size.y - 1}, + vec2f{(float)size.x, (float)size.y} / (float)max(size), {1, 1}); + for (auto j = 0; j < size.y; j++) + for (auto i = 0; i < size.x; i++) + shape.positions[j * size.x + i].y = mean(xyz(color[j * size.x + i])); + shape.normals = quads_normals(shape.quads, shape.positions); + return shape; } -// Update bvh -static void update_bvh(shape_bvh& bvh, const vector& bboxes) { - for (auto nodeid = (int)bvh.nodes.size() - 1; nodeid >= 0; nodeid--) { - auto& node = bvh.nodes[nodeid]; - node.bbox = invalidb3f; - if (node.internal) { - for (auto idx = 0; idx < 2; idx++) { - node.bbox = merge(node.bbox, bvh.nodes[node.start + idx].bbox); - } - } else { - for (auto idx = 0; idx < node.num; idx++) { - node.bbox = merge(node.bbox, bboxes[bvh.primitives[node.start + idx]]); - } - } +// Convert points to small spheres and lines to small cylinders. This is +// intended for making very small primitives for display in interactive +// applications, so the spheres are low res. +shape_data points_to_spheres( + const vector& vertices, int steps, float scale) { + auto shape = shape_data{}; + for (auto& vertex : vertices) { + auto sphere = make_sphere(steps, scale, 1); + for (auto& position : sphere.positions) position += vertex; + merge_shape_inplace(shape, sphere); } + return shape; } - -// Build shape bvh -shape_bvh make_points_bvh(const vector& points, - const vector& positions, const vector& radius) { - // build primitives - auto bboxes = vector(points.size()); - for (auto idx = 0; idx < bboxes.size(); idx++) { - auto& p = points[idx]; - bboxes[idx] = point_bounds(positions[p], radius[p]); +shape_data polyline_to_cylinders( + const vector& vertices, int steps, float scale) { + auto shape = shape_data{}; + for (auto idx = 0; idx < (int)vertices.size() - 1; idx++) { + auto cylinder = make_uvcylinder({steps, 1, 1}, {scale, 1}, {1, 1, 1}); + auto frame = frame_fromz((vertices[idx] + vertices[idx + 1]) / 2, + vertices[idx] - vertices[idx + 1]); + auto length = distance(vertices[idx], vertices[idx + 1]); + for (auto& position : cylinder.positions) + position = transform_point(frame, position * vec3f{1, 1, length / 2}); + for (auto& normal : cylinder.normals) + normal = transform_direction(frame, normal); + merge_shape_inplace(shape, cylinder); } - - // build nodes - auto bvh = shape_bvh{}; - build_bvh(bvh, bboxes); - return bvh; + return shape; } -shape_bvh make_lines_bvh(const vector& lines, - const vector& positions, const vector& radius) { - // build primitives - auto bboxes = vector(lines.size()); - for (auto idx = 0; idx < bboxes.size(); idx++) { - auto& l = lines[idx]; - bboxes[idx] = line_bounds( - positions[l.x], positions[l.y], radius[l.x], radius[l.y]); +shape_data lines_to_cylinders( + const vector& vertices, int steps, float scale) { + auto shape = shape_data{}; + for (auto idx = 0; idx < (int)vertices.size(); idx += 2) { + auto cylinder = make_uvcylinder({steps, 1, 1}, {scale, 1}, {1, 1, 1}); + auto frame = frame_fromz((vertices[idx + 0] + vertices[idx + 1]) / 2, + vertices[idx + 0] - vertices[idx + 1]); + auto length = distance(vertices[idx + 0], vertices[idx + 1]); + for (auto& position : cylinder.positions) + position = transform_point(frame, position * vec3f{1, 1, length / 2}); + for (auto& normal : cylinder.normals) + normal = transform_direction(frame, normal); + merge_shape_inplace(shape, cylinder); } - - // build nodes - auto bvh = shape_bvh{}; - build_bvh(bvh, bboxes); - return bvh; + return shape; } -shape_bvh make_triangles_bvh(const vector& triangles, - const vector& positions, const vector& radius) { - // build primitives - auto bboxes = vector(triangles.size()); - for (auto idx = 0; idx < bboxes.size(); idx++) { - auto& t = triangles[idx]; - bboxes[idx] = triangle_bounds( - positions[t.x], positions[t.y], positions[t.z]); +shape_data lines_to_cylinders(const vector& lines, + const vector& positions, int steps, float scale) { + auto shape = shape_data{}; + for (auto& line : lines) { + auto cylinder = make_uvcylinder({steps, 1, 1}, {scale, 1}, {1, 1, 1}); + auto frame = frame_fromz((positions[line.x] + positions[line.y]) / 2, + positions[line.x] - positions[line.y]); + auto length = distance(positions[line.x], positions[line.y]); + for (auto& position : cylinder.positions) + position = transform_point(frame, position * vec3f{1, 1, length / 2}); + for (auto& normal : cylinder.normals) + normal = transform_direction(frame, normal); + merge_shape_inplace(shape, cylinder); } - - // build nodes - auto bvh = shape_bvh{}; - build_bvh(bvh, bboxes); - return bvh; + return shape; } -shape_bvh make_quads_bvh(const vector& quads, - const vector& positions, const vector& radius) { - // build primitives - auto bboxes = vector(quads.size()); - for (auto idx = 0; idx < bboxes.size(); idx++) { - auto& q = quads[idx]; - bboxes[idx] = quad_bounds( - positions[q.x], positions[q.y], positions[q.z], positions[q.w]); - } - // build nodes - auto bvh = shape_bvh{}; - build_bvh(bvh, bboxes); - return bvh; -} +} // namespace yocto -void update_points_bvh(shape_bvh& bvh, const vector& points, - const vector& positions, const vector& radius) { - // build primitives - auto bboxes = vector(points.size()); - for (auto idx = 0; idx < bboxes.size(); idx++) { - auto& p = points[idx]; - bboxes[idx] = point_bounds(positions[p], radius[p]); - } +// ----------------------------------------------------------------------------- +// IMPLEMENTATION OF COMPUTATION OF PER-VERTEX PROPERTIES +// ----------------------------------------------------------------------------- +namespace yocto { - // update nodes - update_bvh(bvh, bboxes); -} -void update_lines_bvh(shape_bvh& bvh, const vector& lines, - const vector& positions, const vector& radius) { - // build primitives - auto bboxes = vector(lines.size()); - for (auto idx = 0; idx < bboxes.size(); idx++) { - auto& l = lines[idx]; - bboxes[idx] = line_bounds( - positions[l.x], positions[l.y], radius[l.x], radius[l.y]); +// Compute per-vertex tangents for lines. +vector lines_tangents( + const vector& lines, const vector& positions) { + auto tangents = vector{positions.size()}; + for (auto& tangent : tangents) tangent = {0, 0, 0}; + for (auto& l : lines) { + auto tangent = line_tangent(positions[l.x], positions[l.y]); + auto length = line_length(positions[l.x], positions[l.y]); + tangents[l.x] += tangent * length; + tangents[l.y] += tangent * length; } - - // update nodes - update_bvh(bvh, bboxes); + for (auto& tangent : tangents) tangent = normalize(tangent); + return tangents; } -void update_triangles_bvh(shape_bvh& bvh, const vector& triangles, - const vector& positions) { - // build primitives - auto bboxes = vector(triangles.size()); - for (auto idx = 0; idx < bboxes.size(); idx++) { - auto& t = triangles[idx]; - bboxes[idx] = triangle_bounds( + +// Compute per-vertex normals for triangles. +vector triangles_normals( + const vector& triangles, const vector& positions) { + auto normals = vector{positions.size()}; + for (auto& normal : normals) normal = {0, 0, 0}; + for (auto& t : triangles) { + auto normal = triangle_normal( positions[t.x], positions[t.y], positions[t.z]); + auto area = triangle_area(positions[t.x], positions[t.y], positions[t.z]); + normals[t.x] += normal * area; + normals[t.y] += normal * area; + normals[t.z] += normal * area; } - - // update nodes - update_bvh(bvh, bboxes); + for (auto& normal : normals) normal = normalize(normal); + return normals; } -void update_quads_bvh(shape_bvh& bvh, const vector& quads, - const vector& positions) { - // build primitives - auto bboxes = vector(quads.size()); - for (auto idx = 0; idx < bboxes.size(); idx++) { - auto& q = quads[idx]; - bboxes[idx] = quad_bounds( + +// Compute per-vertex normals for quads. +vector quads_normals( + const vector& quads, const vector& positions) { + auto normals = vector{positions.size()}; + for (auto& normal : normals) normal = {0, 0, 0}; + for (auto& q : quads) { + auto normal = quad_normal( + positions[q.x], positions[q.y], positions[q.z], positions[q.w]); + auto area = quad_area( positions[q.x], positions[q.y], positions[q.z], positions[q.w]); + normals[q.x] += normal * area; + normals[q.y] += normal * area; + normals[q.z] += normal * area; + if (q.z != q.w) normals[q.w] += normal * area; } + for (auto& normal : normals) normal = normalize(normal); + return normals; +} - // update nodes - update_bvh(bvh, bboxes); +// Compute per-vertex tangents for lines. +void lines_tangents(vector& tangents, const vector& lines, + const vector& positions) { + if (tangents.size() != positions.size()) { + throw std::out_of_range("array should be the same length"); + } + for (auto& tangent : tangents) tangent = {0, 0, 0}; + for (auto& l : lines) { + auto tangent = line_tangent(positions[l.x], positions[l.y]); + auto length = line_length(positions[l.x], positions[l.y]); + tangents[l.x] += tangent * length; + tangents[l.y] += tangent * length; + } + for (auto& tangent : tangents) tangent = normalize(tangent); } -// Intersect ray with a bvh. -template -static bool intersect_elements_bvh(const shape_bvh& bvh, - Intersect&& intersect_element, const ray3f& ray_, int& element, vec2f& uv, - float& distance, bool find_any) { - // check empty - if (bvh.nodes.empty()) return false; +// Compute per-vertex normals for triangles. +void triangles_normals(vector& normals, const vector& triangles, + const vector& positions) { + if (normals.size() != positions.size()) { + throw std::out_of_range("array should be the same length"); + } + for (auto& normal : normals) normal = {0, 0, 0}; + for (auto& t : triangles) { + auto normal = triangle_normal( + positions[t.x], positions[t.y], positions[t.z]); + auto area = triangle_area(positions[t.x], positions[t.y], positions[t.z]); + normals[t.x] += normal * area; + normals[t.y] += normal * area; + normals[t.z] += normal * area; + } + for (auto& normal : normals) normal = normalize(normal); +} - // node stack - auto node_stack = array{}; - auto node_cur = 0; - node_stack[node_cur++] = 0; +// Compute per-vertex normals for quads. +void quads_normals(vector& normals, const vector& quads, + const vector& positions) { + if (normals.size() != positions.size()) { + throw std::out_of_range("array should be the same length"); + } + for (auto& normal : normals) normal = {0, 0, 0}; + for (auto& q : quads) { + auto normal = quad_normal( + positions[q.x], positions[q.y], positions[q.z], positions[q.w]); + auto area = quad_area( + positions[q.x], positions[q.y], positions[q.z], positions[q.w]); + normals[q.x] += normal * area; + normals[q.y] += normal * area; + normals[q.z] += normal * area; + if (q.z != q.w) normals[q.w] += normal * area; + } + for (auto& normal : normals) normal = normalize(normal); +} - // shared variables - auto hit = false; +// Compute per-vertex tangent frame for triangle meshes. +// Tangent space is defined by a four component vector. +// The first three components are the tangent with respect to the U texcoord. +// The fourth component is the sign of the tangent wrt the V texcoord. +// Tangent frame is useful in normal mapping. +vector triangles_tangent_spaces(const vector& triangles, + const vector& positions, const vector& normals, + const vector& texcoords) { + auto tangu = vector(positions.size(), vec3f{0, 0, 0}); + auto tangv = vector(positions.size(), vec3f{0, 0, 0}); + for (auto t : triangles) { + auto tutv = triangle_tangents_fromuv(positions[t.x], positions[t.y], + positions[t.z], texcoords[t.x], texcoords[t.y], texcoords[t.z]); + for (auto vid : {t.x, t.y, t.z}) tangu[vid] += normalize(tutv.first); + for (auto vid : {t.x, t.y, t.z}) tangv[vid] += normalize(tutv.second); + } + for (auto& t : tangu) t = normalize(t); + for (auto& t : tangv) t = normalize(t); - // copy ray to modify it - auto ray = ray_; + auto tangent_spaces = vector(positions.size()); + for (auto& tangent : tangent_spaces) tangent = zero4f; + for (auto i = 0; i < positions.size(); i++) { + tangu[i] = orthonormalize(tangu[i], normals[i]); + auto s = (dot(cross(normals[i], tangu[i]), tangv[i]) < 0) ? -1.0f : 1.0f; + tangent_spaces[i] = {tangu[i].x, tangu[i].y, tangu[i].z, s}; + } + return tangent_spaces; +} - // prepare ray for fast queries - auto ray_dinv = vec3f{1 / ray.d.x, 1 / ray.d.y, 1 / ray.d.z}; - auto ray_dsign = vec3i{(ray_dinv.x < 0) ? 1 : 0, (ray_dinv.y < 0) ? 1 : 0, - (ray_dinv.z < 0) ? 1 : 0}; +// Apply skinning +pair, vector> skin_vertices(const vector& positions, + const vector& normals, const vector& weights, + const vector& joints, const vector& xforms) { + auto skinned_positions = vector{positions.size()}; + auto skinned_normals = vector{positions.size()}; + for (auto i = 0; i < positions.size(); i++) { + skinned_positions[i] = + transform_point(xforms[joints[i].x], positions[i]) * weights[i].x + + transform_point(xforms[joints[i].y], positions[i]) * weights[i].y + + transform_point(xforms[joints[i].z], positions[i]) * weights[i].z + + transform_point(xforms[joints[i].w], positions[i]) * weights[i].w; + } + for (auto i = 0; i < normals.size(); i++) { + skinned_normals[i] = normalize( + transform_direction(xforms[joints[i].x], normals[i]) * weights[i].x + + transform_direction(xforms[joints[i].y], normals[i]) * weights[i].y + + transform_direction(xforms[joints[i].z], normals[i]) * weights[i].z + + transform_direction(xforms[joints[i].w], normals[i]) * weights[i].w); + } + return {skinned_positions, skinned_normals}; +} - // walking stack - while (node_cur) { - // grab node - auto& node = bvh.nodes[node_stack[--node_cur]]; +// Apply skinning as specified in Khronos glTF +pair, vector> skin_matrices(const vector& positions, + const vector& normals, const vector& weights, + const vector& joints, const vector& xforms) { + auto skinned_positions = vector{positions.size()}; + auto skinned_normals = vector{positions.size()}; + for (auto i = 0; i < positions.size(); i++) { + auto xform = xforms[joints[i].x] * weights[i].x + + xforms[joints[i].y] * weights[i].y + + xforms[joints[i].z] * weights[i].z + + xforms[joints[i].w] * weights[i].w; + skinned_positions[i] = transform_point(xform, positions[i]); + skinned_normals[i] = normalize(transform_direction(xform, normals[i])); + } + return {skinned_positions, skinned_normals}; +} - // intersect bbox - // if (!intersect_bbox(ray, ray_dinv, ray_dsign, node.bbox)) continue; - if (!intersect_bbox(ray, ray_dinv, node.bbox)) continue; +// Apply skinning +void skin_vertices(vector& skinned_positions, + vector& skinned_normals, const vector& positions, + const vector& normals, const vector& weights, + const vector& joints, const vector& xforms) { + if (skinned_positions.size() != positions.size() || + skinned_normals.size() != normals.size()) { + throw std::out_of_range("arrays should be the same size"); + } + for (auto i = 0; i < positions.size(); i++) { + skinned_positions[i] = + transform_point(xforms[joints[i].x], positions[i]) * weights[i].x + + transform_point(xforms[joints[i].y], positions[i]) * weights[i].y + + transform_point(xforms[joints[i].z], positions[i]) * weights[i].z + + transform_point(xforms[joints[i].w], positions[i]) * weights[i].w; + } + for (auto i = 0; i < normals.size(); i++) { + skinned_normals[i] = normalize( + transform_direction(xforms[joints[i].x], normals[i]) * weights[i].x + + transform_direction(xforms[joints[i].y], normals[i]) * weights[i].y + + transform_direction(xforms[joints[i].z], normals[i]) * weights[i].z + + transform_direction(xforms[joints[i].w], normals[i]) * weights[i].w); + } +} - // intersect node, switching based on node type - // for each type, iterate over the the primitive list - if (node.internal) { - // for internal nodes, attempts to proceed along the - // split axis from smallest to largest nodes - if (ray_dsign[node.axis]) { - node_stack[node_cur++] = node.start + 0; - node_stack[node_cur++] = node.start + 1; - } else { - node_stack[node_cur++] = node.start + 1; - node_stack[node_cur++] = node.start + 0; - } +// Apply skinning as specified in Khronos glTF +void skin_matrices(vector& skinned_positions, + vector& skinned_normals, const vector& positions, + const vector& normals, const vector& weights, + const vector& joints, const vector& xforms) { + if (skinned_positions.size() != positions.size() || + skinned_normals.size() != normals.size()) { + throw std::out_of_range("arrays should be the same size"); + } + for (auto i = 0; i < positions.size(); i++) { + auto xform = xforms[joints[i].x] * weights[i].x + + xforms[joints[i].y] * weights[i].y + + xforms[joints[i].z] * weights[i].z + + xforms[joints[i].w] * weights[i].w; + skinned_positions[i] = transform_point(xform, positions[i]); + skinned_normals[i] = normalize(transform_direction(xform, normals[i])); + } +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// COMPUTATION OF PER VERTEX PROPETIES +// ----------------------------------------------------------------------------- +namespace yocto { + +// Flip vertex normals +vector flip_normals(const vector& normals) { + auto flipped = normals; + for (auto& n : flipped) n = -n; + return flipped; +} +// Flip face orientation +vector flip_triangles(const vector& triangles) { + auto flipped = triangles; + for (auto& t : flipped) swap(t.y, t.z); + return flipped; +} +vector flip_quads(const vector& quads) { + auto flipped = quads; + for (auto& q : flipped) { + if (q.z != q.w) { + swap(q.y, q.w); } else { - for (auto idx = 0; idx < node.num; idx++) { - auto primitive = bvh.primitives[node.start + idx]; - if (intersect_element(primitive, ray, uv, distance)) { - hit = true; - element = primitive; - ray.tmax = distance; - } - } + swap(q.y, q.z); + q.w = q.z; } - - // check for early exit - if (find_any && hit) return hit; } - - return hit; + return flipped; } -// Intersect ray with a bvh. -shape_intersection intersect_points_bvh(const shape_bvh& bvh, - const vector& points, const vector& positions, - const vector& radius, const ray3f& ray, bool find_any) { - auto intersection = shape_intersection{}; - intersection.hit = intersect_elements_bvh( - bvh, - [&points, &positions, &radius]( - int idx, const ray3f& ray, vec2f& uv, float& distance) { - auto& p = points[idx]; - return intersect_point(ray, positions[p], radius[p], uv, distance); - }, - ray, intersection.element, intersection.uv, intersection.distance, - find_any); - return intersection; -} -shape_intersection intersect_lines_bvh(const shape_bvh& bvh, - const vector& lines, const vector& positions, - const vector& radius, const ray3f& ray, bool find_any) { - auto intersection = shape_intersection{}; - intersection.hit = intersect_elements_bvh( - bvh, - [&lines, &positions, &radius]( - int idx, const ray3f& ray, vec2f& uv, float& distance) { - auto& l = lines[idx]; - return intersect_line(ray, positions[l.x], positions[l.y], radius[l.x], - radius[l.y], uv, distance); - }, - ray, intersection.element, intersection.uv, intersection.distance, - find_any); - return intersection; -} -shape_intersection intersect_triangles_bvh(const shape_bvh& bvh, - const vector& triangles, const vector& positions, - const ray3f& ray, bool find_any) { - auto intersection = shape_intersection{}; - intersection.hit = intersect_elements_bvh( - bvh, - [&triangles, &positions]( - int idx, const ray3f& ray, vec2f& uv, float& distance) { - auto& t = triangles[idx]; - return intersect_triangle( - ray, positions[t.x], positions[t.y], positions[t.z], uv, distance); - }, - ray, intersection.element, intersection.uv, intersection.distance, - find_any); - return intersection; -} -shape_intersection intersect_quads_bvh(const shape_bvh& bvh, - const vector& quads, const vector& positions, - const ray3f& ray, bool find_any) { - auto intersection = shape_intersection{}; - intersection.hit = intersect_elements_bvh( - bvh, - [&quads, &positions]( - int idx, const ray3f& ray, vec2f& uv, float& distance) { - auto& t = quads[idx]; - return intersect_quad(ray, positions[t.x], positions[t.y], - positions[t.z], positions[t.w], uv, distance); - }, - ray, intersection.element, intersection.uv, intersection.distance, - find_any); - return intersection; -} - -// Intersect ray with a bvh. -template -static bool overlap_elements_bvh(const shape_bvh& bvh, - Overlap&& overlap_element, const vec3f& pos, float max_distance, - int& element, vec2f& uv, float& distance, bool find_any) { - // check if empty - if (bvh.nodes.empty()) return false; - - // node stack - auto node_stack = array{}; - auto node_cur = 0; - node_stack[node_cur++] = 0; - - // hit - auto hit = false; - - // walking stack - while (node_cur) { - // grab node - auto& node = bvh.nodes[node_stack[--node_cur]]; - - // intersect bbox - if (!overlap_bbox(pos, max_distance, node.bbox)) continue; - - // intersect node, switching based on node type - // for each type, iterate over the the primitive list - if (node.internal) { - // internal node - node_stack[node_cur++] = node.start + 0; - node_stack[node_cur++] = node.start + 1; - } else { - for (auto idx = 0; idx < node.num; idx++) { - auto primitive = bvh.primitives[node.start + idx]; - if (overlap_element(primitive, pos, max_distance, uv, distance)) { - hit = true; - element = primitive; - max_distance = distance; - } - } - } - - // check for early exit - if (find_any && hit) return hit; +// Align vertex positions. Alignment is 0: none, 1: min, 2: max, 3: center. +vector align_vertices( + const vector& positions, const vec3i& alignment) { + auto bounds = invalidb3f; + for (auto& p : positions) bounds = merge(bounds, p); + auto offset = vec3f{0, 0, 0}; + switch (alignment.x) { + case 1: offset.x = bounds.min.x; break; + case 2: offset.x = (bounds.min.x + bounds.max.x) / 2; break; + case 3: offset.x = bounds.max.x; break; } - - return hit; -} - -// Find a shape element that overlaps a point within a given distance -// max distance, returning either the closest or any overlap depending on -// `find_any`. Returns the point distance, the instance id, the shape element -// index and the element barycentric coordinates. -shape_intersection overlap_points_bvh(const shape_bvh& bvh, - const vector& points, const vector& positions, - const vector& radius, const vec3f& pos, float max_distance, - bool find_any) { - auto intersection = shape_intersection{}; - intersection.hit = overlap_elements_bvh( - bvh, - [&points, &positions, &radius](int idx, const vec3f& pos, - float max_distance, vec2f& uv, float& distance) { - auto& p = points[idx]; - return overlap_point( - pos, max_distance, positions[p], radius[p], uv, distance); - }, - pos, max_distance, intersection.element, intersection.uv, - intersection.distance, find_any); - return intersection; -} -shape_intersection overlap_lines_bvh(const shape_bvh& bvh, - const vector& lines, const vector& positions, - const vector& radius, const vec3f& pos, float max_distance, - bool find_any) { - auto intersection = shape_intersection{}; - intersection.hit = overlap_elements_bvh( - bvh, - [&lines, &positions, &radius](int idx, const vec3f& pos, - float max_distance, vec2f& uv, float& distance) { - auto& l = lines[idx]; - return overlap_line(pos, max_distance, positions[l.x], positions[l.y], - radius[l.x], radius[l.y], uv, distance); - }, - pos, max_distance, intersection.element, intersection.uv, - intersection.distance, find_any); - return intersection; -} -shape_intersection overlap_triangles_bvh(const shape_bvh& bvh, - const vector& triangles, const vector& positions, - const vector& radius, const vec3f& pos, float max_distance, - bool find_any) { - auto intersection = shape_intersection{}; - intersection.hit = overlap_elements_bvh( - bvh, - [&triangles, &positions, &radius](int idx, const vec3f& pos, - float max_distance, vec2f& uv, float& distance) { - auto& t = triangles[idx]; - return overlap_triangle(pos, max_distance, positions[t.x], - positions[t.y], positions[t.z], radius[t.x], radius[t.y], - radius[t.z], uv, distance); - }, - pos, max_distance, intersection.element, intersection.uv, - intersection.distance, find_any); - return intersection; -} -shape_intersection overlap_quads_bvh(const shape_bvh& bvh, - const vector& quads, const vector& positions, - const vector& radius, const vec3f& pos, float max_distance, - bool find_any) { - auto intersection = shape_intersection{}; - intersection.hit = overlap_elements_bvh( - bvh, - [&quads, &positions, &radius](int idx, const vec3f& pos, - float max_distance, vec2f& uv, float& distance) { - auto& q = quads[idx]; - return overlap_quad(pos, max_distance, positions[q.x], positions[q.y], - positions[q.z], positions[q.w], radius[q.x], radius[q.y], - radius[q.z], radius[q.w], uv, distance); - }, - pos, max_distance, intersection.element, intersection.uv, - intersection.distance, find_any); - return intersection; + switch (alignment.y) { + case 1: offset.y = bounds.min.y; break; + case 2: offset.y = (bounds.min.y + bounds.max.y) / 2; break; + case 3: offset.y = bounds.max.y; break; + } + switch (alignment.z) { + case 1: offset.z = bounds.min.z; break; + case 2: offset.z = (bounds.min.z + bounds.max.z) / 2; break; + case 3: offset.z = bounds.max.z; break; + } + auto aligned = positions; + for (auto& p : aligned) p -= offset; + return aligned; } } // namespace yocto // ----------------------------------------------------------------------------- -// HASH GRID AND NEAREST NEIGHBORS +// EDGEA AND ADJACENCIES // ----------------------------------------------------------------------------- - namespace yocto { -// Gets the cell index -vec3i get_cell_index(const hash_grid& grid, const vec3f& position) { - auto scaledpos = position * grid.cell_inv_size; - return vec3i{(int)scaledpos.x, (int)scaledpos.y, (int)scaledpos.z}; +// Initialize an edge map with elements. +edge_map make_edge_map(const vector& triangles) { + auto emap = edge_map{}; + for (auto& t : triangles) { + insert_edge(emap, {t.x, t.y}); + insert_edge(emap, {t.y, t.z}); + insert_edge(emap, {t.z, t.x}); + } + return emap; } - -// Create a hash_grid -hash_grid make_hash_grid(float cell_size) { - auto grid = hash_grid{}; - grid.cell_size = cell_size; - grid.cell_inv_size = 1 / cell_size; - return grid; +edge_map make_edge_map(const vector& quads) { + auto emap = edge_map{}; + for (auto& q : quads) { + insert_edge(emap, {q.x, q.y}); + insert_edge(emap, {q.y, q.z}); + if (q.z != q.w) insert_edge(emap, {q.z, q.w}); + insert_edge(emap, {q.w, q.x}); + } + return emap; } -hash_grid make_hash_grid(const vector& positions, float cell_size) { - auto grid = hash_grid{}; - grid.cell_size = cell_size; - grid.cell_inv_size = 1 / cell_size; - for (auto& position : positions) insert_vertex(grid, position); - return grid; +void insert_edges(edge_map& emap, const vector& triangles) { + for (auto& t : triangles) { + insert_edge(emap, {t.x, t.y}); + insert_edge(emap, {t.y, t.z}); + insert_edge(emap, {t.z, t.x}); + } } -// Inserts a point into the grid -int insert_vertex(hash_grid& grid, const vec3f& position) { - auto vertex_id = (int)grid.positions.size(); - auto cell = get_cell_index(grid, position); - grid.cells[cell].push_back(vertex_id); - grid.positions.push_back(position); - return vertex_id; +void insert_edges(edge_map& emap, const vector& quads) { + for (auto& q : quads) { + insert_edge(emap, {q.x, q.y}); + insert_edge(emap, {q.y, q.z}); + if (q.z != q.w) insert_edge(emap, {q.z, q.w}); + insert_edge(emap, {q.w, q.x}); + } } -// Finds the nearest neighbors within a given radius -void find_neighbors(const hash_grid& grid, vector& neighbors, - const vec3f& position, float max_radius, int skip_id) { - auto cell = get_cell_index(grid, position); - auto cell_radius = (int)(max_radius * grid.cell_inv_size) + 1; - neighbors.clear(); - auto max_radius_squared = max_radius * max_radius; - for (auto k = -cell_radius; k <= cell_radius; k++) { - for (auto j = -cell_radius; j <= cell_radius; j++) { - for (auto i = -cell_radius; i <= cell_radius; i++) { - auto ncell = cell + vec3i{i, j, k}; - auto cell_iterator = grid.cells.find(ncell); - if (cell_iterator == grid.cells.end()) continue; - auto& ncell_vertices = cell_iterator->second; - for (auto vertex_id : ncell_vertices) { - if (distance_squared(grid.positions[vertex_id], position) > - max_radius_squared) - continue; - if (vertex_id == skip_id) continue; - neighbors.push_back(vertex_id); - } - } - } +// Insert an edge and return its index +int insert_edge(edge_map& emap, const vec2i& edge) { + auto es = edge.x < edge.y ? edge : vec2i{edge.y, edge.x}; + auto it = emap.edges.find(es); + if (it == emap.edges.end()) { + auto data = edge_map::edge_data{(int)emap.edges.size(), 1}; + emap.edges.insert(it, {es, data}); + return data.index; + } else { + auto& data = it->second; + data.nfaces += 1; + return data.index; } } -void find_neighbors(const hash_grid& grid, vector& neighbors, - const vec3f& position, float max_radius) { - find_neighbors(grid, neighbors, position, max_radius, -1); +// Get number of edges +int num_edges(const edge_map& emap) { return (int)emap.edges.size(); } +// Get the edge index +int edge_index(const edge_map& emap, const vec2i& edge) { + auto es = edge.x < edge.y ? edge : vec2i{edge.y, edge.x}; + auto iterator = emap.edges.find(es); + if (iterator == emap.edges.end()) return -1; + return iterator->second.index; } -void find_neighbors(const hash_grid& grid, vector& neighbors, int vertex, - float max_radius) { - find_neighbors(grid, neighbors, grid.positions[vertex], max_radius, vertex); +// Get a list of edges, boundary edges, boundary vertices +vector get_edges(const edge_map& emap) { + auto edges = vector(emap.edges.size()); + for (auto& [edge, data] : emap.edges) edges[data.index] = edge; + return edges; } - -} // namespace yocto - -// ----------------------------------------------------------------------------- -// IMPLEMENTATION OF SHAPE ELEMENT CONVERSION AND GROUPING -// ----------------------------------------------------------------------------- -namespace yocto { - -// Convert quads to triangles -vector quads_to_triangles(const vector& quads) { - auto triangles = vector{}; - triangles.reserve(quads.size() * 2); - for (auto& q : quads) { - triangles.push_back({q.x, q.y, q.w}); - if (q.z != q.w) triangles.push_back({q.z, q.w, q.y}); +vector get_boundary(const edge_map& emap) { + auto boundary = vector{}; + for (auto& [edge, data] : emap.edges) { + if (data.nfaces < 2) boundary.push_back(edge); } - return triangles; + return boundary; } - -// Convert triangles to quads by creating degenerate quads -vector triangles_to_quads(const vector& triangles) { - auto quads = vector{}; - quads.reserve(triangles.size()); - for (auto& t : triangles) quads.push_back({t.x, t.y, t.z, t.z}); - return quads; +vector get_edges(const vector& triangles) { + return get_edges(make_edge_map(triangles)); } - -// Convert beziers to lines using 3 lines for each bezier. -vector bezier_to_lines(const vector& beziers) { - auto lines = vector{}; - lines.reserve(beziers.size() * 3); - for (auto b : beziers) { - lines.push_back({b.x, b.y}); - lines.push_back({b.y, b.z}); - lines.push_back({b.z, b.w}); - } - return lines; +vector get_edges(const vector& quads) { + return get_edges(make_edge_map(quads)); +} +vector get_edges( + const vector& triangles, const vector& quads) { + auto edges = get_edges(triangles); + auto more_edges = get_edges(quads); + edges.insert(edges.end(), more_edges.begin(), more_edges.end()); + return edges; } -// Convert face varying data to single primitives. Returns the quads indices -// and filled vectors for pos, norm and texcoord. -void split_facevarying(vector& split_quads, - vector& split_positions, vector& split_normals, - vector& split_texcoords, const vector& quadspos, - const vector& quadsnorm, const vector& quadstexcoord, - const vector& positions, const vector& normals, - const vector& texcoords) { - // make faces unique - unordered_map vert_map; - split_quads.resize(quadspos.size()); - for (auto fid = 0; fid < quadspos.size(); fid++) { - for (auto c = 0; c < 4; c++) { - auto v = vec3i{ - (&quadspos[fid].x)[c], - (!quadsnorm.empty()) ? (&quadsnorm[fid].x)[c] : -1, - (!quadstexcoord.empty()) ? (&quadstexcoord[fid].x)[c] : -1, - }; - auto it = vert_map.find(v); - if (it == vert_map.end()) { - auto s = (int)vert_map.size(); - vert_map.insert(it, {v, s}); - (&split_quads[fid].x)[c] = s; +// Build adjacencies between faces (sorted counter-clockwise) +vector face_adjacencies(const vector& triangles) { + auto get_edge = [](const vec3i& triangle, int i) -> vec2i { + auto x = triangle[i], y = triangle[i < 2 ? i + 1 : 0]; + return x < y ? vec2i{x, y} : vec2i{y, x}; + }; + auto adjacencies = vector{triangles.size(), vec3i{-1, -1, -1}}; + auto edge_map = unordered_map(); + edge_map.reserve((size_t)(triangles.size() * 1.5)); + for (auto i = 0; i < (int)triangles.size(); ++i) { + for (auto k = 0; k < 3; ++k) { + auto edge = get_edge(triangles[i], k); + auto it = edge_map.find(edge); + if (it == edge_map.end()) { + edge_map.insert(it, {edge, i}); } else { - (&split_quads[fid].x)[c] = it->second; + auto neighbor = it->second; + adjacencies[i][k] = neighbor; + for (auto kk = 0; kk < 3; ++kk) { + auto edge2 = get_edge(triangles[neighbor], kk); + if (edge2 == edge) { + adjacencies[neighbor][kk] = i; + break; + } + } } } } + return adjacencies; +} - // fill vert data - split_positions.clear(); - if (!positions.empty()) { - split_positions.resize(vert_map.size()); - for (auto& [vert, index] : vert_map) { - split_positions[index] = positions[vert.x]; +// Build adjacencies between vertices (sorted counter-clockwise) +vector> vertex_adjacencies( + const vector& triangles, const vector& adjacencies) { + auto find_index = [](const vec3i& v, int x) { + if (v.x == x) return 0; + if (v.y == x) return 1; + if (v.z == x) return 2; + return -1; + }; + + // For each vertex, find any adjacent face. + auto num_vertices = 0; + auto face_from_vertex = vector(triangles.size() * 3, -1); + + for (auto i = 0; i < (int)triangles.size(); ++i) { + for (auto k = 0; k < 3; k++) { + face_from_vertex[triangles[i][k]] = i; + num_vertices = max(num_vertices, triangles[i][k]); } } - split_normals.clear(); - if (!normals.empty()) { - split_normals.resize(vert_map.size()); - for (auto& [vert, index] : vert_map) { - split_normals[index] = normals[vert.y]; + + // Init result. + auto result = vector>(num_vertices); + + // For each vertex, loop around it and build its adjacency. + for (auto i = 0; i < num_vertices; ++i) { + result[i].reserve(6); + auto first_face = face_from_vertex[i]; + if (first_face == -1) continue; + + auto face = first_face; + while (true) { + auto k = find_index(triangles[face], i); + k = k != 0 ? k - 1 : 2; + result[i].push_back(triangles[face][k]); + face = adjacencies[face][k]; + if (face == -1) break; + if (face == first_face) break; } } - split_texcoords.clear(); - if (!texcoords.empty()) { - split_texcoords.resize(vert_map.size()); - for (auto& [vert, index] : vert_map) { - split_texcoords[index] = texcoords[vert.z]; + + return result; +} + +// Build adjacencies between each vertex and its adjacent faces. +// Adjacencies are sorted counter-clockwise and have same starting points as +// vertex_adjacencies() +vector> vertex_to_faces_adjacencies( + const vector& triangles, const vector& adjacencies) { + auto find_index = [](const vec3i& v, int x) { + if (v.x == x) return 0; + if (v.y == x) return 1; + if (v.z == x) return 2; + return -1; + }; + + // For each vertex, find any adjacent face. + auto num_vertices = 0; + auto face_from_vertex = vector(triangles.size() * 3, -1); + + for (auto i = 0; i < (int)triangles.size(); ++i) { + for (auto k = 0; k < 3; k++) { + face_from_vertex[triangles[i][k]] = i; + num_vertices = max(num_vertices, triangles[i][k]); } } -} -// Weld vertices within a threshold. -pair, vector> weld_vertices( - const vector& positions, float threshold) { - auto indices = vector(positions.size()); - auto welded = vector{}; - auto grid = make_hash_grid(threshold); - auto neighbors = vector{}; - for (auto vertex = 0; vertex < positions.size(); vertex++) { - auto& position = positions[vertex]; - find_neighbors(grid, neighbors, position, threshold); - if (neighbors.empty()) { - welded.push_back(position); - indices[vertex] = (int)welded.size() - 1; - insert_vertex(grid, position); - } else { - indices[vertex] = neighbors.front(); + // Init result. + auto result = vector>(num_vertices); + + // For each vertex, loop around it and build its adjacency. + for (auto i = 0; i < num_vertices; ++i) { + result[i].reserve(6); + auto first_face = face_from_vertex[i]; + if (first_face == -1) continue; + + auto face = first_face; + while (true) { + auto k = find_index(triangles[face], i); + k = k != 0 ? k - 1 : 2; + face = adjacencies[face][k]; + result[i].push_back(face); + if (face == -1) break; + if (face == first_face) break; } } - return {welded, indices}; -} -pair, vector> weld_triangles( - const vector& triangles, const vector& positions, - float threshold) { - auto [wpositions, indices] = weld_vertices(positions, threshold); - auto wtriangles = triangles; - for (auto& t : wtriangles) t = {indices[t.x], indices[t.y], indices[t.z]}; - return {wtriangles, wpositions}; -} -pair, vector> weld_quads(const vector& quads, - const vector& positions, float threshold) { - auto [wpositions, indices] = weld_vertices(positions, threshold); - auto wquads = quads; - for (auto& q : wquads) - q = { - indices[q.x], - indices[q.y], - indices[q.z], - indices[q.w], - }; - return {wquads, wpositions}; -} -// Merge shape elements -void merge_lines(vector& lines, vector& positions, - vector& tangents, vector& texcoords, vector& radius, - const vector& merge_lines, const vector& merge_positions, - const vector& merge_tangents, - const vector& merge_texturecoords, - const vector& merge_radius) { - auto merge_verts = (int)positions.size(); - for (auto& l : merge_lines) - lines.push_back({l.x + merge_verts, l.y + merge_verts}); - positions.insert( - positions.end(), merge_positions.begin(), merge_positions.end()); - tangents.insert(tangents.end(), merge_tangents.begin(), merge_tangents.end()); - texcoords.insert( - texcoords.end(), merge_texturecoords.begin(), merge_texturecoords.end()); - radius.insert(radius.end(), merge_radius.begin(), merge_radius.end()); -} -void merge_triangles(vector& triangles, vector& positions, - vector& normals, vector& texcoords, - const vector& merge_triangles, const vector& merge_positions, - const vector& merge_normals, - const vector& merge_texturecoords) { - auto merge_verts = (int)positions.size(); - for (auto& t : merge_triangles) - triangles.push_back( - {t.x + merge_verts, t.y + merge_verts, t.z + merge_verts}); - positions.insert( - positions.end(), merge_positions.begin(), merge_positions.end()); - normals.insert(normals.end(), merge_normals.begin(), merge_normals.end()); - texcoords.insert( - texcoords.end(), merge_texturecoords.begin(), merge_texturecoords.end()); + return result; } -void merge_quads(vector& quads, vector& positions, - vector& normals, vector& texcoords, - const vector& merge_quads, const vector& merge_positions, - const vector& merge_normals, - const vector& merge_texturecoords) { - auto merge_verts = (int)positions.size(); - for (auto& q : merge_quads) - quads.push_back({q.x + merge_verts, q.y + merge_verts, q.z + merge_verts, - q.w + merge_verts}); - positions.insert( - positions.end(), merge_positions.begin(), merge_positions.end()); - normals.insert(normals.end(), merge_normals.begin(), merge_normals.end()); - texcoords.insert( - texcoords.end(), merge_texturecoords.begin(), merge_texturecoords.end()); + +// Compute boundaries as a list of loops (sorted counter-clockwise) +vector> ordered_boundaries(const vector& triangles, + const vector& adjacency, int num_vertices) { + // map every boundary vertex to its next one + auto next_vert = vector(num_vertices, -1); + for (auto i = 0; i < (int)triangles.size(); ++i) { + for (auto k = 0; k < 3; ++k) { + if (adjacency[i][k] == -1) + next_vert[triangles[i][k]] = triangles[i][(k + 1) % 3]; + } + } + + // result + auto boundaries = vector>(); + + // arrange boundary vertices in loops + for (auto i = 0; i < (int)next_vert.size(); i++) { + if (next_vert[i] == -1) continue; + + // add new empty boundary + boundaries.emplace_back(); + auto current = i; + + while (true) { + auto next = next_vert[current]; + if (next == -1) { + return {}; + } + next_vert[current] = -1; + boundaries.back().push_back(current); + + // close loop if necessary + if (next == i) + break; + else + current = next; + } + } + + return boundaries; } } // namespace yocto // ----------------------------------------------------------------------------- -// IMPLEMENTATION OF SHAPE SUBDIVISION +// IMPLEMENTATION FOR BVH // ----------------------------------------------------------------------------- namespace yocto { -// Subdivide lines. -template -static pair, vector> subdivide_lines_impl( - const vector& lines, const vector& vertices) { - // early exit - if (lines.empty() || vertices.empty()) return {lines, vertices}; - // create vertices - auto tvertices = vector{}; - tvertices.reserve(vertices.size() + lines.size()); - for (auto& vertex : vertices) tvertices.push_back(vertex); - for (auto& line : lines) { - tvertices.push_back((vertices[line.x] + vertices[line.y]) / 2); - } - // create lines - auto tlines = vector{}; - tlines.reserve(lines.size() * 2); - auto line_vertex = [nverts = (int)vertices.size()]( - size_t line_id) { return nverts + (int)line_id; }; - for (auto&& [line_id, line] : enumerate(lines)) { - tlines.push_back({line.x, line_vertex(line_id)}); - tlines.push_back({line_vertex(line_id), line.y}); - } - // done - return {tlines, tvertices}; -} +// Splits a BVH node using the middle heuristic. Returns split position and +// axis. +static pair split_middle(vector& primitives, + const vector& bboxes, const vector& centers, int start, + int end) { + // initialize split axis and position + auto axis = 0; + auto mid = (start + end) / 2; -// Subdivide triangle. -template -static pair, vector> subdivide_triangles_impl( - const vector& triangles, const vector& vertices) { - // early exit - if (triangles.empty() || vertices.empty()) return {triangles, vertices}; - // get edges - auto emap = make_edge_map(triangles); - auto edges = get_edges(emap); - // create vertices - auto tvertices = vector{}; - tvertices.reserve(vertices.size() + edges.size()); - for (auto& vertex : vertices) tvertices.push_back(vertex); - for (auto& edge : edges) - tvertices.push_back((vertices[edge.x] + vertices[edge.y]) / 2); - // create triangles - auto ttriangles = vector{}; - ttriangles.reserve(triangles.size() * 4); - auto edge_vertex = [&emap, nverts = (int)vertices.size()](const vec2i& edge) { - return nverts + edge_index(emap, edge); - }; - for (auto& triangle : triangles) { - ttriangles.push_back({triangle.x, edge_vertex({triangle.x, triangle.y}), - edge_vertex({triangle.z, triangle.x})}); - ttriangles.push_back({triangle.y, edge_vertex({triangle.y, triangle.z}), - edge_vertex({triangle.x, triangle.y})}); - ttriangles.push_back({triangle.z, edge_vertex({triangle.z, triangle.x}), - edge_vertex({triangle.y, triangle.z})}); - ttriangles.push_back({edge_vertex({triangle.x, triangle.y}), - edge_vertex({triangle.y, triangle.z}), - edge_vertex({triangle.z, triangle.x})}); + // compute primintive bounds and size + auto cbbox = invalidb3f; + for (auto i = start; i < end; i++) + cbbox = merge(cbbox, centers[primitives[i]]); + auto csize = cbbox.max - cbbox.min; + if (csize == vec3f{0, 0, 0}) return {mid, axis}; + + // split along largest + if (csize.x >= csize.y && csize.x >= csize.z) axis = 0; + if (csize.y >= csize.x && csize.y >= csize.z) axis = 1; + if (csize.z >= csize.x && csize.z >= csize.y) axis = 2; + + // split the space in the middle along the largest axis + auto cmiddle = (cbbox.max + cbbox.min) / 2; + auto middle = cmiddle[axis]; + mid = (int)(std::partition(primitives.data() + start, primitives.data() + end, + [axis, middle, ¢ers]( + auto a) { return centers[a][axis] < middle; }) - + primitives.data()); + + // if we were not able to split, just break the primitives in half + if (mid == start || mid == end) { + axis = 0; + mid = (start + end) / 2; + // throw std::runtime_error("bad bvh split"); } - // done - return {ttriangles, tvertices}; + + return {mid, axis}; } -// Subdivide quads. -template -static pair, vector> subdivide_quads_impl( - const vector& quads, const vector& vertices) { - // early exit - if (quads.empty() || vertices.empty()) return {quads, vertices}; - // get edges - auto emap = make_edge_map(quads); - auto edges = get_edges(emap); - // create vertices - auto tvertices = vector{}; - tvertices.reserve(vertices.size() + edges.size() + quads.size()); - for (auto& vertex : vertices) tvertices.push_back(vertex); - for (auto& edge : edges) - tvertices.push_back((vertices[edge.x] + vertices[edge.y]) / 2); - for (auto& quad : quads) { - if (quad.z != quad.w) { - tvertices.push_back((vertices[quad.x] + vertices[quad.y] + - vertices[quad.z] + vertices[quad.w]) / - 4); - } else { - tvertices.push_back( - (vertices[quad.x] + vertices[quad.y] + vertices[quad.z]) / 3); - } - } - // create quads - auto tquads = vector{}; - tquads.reserve(quads.size() * 4); - auto edge_vertex = [&emap, nverts = (int)vertices.size()](const vec2i& edge) { - return nverts + edge_index(emap, edge); - }; - auto quad_vertex = [nverts = (int)vertices.size(), - nedges = (int)edges.size()](size_t quad_id) { - return nverts + nedges + (int)quad_id; - }; - for (auto&& [quad_id, quad] : enumerate(quads)) { - if (quad.z != quad.w) { - tquads.push_back({quad.x, edge_vertex({quad.x, quad.y}), - quad_vertex(quad_id), edge_vertex({quad.w, quad.x})}); - tquads.push_back({quad.y, edge_vertex({quad.y, quad.z}), - quad_vertex(quad_id), edge_vertex({quad.x, quad.y})}); - tquads.push_back({quad.z, edge_vertex({quad.z, quad.w}), - quad_vertex(quad_id), edge_vertex({quad.y, quad.z})}); - tquads.push_back({quad.w, edge_vertex({quad.w, quad.x}), - quad_vertex(quad_id), edge_vertex({quad.z, quad.w})}); - } else { - tquads.push_back({quad.x, edge_vertex({quad.x, quad.y}), - quad_vertex(quad_id), edge_vertex({quad.z, quad.x})}); - tquads.push_back({quad.y, edge_vertex({quad.y, quad.z}), - quad_vertex(quad_id), edge_vertex({quad.x, quad.y})}); - tquads.push_back({quad.z, edge_vertex({quad.z, quad.x}), - quad_vertex(quad_id), edge_vertex({quad.y, quad.z})}); - } - } - // done - return {tquads, tvertices}; -} +// Maximum number of primitives per BVH node. +const int bvh_max_prims = 4; -// Subdivide beziers. -template -static pair, vector> subdivide_beziers_impl( - const vector& beziers, const vector& vertices) { - // early exit - if (beziers.empty() || vertices.empty()) return {beziers, vertices}; - // get edges - auto vmap = unordered_map(); - auto tvertices = vector(); - auto tbeziers = vector(); - for (auto& bezier : beziers) { - if (vmap.find(bezier.x) == vmap.end()) { - vmap[bezier.x] = (int)tvertices.size(); - tvertices.push_back(vertices[bezier.x]); - } - if (vmap.find(bezier.w) == vmap.end()) { - vmap[bezier.w] = (int)tvertices.size(); - tvertices.push_back(vertices[bezier.w]); - } - auto bo = (int)tvertices.size(); - tbeziers.push_back({vmap.at(bezier.x), bo + 0, bo + 1, bo + 2}); - tbeziers.push_back({bo + 2, bo + 3, bo + 4, vmap.at(bezier.w)}); - tvertices.push_back(vertices[bezier.x] / 2 + vertices[bezier.y] / 2); - tvertices.push_back(vertices[bezier.x] / 4 + vertices[bezier.y] / 2 + - vertices[bezier.z] / 4); - tvertices.push_back( - vertices[bezier.x] / 8 + vertices[bezier.y] * ((float)3 / (float)8) + - vertices[bezier.z] * ((float)3 / (float)8) + vertices[bezier.w] / 8); - tvertices.push_back(vertices[bezier.y] / 4 + vertices[bezier.z] / 2 + - vertices[bezier.w] / 4); - tvertices.push_back(vertices[bezier.z] / 2 + vertices[bezier.w] / 2); - } +// Build BVH nodes +static void build_bvh(shape_bvh& bvh, vector& bboxes) { + // get values + auto& nodes = bvh.nodes; + auto& primitives = bvh.primitives; - // done - return {tbeziers, tvertices}; -} + // prepare to build nodes + nodes.clear(); + nodes.reserve(bboxes.size() * 2); -// Subdivide catmullclark. -template -static pair, vector> subdivide_catmullclark_impl( - const vector& quads, const vector& vertices, bool lock_boundary) { - // early exit - if (quads.empty() || vertices.empty()) return {quads, vertices}; - // get edges - auto emap = make_edge_map(quads); - auto edges = get_edges(emap); - auto boundary = get_boundary(emap); + // prepare primitives + bvh.primitives.resize(bboxes.size()); + for (auto idx = 0; idx < bboxes.size(); idx++) bvh.primitives[idx] = idx; - // split elements ------------------------------------ - // create vertices - auto tvertices = vector{}; - tvertices.reserve(vertices.size() + edges.size() + quads.size()); - for (auto& vertex : vertices) tvertices.push_back(vertex); - for (auto& edge : edges) - tvertices.push_back((vertices[edge.x] + vertices[edge.y]) / 2); - for (auto& quad : quads) { - if (quad.z != quad.w) { - tvertices.push_back((vertices[quad.x] + vertices[quad.y] + - vertices[quad.z] + vertices[quad.w]) / - 4); - } else { - tvertices.push_back( - (vertices[quad.x] + vertices[quad.y] + vertices[quad.z]) / 3); - } - } - // create quads - auto tquads = vector{}; - tquads.reserve(quads.size() * 4); - auto edge_vertex = [&emap, nverts = (int)vertices.size()](const vec2i& edge) { - return nverts + edge_index(emap, edge); - }; - auto quad_vertex = [nverts = (int)vertices.size(), - nedges = (int)edges.size()](size_t quad_id) { - return nverts + nedges + (int)quad_id; - }; - for (auto&& [quad_id, quad] : enumerate(quads)) { - if (quad.z != quad.w) { - tquads.push_back({quad.x, edge_vertex({quad.x, quad.y}), - quad_vertex(quad_id), edge_vertex({quad.w, quad.x})}); - tquads.push_back({quad.y, edge_vertex({quad.y, quad.z}), - quad_vertex(quad_id), edge_vertex({quad.x, quad.y})}); - tquads.push_back({quad.z, edge_vertex({quad.z, quad.w}), - quad_vertex(quad_id), edge_vertex({quad.y, quad.z})}); - tquads.push_back({quad.w, edge_vertex({quad.w, quad.x}), - quad_vertex(quad_id), edge_vertex({quad.z, quad.w})}); - } else { - tquads.push_back({quad.x, edge_vertex({quad.x, quad.y}), - quad_vertex(quad_id), edge_vertex({quad.z, quad.x})}); - tquads.push_back({quad.y, edge_vertex({quad.y, quad.z}), - quad_vertex(quad_id), edge_vertex({quad.x, quad.y})}); - tquads.push_back({quad.z, edge_vertex({quad.z, quad.x}), - quad_vertex(quad_id), edge_vertex({quad.y, quad.z})}); - } - } + // prepare centers + auto centers = vector(bboxes.size()); + for (auto idx = 0; idx < bboxes.size(); idx++) + centers[idx] = center(bboxes[idx]); - // split boundary - auto tboundary = vector{}; - tboundary.reserve(boundary.size()); - for (auto& edge : boundary) { - tboundary.push_back({edge.x, edge_vertex(edge)}); - tboundary.push_back({edge_vertex(edge), edge.y}); - } + // queue up first node + auto queue = deque{{0, 0, (int)bboxes.size()}}; + nodes.emplace_back(); - // setup creases ----------------------------------- - auto tcrease_edges = vector{}; - auto tcrease_verts = vector{}; - if (lock_boundary) { - for (auto& b : tboundary) { - tcrease_verts.push_back(b.x); - tcrease_verts.push_back(b.y); - } - } else { - for (auto& b : tboundary) tcrease_edges.push_back(b); - } + // create nodes until the queue is empty + while (!queue.empty()) { + // grab node to work on + auto next = queue.front(); + queue.pop_front(); + auto nodeid = next.x, start = next.y, end = next.z; - // define vertices valence --------------------------- - auto tvert_val = vector(tvertices.size(), 2); - for (auto& edge : tboundary) { - tvert_val[edge.x] = (lock_boundary) ? 0 : 1; - tvert_val[edge.y] = (lock_boundary) ? 0 : 1; - } + // grab node + auto& node = nodes[nodeid]; - // averaging pass ---------------------------------- - auto avert = vector(tvertices.size(), T()); - auto acount = vector(tvertices.size(), 0); - for (auto& point : tcrease_verts) { - if (tvert_val[point] != 0) continue; - avert[point] += tvertices[point]; - acount[point] += 1; - } - for (auto& edge : tcrease_edges) { - auto centroid = (tvertices[edge.x] + tvertices[edge.y]) / 2; - for (auto vid : {edge.x, edge.y}) { - if (tvert_val[vid] != 1) continue; - avert[vid] += centroid; - acount[vid] += 1; + // compute bounds + node.bbox = invalidb3f; + for (auto i = start; i < end; i++) + node.bbox = merge(node.bbox, bboxes[primitives[i]]); + + // split into two children + if (end - start > bvh_max_prims) { + // get split + auto [mid, axis] = split_middle(primitives, bboxes, centers, start, end); + + // make an internal node + node.internal = true; + node.axis = (int8_t)axis; + node.num = 2; + node.start = (int)nodes.size(); + nodes.emplace_back(); + nodes.emplace_back(); + queue.push_back({node.start + 0, start, mid}); + queue.push_back({node.start + 1, mid, end}); + } else { + // Make a leaf node + node.internal = false; + node.num = (int16_t)(end - start); + node.start = start; } } - for (auto& quad : tquads) { - auto centroid = (tvertices[quad.x] + tvertices[quad.y] + tvertices[quad.z] + - tvertices[quad.w]) / - 4; - for (auto vid : {quad.x, quad.y, quad.z, quad.w}) { - if (tvert_val[vid] != 2) continue; - avert[vid] += centroid; - acount[vid] += 1; + + // cleanup + nodes.shrink_to_fit(); +} + +// Update bvh +static void update_bvh(shape_bvh& bvh, const vector& bboxes) { + for (auto nodeid = (int)bvh.nodes.size() - 1; nodeid >= 0; nodeid--) { + auto& node = bvh.nodes[nodeid]; + node.bbox = invalidb3f; + if (node.internal) { + for (auto idx = 0; idx < 2; idx++) { + node.bbox = merge(node.bbox, bvh.nodes[node.start + idx].bbox); + } + } else { + for (auto idx = 0; idx < node.num; idx++) { + node.bbox = merge(node.bbox, bboxes[bvh.primitives[node.start + idx]]); + } } } - for (auto i = 0; i < (int)tvertices.size(); i++) avert[i] /= (float)acount[i]; +} - // correction pass ---------------------------------- - // p = p + (avg_p - p) * (4/avg_count) - for (auto i = 0; i < (int)tvertices.size(); i++) { - if (tvert_val[i] != 2) continue; - avert[i] = tvertices[i] + - (avert[i] - tvertices[i]) * (4 / (float)acount[i]); +// Build shape bvh +shape_bvh make_points_bvh(const vector& points, + const vector& positions, const vector& radius) { + // build primitives + auto bboxes = vector(points.size()); + for (auto idx = 0; idx < bboxes.size(); idx++) { + auto& p = points[idx]; + bboxes[idx] = point_bounds(positions[p], radius[p]); } - tvertices = avert; - // done - return {tquads, tvertices}; + // build nodes + auto bvh = shape_bvh{}; + build_bvh(bvh, bboxes); + return bvh; } +shape_bvh make_lines_bvh(const vector& lines, + const vector& positions, const vector& radius) { + // build primitives + auto bboxes = vector(lines.size()); + for (auto idx = 0; idx < bboxes.size(); idx++) { + auto& l = lines[idx]; + bboxes[idx] = line_bounds( + positions[l.x], positions[l.y], radius[l.x], radius[l.y]); + } -pair, vector> subdivide_lines( - const vector& lines, const vector& vertices) { - return subdivide_lines_impl(lines, vertices); -} -pair, vector> subdivide_lines( - const vector& lines, const vector& vertices) { - return subdivide_lines_impl(lines, vertices); -} -pair, vector> subdivide_lines( - const vector& lines, const vector& vertices) { - return subdivide_lines_impl(lines, vertices); -} -pair, vector> subdivide_lines( - const vector& lines, const vector& vertices) { - return subdivide_lines_impl(lines, vertices); -} - -pair, vector> subdivide_triangles( - const vector& triangles, const vector& vertices) { - return subdivide_triangles_impl(triangles, vertices); -} -pair, vector> subdivide_triangles( - const vector& triangles, const vector& vertices) { - return subdivide_triangles_impl(triangles, vertices); -} -pair, vector> subdivide_triangles( - const vector& triangles, const vector& vertices) { - return subdivide_triangles_impl(triangles, vertices); -} -pair, vector> subdivide_triangles( - const vector& triangles, const vector& vertices) { - return subdivide_triangles_impl(triangles, vertices); -} - -pair, vector> subdivide_quads( - const vector& quads, const vector& vertices) { - return subdivide_quads_impl(quads, vertices); -} -pair, vector> subdivide_quads( - const vector& quads, const vector& vertices) { - return subdivide_quads_impl(quads, vertices); -} -pair, vector> subdivide_quads( - const vector& quads, const vector& vertices) { - return subdivide_quads_impl(quads, vertices); -} -pair, vector> subdivide_quads( - const vector& quads, const vector& vertices) { - return subdivide_quads_impl(quads, vertices); -} - -pair, vector> subdivide_beziers( - const vector& beziers, const vector& vertices) { - return subdivide_beziers_impl(beziers, vertices); -} -pair, vector> subdivide_beziers( - const vector& beziers, const vector& vertices) { - return subdivide_beziers_impl(beziers, vertices); -} -pair, vector> subdivide_beziers( - const vector& beziers, const vector& vertices) { - return subdivide_beziers_impl(beziers, vertices); -} -pair, vector> subdivide_beziers( - const vector& beziers, const vector& vertices) { - return subdivide_beziers_impl(beziers, vertices); + // build nodes + auto bvh = shape_bvh{}; + build_bvh(bvh, bboxes); + return bvh; } +shape_bvh make_triangles_bvh(const vector& triangles, + const vector& positions, const vector& radius) { + // build primitives + auto bboxes = vector(triangles.size()); + for (auto idx = 0; idx < bboxes.size(); idx++) { + auto& t = triangles[idx]; + bboxes[idx] = triangle_bounds( + positions[t.x], positions[t.y], positions[t.z]); + } -pair, vector> subdivide_catmullclark( - const vector& quads, const vector& vertices, - bool lock_boundary) { - return subdivide_catmullclark_impl(quads, vertices, lock_boundary); -} -pair, vector> subdivide_catmullclark( - const vector& quads, const vector& vertices, - bool lock_boundary) { - return subdivide_catmullclark_impl(quads, vertices, lock_boundary); -} -pair, vector> subdivide_catmullclark( - const vector& quads, const vector& vertices, - bool lock_boundary) { - return subdivide_catmullclark_impl(quads, vertices, lock_boundary); -} -pair, vector> subdivide_catmullclark( - const vector& quads, const vector& vertices, - bool lock_boundary) { - return subdivide_catmullclark_impl(quads, vertices, lock_boundary); + // build nodes + auto bvh = shape_bvh{}; + build_bvh(bvh, bboxes); + return bvh; } +shape_bvh make_quads_bvh(const vector& quads, + const vector& positions, const vector& radius) { + // build primitives + auto bboxes = vector(quads.size()); + for (auto idx = 0; idx < bboxes.size(); idx++) { + auto& q = quads[idx]; + bboxes[idx] = quad_bounds( + positions[q.x], positions[q.y], positions[q.z], positions[q.w]); + } -} // namespace yocto - -// ----------------------------------------------------------------------------- -// IMPLEMENTATION OF SHAPE SAMPLING -// ----------------------------------------------------------------------------- -namespace yocto { - -// Pick a point in a point set uniformly. -int sample_points(int npoints, float re) { return sample_uniform(npoints, re); } -int sample_points(const vector& cdf, float re) { - return sample_discrete(cdf, re); -} -vector sample_points_cdf(int npoints) { - auto cdf = vector(npoints); - for (auto i = 0; i < cdf.size(); i++) cdf[i] = 1 + (i != 0 ? cdf[i - 1] : 0); - return cdf; -} -void sample_points_cdf(vector& cdf, int npoints) { - for (auto i = 0; i < cdf.size(); i++) cdf[i] = 1 + (i != 0 ? cdf[i - 1] : 0); + // build nodes + auto bvh = shape_bvh{}; + build_bvh(bvh, bboxes); + return bvh; } -// Pick a point on lines uniformly. -pair sample_lines(const vector& cdf, float re, float ru) { - return {sample_discrete(cdf, re), ru}; -} -vector sample_lines_cdf( - const vector& lines, const vector& positions) { - auto cdf = vector(lines.size()); - for (auto i = 0; i < cdf.size(); i++) { - auto& l = lines[i]; - auto w = line_length(positions[l.x], positions[l.y]); - cdf[i] = w + (i != 0 ? cdf[i - 1] : 0); - } - return cdf; -} -void sample_lines_cdf(vector& cdf, const vector& lines, - const vector& positions) { - for (auto i = 0; i < cdf.size(); i++) { - auto& l = lines[i]; - auto w = line_length(positions[l.x], positions[l.y]); - cdf[i] = w + (i != 0 ? cdf[i - 1] : 0); +void update_points_bvh(shape_bvh& bvh, const vector& points, + const vector& positions, const vector& radius) { + // build primitives + auto bboxes = vector(points.size()); + for (auto idx = 0; idx < bboxes.size(); idx++) { + auto& p = points[idx]; + bboxes[idx] = point_bounds(positions[p], radius[p]); } -} -// Pick a point on a triangle mesh uniformly. -pair sample_triangles( - const vector& cdf, float re, const vec2f& ruv) { - return {sample_discrete(cdf, re), sample_triangle(ruv)}; + // update nodes + update_bvh(bvh, bboxes); } -vector sample_triangles_cdf( - const vector& triangles, const vector& positions) { - auto cdf = vector(triangles.size()); - for (auto i = 0; i < cdf.size(); i++) { - auto& t = triangles[i]; - auto w = triangle_area(positions[t.x], positions[t.y], positions[t.z]); - cdf[i] = w + (i != 0 ? cdf[i - 1] : 0); +void update_lines_bvh(shape_bvh& bvh, const vector& lines, + const vector& positions, const vector& radius) { + // build primitives + auto bboxes = vector(lines.size()); + for (auto idx = 0; idx < bboxes.size(); idx++) { + auto& l = lines[idx]; + bboxes[idx] = line_bounds( + positions[l.x], positions[l.y], radius[l.x], radius[l.y]); } - return cdf; + + // update nodes + update_bvh(bvh, bboxes); } -void sample_triangles_cdf(vector& cdf, const vector& triangles, +void update_triangles_bvh(shape_bvh& bvh, const vector& triangles, const vector& positions) { - for (auto i = 0; i < cdf.size(); i++) { - auto& t = triangles[i]; - auto w = triangle_area(positions[t.x], positions[t.y], positions[t.z]); - cdf[i] = w + (i != 0 ? cdf[i - 1] : 0); + // build primitives + auto bboxes = vector(triangles.size()); + for (auto idx = 0; idx < bboxes.size(); idx++) { + auto& t = triangles[idx]; + bboxes[idx] = triangle_bounds( + positions[t.x], positions[t.y], positions[t.z]); } -} -// Pick a point on a quad mesh uniformly. -pair sample_quads( - const vector& cdf, float re, const vec2f& ruv) { - return {sample_discrete(cdf, re), ruv}; -} -pair sample_quads(const vector& quads, - const vector& cdf, float re, const vec2f& ruv) { - auto element = sample_discrete(cdf, re); - if (quads[element].z == quads[element].w) { - return {element, sample_triangle(ruv)}; - } else { - return {element, ruv}; - } -} -vector sample_quads_cdf( - const vector& quads, const vector& positions) { - auto cdf = vector(quads.size()); - for (auto i = 0; i < cdf.size(); i++) { - auto& q = quads[i]; - auto w = quad_area( - positions[q.x], positions[q.y], positions[q.z], positions[q.w]); - cdf[i] = w + (i ? cdf[i - 1] : 0); - } - return cdf; + // update nodes + update_bvh(bvh, bboxes); } -void sample_quads_cdf(vector& cdf, const vector& quads, +void update_quads_bvh(shape_bvh& bvh, const vector& quads, const vector& positions) { - for (auto i = 0; i < cdf.size(); i++) { - auto& q = quads[i]; - auto w = quad_area( + // build primitives + auto bboxes = vector(quads.size()); + for (auto idx = 0; idx < bboxes.size(); idx++) { + auto& q = quads[idx]; + bboxes[idx] = quad_bounds( positions[q.x], positions[q.y], positions[q.z], positions[q.w]); - cdf[i] = w + (i ? cdf[i - 1] : 0); } + + // update nodes + update_bvh(bvh, bboxes); } -// Samples a set of points over a triangle mesh uniformly. The rng function -// takes the point index and returns vec3f numbers uniform directibuted in -// [0,1]^3. unorm and texcoord are optional. -void sample_triangles(vector& sampled_positions, - vector& sampled_normals, vector& sampled_texcoords, - const vector& triangles, const vector& positions, - const vector& normals, const vector& texcoords, int npoints, - int seed) { - sampled_positions.resize(npoints); - sampled_normals.resize(npoints); - sampled_texcoords.resize(npoints); - auto cdf = sample_triangles_cdf(triangles, positions); - auto rng = make_rng(seed); - for (auto i = 0; i < npoints; i++) { - auto sample = sample_triangles(cdf, rand1f(rng), rand2f(rng)); - auto& t = triangles[sample.first]; - auto uv = sample.second; - sampled_positions[i] = interpolate_triangle( - positions[t.x], positions[t.y], positions[t.z], uv); - if (!sampled_normals.empty()) { - sampled_normals[i] = normalize( - interpolate_triangle(normals[t.x], normals[t.y], normals[t.z], uv)); - } else { - sampled_normals[i] = triangle_normal( - positions[t.x], positions[t.y], positions[t.z]); - } - if (!sampled_texcoords.empty()) { - sampled_texcoords[i] = interpolate_triangle( - texcoords[t.x], texcoords[t.y], texcoords[t.z], uv); +// Intersect ray with a bvh. +template +static bool intersect_elements_bvh(const shape_bvh& bvh, + Intersect&& intersect_element, const ray3f& ray_, int& element, vec2f& uv, + float& distance, bool find_any) { + // check empty + if (bvh.nodes.empty()) return false; + + // node stack + auto node_stack = array{}; + auto node_cur = 0; + node_stack[node_cur++] = 0; + + // shared variables + auto hit = false; + + // copy ray to modify it + auto ray = ray_; + + // prepare ray for fast queries + auto ray_dinv = vec3f{1 / ray.d.x, 1 / ray.d.y, 1 / ray.d.z}; + auto ray_dsign = vec3i{(ray_dinv.x < 0) ? 1 : 0, (ray_dinv.y < 0) ? 1 : 0, + (ray_dinv.z < 0) ? 1 : 0}; + + // walking stack + while (node_cur) { + // grab node + auto& node = bvh.nodes[node_stack[--node_cur]]; + + // intersect bbox + // if (!intersect_bbox(ray, ray_dinv, ray_dsign, node.bbox)) continue; + if (!intersect_bbox(ray, ray_dinv, node.bbox)) continue; + + // intersect node, switching based on node type + // for each type, iterate over the the primitive list + if (node.internal) { + // for internal nodes, attempts to proceed along the + // split axis from smallest to largest nodes + if (ray_dsign[node.axis]) { + node_stack[node_cur++] = node.start + 0; + node_stack[node_cur++] = node.start + 1; + } else { + node_stack[node_cur++] = node.start + 1; + node_stack[node_cur++] = node.start + 0; + } } else { - sampled_texcoords[i] = zero2f; + for (auto idx = 0; idx < node.num; idx++) { + auto primitive = bvh.primitives[node.start + idx]; + if (intersect_element(primitive, ray, uv, distance)) { + hit = true; + element = primitive; + ray.tmax = distance; + } + } } + + // check for early exit + if (find_any && hit) return hit; } + + return hit; } -// Samples a set of points over a triangle mesh uniformly. The rng function -// takes the point index and returns vec3f numbers uniform directibuted in -// [0,1]^3. unorm and texcoord are optional. -void sample_quads(vector& sampled_positions, - vector& sampled_normals, vector& sampled_texcoords, +// Intersect ray with a bvh. +shape_intersection intersect_points_bvh(const shape_bvh& bvh, + const vector& points, const vector& positions, + const vector& radius, const ray3f& ray, bool find_any) { + auto intersection = shape_intersection{}; + intersection.hit = intersect_elements_bvh( + bvh, + [&points, &positions, &radius]( + int idx, const ray3f& ray, vec2f& uv, float& distance) { + auto& p = points[idx]; + return intersect_point(ray, positions[p], radius[p], uv, distance); + }, + ray, intersection.element, intersection.uv, intersection.distance, + find_any); + return intersection; +} +shape_intersection intersect_lines_bvh(const shape_bvh& bvh, + const vector& lines, const vector& positions, + const vector& radius, const ray3f& ray, bool find_any) { + auto intersection = shape_intersection{}; + intersection.hit = intersect_elements_bvh( + bvh, + [&lines, &positions, &radius]( + int idx, const ray3f& ray, vec2f& uv, float& distance) { + auto& l = lines[idx]; + return intersect_line(ray, positions[l.x], positions[l.y], radius[l.x], + radius[l.y], uv, distance); + }, + ray, intersection.element, intersection.uv, intersection.distance, + find_any); + return intersection; +} +shape_intersection intersect_triangles_bvh(const shape_bvh& bvh, + const vector& triangles, const vector& positions, + const ray3f& ray, bool find_any) { + auto intersection = shape_intersection{}; + intersection.hit = intersect_elements_bvh( + bvh, + [&triangles, &positions]( + int idx, const ray3f& ray, vec2f& uv, float& distance) { + auto& t = triangles[idx]; + return intersect_triangle( + ray, positions[t.x], positions[t.y], positions[t.z], uv, distance); + }, + ray, intersection.element, intersection.uv, intersection.distance, + find_any); + return intersection; +} +shape_intersection intersect_quads_bvh(const shape_bvh& bvh, const vector& quads, const vector& positions, - const vector& normals, const vector& texcoords, int npoints, - int seed) { - sampled_positions.resize(npoints); - sampled_normals.resize(npoints); - sampled_texcoords.resize(npoints); - auto cdf = sample_quads_cdf(quads, positions); - auto rng = make_rng(seed); - for (auto i = 0; i < npoints; i++) { - auto sample = sample_quads(cdf, rand1f(rng), rand2f(rng)); - auto& q = quads[sample.first]; - auto uv = sample.second; - sampled_positions[i] = interpolate_quad( - positions[q.x], positions[q.y], positions[q.z], positions[q.w], uv); - if (!sampled_normals.empty()) { - sampled_normals[i] = normalize(interpolate_quad( - normals[q.x], normals[q.y], normals[q.z], normals[q.w], uv)); - } else { - sampled_normals[i] = quad_normal( - positions[q.x], positions[q.y], positions[q.z], positions[q.w]); - } - if (!sampled_texcoords.empty()) { - sampled_texcoords[i] = interpolate_quad( - texcoords[q.x], texcoords[q.y], texcoords[q.z], texcoords[q.w], uv); - } else { - sampled_texcoords[i] = zero2f; - } - } + const ray3f& ray, bool find_any) { + auto intersection = shape_intersection{}; + intersection.hit = intersect_elements_bvh( + bvh, + [&quads, &positions]( + int idx, const ray3f& ray, vec2f& uv, float& distance) { + auto& t = quads[idx]; + return intersect_quad(ray, positions[t.x], positions[t.y], + positions[t.z], positions[t.w], uv, distance); + }, + ray, intersection.element, intersection.uv, intersection.distance, + find_any); + return intersection; } -} // namespace yocto +// Intersect ray with a bvh. +template +static bool overlap_elements_bvh(const shape_bvh& bvh, + Overlap&& overlap_element, const vec3f& pos, float max_distance, + int& element, vec2f& uv, float& distance, bool find_any) { + // check if empty + if (bvh.nodes.empty()) return false; -// ----------------------------------------------------------------------------- -// EXAMPLE SHAPES -// ----------------------------------------------------------------------------- -namespace yocto { + // node stack + auto node_stack = array{}; + auto node_cur = 0; + node_stack[node_cur++] = 0; -// Make a quad. -void make_rect(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - const vec2f& scale, const vec2f& uvscale) { - positions.resize((steps.x + 1) * (steps.y + 1)); - normals.resize((steps.x + 1) * (steps.y + 1)); - texcoords.resize((steps.x + 1) * (steps.y + 1)); - for (auto j = 0; j <= steps.y; j++) { - for (auto i = 0; i <= steps.x; i++) { - auto uv = vec2f{i / (float)steps.x, j / (float)steps.y}; - positions[j * (steps.x + 1) + i] = { - (2 * uv.x - 1) * scale.x, (2 * uv.y - 1) * scale.y, 0}; - normals[j * (steps.x + 1) + i] = {0, 0, 1}; - texcoords[j * (steps.x + 1) + i] = vec2f{uv.x, 1 - uv.y} * uvscale; - } - } + // hit + auto hit = false; - quads.resize(steps.x * steps.y); - for (auto j = 0; j < steps.y; j++) { - for (auto i = 0; i < steps.x; i++) { - quads[j * steps.x + i] = {j * (steps.x + 1) + i, - j * (steps.x + 1) + i + 1, (j + 1) * (steps.x + 1) + i + 1, - (j + 1) * (steps.x + 1) + i}; - } - } -} + // walking stack + while (node_cur) { + // grab node + auto& node = bvh.nodes[node_stack[--node_cur]]; -void make_bulged_rect(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - const vec2f& scale, const vec2f& uvscale, float height) { - make_rect(quads, positions, normals, texcoords, steps, scale, uvscale); - if (height != 0) { - height = min(height, min(scale)); - auto radius = (1 + height * height) / (2 * height); - auto center = vec3f{0, 0, -radius + height}; - for (auto i = 0; i < positions.size(); i++) { - auto pn = normalize(positions[i] - center); - positions[i] = center + pn * radius; - normals[i] = pn; + // intersect bbox + if (!overlap_bbox(pos, max_distance, node.bbox)) continue; + + // intersect node, switching based on node type + // for each type, iterate over the the primitive list + if (node.internal) { + // internal node + node_stack[node_cur++] = node.start + 0; + node_stack[node_cur++] = node.start + 1; + } else { + for (auto idx = 0; idx < node.num; idx++) { + auto primitive = bvh.primitives[node.start + idx]; + if (overlap_element(primitive, pos, max_distance, uv, distance)) { + hit = true; + element = primitive; + max_distance = distance; + } + } } - } -} -// Make a quad. -void make_recty(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - const vec2f& scale, const vec2f& uvscale) { - make_rect(quads, positions, normals, texcoords, steps, scale, uvscale); - for (auto& p : positions) { - std::swap(p.y, p.z); - p.z = -p.z; + // check for early exit + if (find_any && hit) return hit; } - for (auto& n : normals) std::swap(n.y, n.z); -} -void make_bulged_recty(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - const vec2f& scale, const vec2f& uvscale, float height) { - make_bulged_rect( - quads, positions, normals, texcoords, steps, scale, uvscale, height); - for (auto& p : positions) { - std::swap(p.y, p.z); - p.z = -p.z; - } - for (auto& n : normals) std::swap(n.y, n.z); + return hit; } -// Make a cube. -void make_box(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec3i& steps, - const vec3f& scale, const vec3f& uvscale) { - quads.clear(); - positions.clear(); - normals.clear(); - texcoords.clear(); - auto qquads = vector{}; - auto qpositions = vector{}; - auto qnormals = vector{}; - auto qtexturecoords = vector{}; - // + z - make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.x, steps.y}, - {scale.x, scale.y}, {uvscale.x, uvscale.y}); - for (auto& p : qpositions) p = {p.x, p.y, scale.z}; - for (auto& n : qnormals) n = {0, 0, 1}; - merge_quads(quads, positions, normals, texcoords, qquads, qpositions, - qnormals, qtexturecoords); - // - z - make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.x, steps.y}, - {scale.x, scale.y}, {uvscale.x, uvscale.y}); - for (auto& p : qpositions) p = {-p.x, p.y, -scale.z}; - for (auto& n : qnormals) n = {0, 0, -1}; - merge_quads(quads, positions, normals, texcoords, qquads, qpositions, - qnormals, qtexturecoords); - // + x - make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.z, steps.y}, - {scale.z, scale.y}, {uvscale.z, uvscale.y}); - for (auto& p : qpositions) p = {scale.x, p.y, -p.x}; - for (auto& n : qnormals) n = {1, 0, 0}; - merge_quads(quads, positions, normals, texcoords, qquads, qpositions, - qnormals, qtexturecoords); - // - x - make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.z, steps.y}, - {scale.z, scale.y}, {uvscale.z, uvscale.y}); - for (auto& p : qpositions) p = {-scale.x, p.y, p.x}; - for (auto& n : qnormals) n = {-1, 0, 0}; - merge_quads(quads, positions, normals, texcoords, qquads, qpositions, - qnormals, qtexturecoords); - // + y - make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.x, steps.z}, - {scale.x, scale.z}, {uvscale.x, uvscale.z}); - for (auto i = 0; i < qpositions.size(); i++) { - qpositions[i] = {qpositions[i].x, scale.y, -qpositions[i].y}; - qnormals[i] = {0, 1, 0}; - } - merge_quads(quads, positions, normals, texcoords, qquads, qpositions, - qnormals, qtexturecoords); - // - y - make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.x, steps.z}, - {scale.x, scale.z}, {uvscale.x, uvscale.z}); - for (auto i = 0; i < qpositions.size(); i++) { - qpositions[i] = {qpositions[i].x, -scale.y, qpositions[i].y}; - qnormals[i] = {0, -1, 0}; - } - merge_quads(quads, positions, normals, texcoords, qquads, qpositions, - qnormals, qtexturecoords); +// Find a shape element that overlaps a point within a given distance +// max distance, returning either the closest or any overlap depending on +// `find_any`. Returns the point distance, the instance id, the shape element +// index and the element barycentric coordinates. +shape_intersection overlap_points_bvh(const shape_bvh& bvh, + const vector& points, const vector& positions, + const vector& radius, const vec3f& pos, float max_distance, + bool find_any) { + auto intersection = shape_intersection{}; + intersection.hit = overlap_elements_bvh( + bvh, + [&points, &positions, &radius](int idx, const vec3f& pos, + float max_distance, vec2f& uv, float& distance) { + auto& p = points[idx]; + return overlap_point( + pos, max_distance, positions[p], radius[p], uv, distance); + }, + pos, max_distance, intersection.element, intersection.uv, + intersection.distance, find_any); + return intersection; +} +shape_intersection overlap_lines_bvh(const shape_bvh& bvh, + const vector& lines, const vector& positions, + const vector& radius, const vec3f& pos, float max_distance, + bool find_any) { + auto intersection = shape_intersection{}; + intersection.hit = overlap_elements_bvh( + bvh, + [&lines, &positions, &radius](int idx, const vec3f& pos, + float max_distance, vec2f& uv, float& distance) { + auto& l = lines[idx]; + return overlap_line(pos, max_distance, positions[l.x], positions[l.y], + radius[l.x], radius[l.y], uv, distance); + }, + pos, max_distance, intersection.element, intersection.uv, + intersection.distance, find_any); + return intersection; +} +shape_intersection overlap_triangles_bvh(const shape_bvh& bvh, + const vector& triangles, const vector& positions, + const vector& radius, const vec3f& pos, float max_distance, + bool find_any) { + auto intersection = shape_intersection{}; + intersection.hit = overlap_elements_bvh( + bvh, + [&triangles, &positions, &radius](int idx, const vec3f& pos, + float max_distance, vec2f& uv, float& distance) { + auto& t = triangles[idx]; + return overlap_triangle(pos, max_distance, positions[t.x], + positions[t.y], positions[t.z], radius[t.x], radius[t.y], + radius[t.z], uv, distance); + }, + pos, max_distance, intersection.element, intersection.uv, + intersection.distance, find_any); + return intersection; +} +shape_intersection overlap_quads_bvh(const shape_bvh& bvh, + const vector& quads, const vector& positions, + const vector& radius, const vec3f& pos, float max_distance, + bool find_any) { + auto intersection = shape_intersection{}; + intersection.hit = overlap_elements_bvh( + bvh, + [&quads, &positions, &radius](int idx, const vec3f& pos, + float max_distance, vec2f& uv, float& distance) { + auto& q = quads[idx]; + return overlap_quad(pos, max_distance, positions[q.x], positions[q.y], + positions[q.z], positions[q.w], radius[q.x], radius[q.y], + radius[q.z], radius[q.w], uv, distance); + }, + pos, max_distance, intersection.element, intersection.uv, + intersection.distance, find_any); + return intersection; } -void make_rounded_box(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec3i& steps, - const vec3f& scale, const vec3f& uvscale, float radius) { - make_box(quads, positions, normals, texcoords, steps, scale, uvscale); - if (radius != 0) { - radius = min(radius, min(scale)); - auto c = scale - radius; - for (auto i = 0; i < positions.size(); i++) { - auto pc = vec3f{ - abs(positions[i].x), abs(positions[i].y), abs(positions[i].z)}; - auto ps = vec3f{positions[i].x < 0 ? -1.0f : 1.0f, - positions[i].y < 0 ? -1.0f : 1.0f, positions[i].z < 0 ? -1.0f : 1.0f}; - if (pc.x >= c.x && pc.y >= c.y && pc.z >= c.z) { - auto pn = normalize(pc - c); - positions[i] = c + radius * pn; - normals[i] = pn; - } else if (pc.x >= c.x && pc.y >= c.y) { - auto pn = normalize((pc - c) * vec3f{1, 1, 0}); - positions[i] = {c.x + radius * pn.x, c.y + radius * pn.y, pc.z}; - normals[i] = pn; - } else if (pc.x >= c.x && pc.z >= c.z) { - auto pn = normalize((pc - c) * vec3f{1, 0, 1}); - positions[i] = {c.x + radius * pn.x, pc.y, c.z + radius * pn.z}; - normals[i] = pn; - } else if (pc.y >= c.y && pc.z >= c.z) { - auto pn = normalize((pc - c) * vec3f{0, 1, 1}); - positions[i] = {pc.x, c.y + radius * pn.y, c.z + radius * pn.z}; - normals[i] = pn; - } else { - continue; +} // namespace yocto + +// ----------------------------------------------------------------------------- +// HASH GRID AND NEAREST NEIGHBORS +// ----------------------------------------------------------------------------- + +namespace yocto { + +// Gets the cell index +vec3i get_cell_index(const hash_grid& grid, const vec3f& position) { + auto scaledpos = position * grid.cell_inv_size; + return vec3i{(int)scaledpos.x, (int)scaledpos.y, (int)scaledpos.z}; +} + +// Create a hash_grid +hash_grid make_hash_grid(float cell_size) { + auto grid = hash_grid{}; + grid.cell_size = cell_size; + grid.cell_inv_size = 1 / cell_size; + return grid; +} +hash_grid make_hash_grid(const vector& positions, float cell_size) { + auto grid = hash_grid{}; + grid.cell_size = cell_size; + grid.cell_inv_size = 1 / cell_size; + for (auto& position : positions) insert_vertex(grid, position); + return grid; +} +// Inserts a point into the grid +int insert_vertex(hash_grid& grid, const vec3f& position) { + auto vertex_id = (int)grid.positions.size(); + auto cell = get_cell_index(grid, position); + grid.cells[cell].push_back(vertex_id); + grid.positions.push_back(position); + return vertex_id; +} +// Finds the nearest neighbors within a given radius +void find_neighbors(const hash_grid& grid, vector& neighbors, + const vec3f& position, float max_radius, int skip_id) { + auto cell = get_cell_index(grid, position); + auto cell_radius = (int)(max_radius * grid.cell_inv_size) + 1; + neighbors.clear(); + auto max_radius_squared = max_radius * max_radius; + for (auto k = -cell_radius; k <= cell_radius; k++) { + for (auto j = -cell_radius; j <= cell_radius; j++) { + for (auto i = -cell_radius; i <= cell_radius; i++) { + auto ncell = cell + vec3i{i, j, k}; + auto cell_iterator = grid.cells.find(ncell); + if (cell_iterator == grid.cells.end()) continue; + auto& ncell_vertices = cell_iterator->second; + for (auto vertex_id : ncell_vertices) { + if (distance_squared(grid.positions[vertex_id], position) > + max_radius_squared) + continue; + if (vertex_id == skip_id) continue; + neighbors.push_back(vertex_id); + } } - positions[i] *= ps; - normals[i] *= ps; } } } - -void make_rect_stack(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec3i& steps, - const vec3f& scale, const vec2f& uvscale) { - auto qquads = vector{}; - auto qpositions = vector{}; - auto qnormals = vector{}; - auto qtexturecoords = vector{}; - for (auto i = 0; i <= steps.z; i++) { - make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.x, steps.y}, - {scale.x, scale.y}, uvscale); - for (auto& p : qpositions) p.z = (-1 + 2 * (float)i / steps.z) * scale.z; - merge_quads(quads, positions, normals, texcoords, qquads, qpositions, - qnormals, qtexturecoords); - } +void find_neighbors(const hash_grid& grid, vector& neighbors, + const vec3f& position, float max_radius) { + find_neighbors(grid, neighbors, position, max_radius, -1); +} +void find_neighbors(const hash_grid& grid, vector& neighbors, int vertex, + float max_radius) { + find_neighbors(grid, neighbors, grid.positions[vertex], max_radius, vertex); } -void make_floor(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - const vec2f& scale, const vec2f& uvscale) { - make_rect(quads, positions, normals, texcoords, steps, scale, uvscale); - for (auto& p : positions) { - std::swap(p.y, p.z); - p.z = -p.z; +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMPLEMENTATION OF SHAPE ELEMENT CONVERSION AND GROUPING +// ----------------------------------------------------------------------------- +namespace yocto { + +// Convert quads to triangles +vector quads_to_triangles(const vector& quads) { + auto triangles = vector{}; + triangles.reserve(quads.size() * 2); + for (auto& q : quads) { + triangles.push_back({q.x, q.y, q.w}); + if (q.z != q.w) triangles.push_back({q.z, q.w, q.y}); } - for (auto& n : normals) std::swap(n.y, n.z); + return triangles; } -void make_bent_floor(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - const vec2f& scale, const vec2f& uvscale, float radius) { - make_floor(quads, positions, normals, texcoords, steps, scale, uvscale); - if (radius != 0) { - radius = min(radius, scale.y); - auto start = (scale.y - radius) / 2; - auto end = start + radius; - for (auto i = 0; i < positions.size(); i++) { - if (positions[i].z < -end) { - positions[i] = {positions[i].x, -positions[i].z - end + radius, -end}; - normals[i] = {0, 0, 1}; - } else if (positions[i].z < -start && positions[i].z >= -end) { - auto phi = (pif / 2) * (-positions[i].z - start) / radius; - positions[i] = {positions[i].x, -cos(phi) * radius + radius, - -sin(phi) * radius - start}; - normals[i] = {0, cos(phi), sin(phi)}; - } else { - } - } - } -} - -// Generate a sphere -void make_sphere(vector& quads, vector& positions, - vector& normals, vector& texcoords, int steps, float scale, - float uvscale) { - make_box(quads, positions, normals, texcoords, {steps, steps, steps}, - {scale, scale, scale}, {uvscale, uvscale, uvscale}); - for (auto& p : positions) p = normalize(p) * scale; - normals = positions; - for (auto& n : normals) n = normalize(n); +// Convert triangles to quads by creating degenerate quads +vector triangles_to_quads(const vector& triangles) { + auto quads = vector{}; + quads.reserve(triangles.size()); + for (auto& t : triangles) quads.push_back({t.x, t.y, t.z, t.z}); + return quads; } -// Generate a uvsphere -void make_uvsphere(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - float scale, const vec2f& uvscale) { - make_rect(quads, positions, normals, texcoords, steps, {1, 1}, {1, 1}); - for (auto i = 0; i < positions.size(); i++) { - auto uv = texcoords[i]; - auto a = vec2f{2 * pif * uv.x, pif * (1 - uv.y)}; - positions[i] = vec3f{cos(a.x) * sin(a.y), sin(a.x) * sin(a.y), cos(a.y)} * - scale; - normals[i] = normalize(positions[i]); - texcoords[i] = uv * uvscale; +// Convert beziers to lines using 3 lines for each bezier. +vector bezier_to_lines(const vector& beziers) { + auto lines = vector{}; + lines.reserve(beziers.size() * 3); + for (auto b : beziers) { + lines.push_back({b.x, b.y}); + lines.push_back({b.y, b.z}); + lines.push_back({b.z, b.w}); } + return lines; } -void make_capped_uvsphere(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - float scale, const vec2f& uvscale, float cap) { - make_uvsphere(quads, positions, normals, texcoords, steps, scale, uvscale); - if (cap != 0) { - cap = min(cap, scale / 2); - auto zflip = (scale - cap); - for (auto i = 0; i < positions.size(); i++) { - if (positions[i].z > zflip) { - positions[i].z = 2 * zflip - positions[i].z; - normals[i].x = -normals[i].x; - normals[i].y = -normals[i].y; - } else if (positions[i].z < -zflip) { - positions[i].z = 2 * (-zflip) - positions[i].z; - normals[i].x = -normals[i].x; - normals[i].y = -normals[i].y; +// Convert face varying data to single primitives. Returns the quads indices +// and filled vectors for pos, norm and texcoord. +void split_facevarying(vector& split_quads, + vector& split_positions, vector& split_normals, + vector& split_texcoords, const vector& quadspos, + const vector& quadsnorm, const vector& quadstexcoord, + const vector& positions, const vector& normals, + const vector& texcoords) { + // make faces unique + unordered_map vert_map; + split_quads.resize(quadspos.size()); + for (auto fid = 0; fid < quadspos.size(); fid++) { + for (auto c = 0; c < 4; c++) { + auto v = vec3i{ + (&quadspos[fid].x)[c], + (!quadsnorm.empty()) ? (&quadsnorm[fid].x)[c] : -1, + (!quadstexcoord.empty()) ? (&quadstexcoord[fid].x)[c] : -1, + }; + auto it = vert_map.find(v); + if (it == vert_map.end()) { + auto s = (int)vert_map.size(); + vert_map.insert(it, {v, s}); + (&split_quads[fid].x)[c] = s; + } else { + (&split_quads[fid].x)[c] = it->second; } } } -} - -// Generate a uvsphere -void make_uvspherey(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - float scale, const vec2f& uvscale) { - make_uvsphere(quads, positions, normals, texcoords, steps, scale, uvscale); - for (auto& p : positions) std::swap(p.y, p.z); - for (auto& n : normals) std::swap(n.y, n.z); - for (auto& t : texcoords) t.y = 1 - t.y; - for (auto& q : quads) std::swap(q.y, q.w); -} - -void make_capped_uvspherey(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - float scale, const vec2f& uvscale, float cap) { - make_capped_uvsphere( - quads, positions, normals, texcoords, steps, scale, uvscale, cap); - for (auto& p : positions) std::swap(p.y, p.z); - for (auto& n : normals) std::swap(n.y, n.z); - for (auto& q : quads) std::swap(q.y, q.w); -} -// Generate a disk -void make_disk(vector& quads, vector& positions, - vector& normals, vector& texcoords, int steps, float scale, - float uvscale) { - make_rect(quads, positions, normals, texcoords, {steps, steps}, {1, 1}, - {uvscale, uvscale}); - for (auto& position : positions) { - // Analytical Methods for Squaring the Disc, by C. Fong - // https://arxiv.org/abs/1509.06344 - auto xy = vec2f{position.x, position.y}; - auto uv = vec2f{ - xy.x * sqrt(1 - xy.y * xy.y / 2), xy.y * sqrt(1 - xy.x * xy.x / 2)}; - position = vec3f{uv.x, uv.y, 0} * scale; + // fill vert data + split_positions.clear(); + if (!positions.empty()) { + split_positions.resize(vert_map.size()); + for (auto& [vert, index] : vert_map) { + split_positions[index] = positions[vert.x]; + } + } + split_normals.clear(); + if (!normals.empty()) { + split_normals.resize(vert_map.size()); + for (auto& [vert, index] : vert_map) { + split_normals[index] = normals[vert.y]; + } + } + split_texcoords.clear(); + if (!texcoords.empty()) { + split_texcoords.resize(vert_map.size()); + for (auto& [vert, index] : vert_map) { + split_texcoords[index] = texcoords[vert.z]; + } } } -void make_bulged_disk(vector& quads, vector& positions, - vector& normals, vector& texcoords, int steps, float scale, - float uvscale, float height) { - make_disk(quads, positions, normals, texcoords, steps, scale, uvscale); - if (height != 0) { - height = min(height, scale); - auto radius = (1 + height * height) / (2 * height); - auto center = vec3f{0, 0, -radius + height}; - for (auto i = 0; i < positions.size(); i++) { - auto pn = normalize(positions[i] - center); - positions[i] = center + pn * radius; - normals[i] = pn; +// Weld vertices within a threshold. +pair, vector> weld_vertices( + const vector& positions, float threshold) { + auto indices = vector(positions.size()); + auto welded = vector{}; + auto grid = make_hash_grid(threshold); + auto neighbors = vector{}; + for (auto vertex = 0; vertex < positions.size(); vertex++) { + auto& position = positions[vertex]; + find_neighbors(grid, neighbors, position, threshold); + if (neighbors.empty()) { + welded.push_back(position); + indices[vertex] = (int)welded.size() - 1; + insert_vertex(grid, position); + } else { + indices[vertex] = neighbors.front(); } } + return {welded, indices}; +} +pair, vector> weld_triangles( + const vector& triangles, const vector& positions, + float threshold) { + auto [wpositions, indices] = weld_vertices(positions, threshold); + auto wtriangles = triangles; + for (auto& t : wtriangles) t = {indices[t.x], indices[t.y], indices[t.z]}; + return {wtriangles, wpositions}; +} +pair, vector> weld_quads(const vector& quads, + const vector& positions, float threshold) { + auto [wpositions, indices] = weld_vertices(positions, threshold); + auto wquads = quads; + for (auto& q : wquads) + q = { + indices[q.x], + indices[q.y], + indices[q.z], + indices[q.w], + }; + return {wquads, wpositions}; } -// Generate a uvdisk -void make_uvdisk(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - float scale, const vec2f& uvscale) { - make_rect(quads, positions, normals, texcoords, steps, {1, 1}, {1, 1}); - for (auto i = 0; i < positions.size(); i++) { - auto uv = texcoords[i]; - auto phi = 2 * pif * uv.x; - positions[i] = vec3f{cos(phi) * uv.y, sin(phi) * uv.y, 0} * scale; - normals[i] = {0, 0, 1}; - texcoords[i] = uv * uvscale; - } +// Merge shape elements +void merge_lines(vector& lines, vector& positions, + vector& tangents, vector& texcoords, vector& radius, + const vector& merge_lines, const vector& merge_positions, + const vector& merge_tangents, + const vector& merge_texturecoords, + const vector& merge_radius) { + auto merge_verts = (int)positions.size(); + for (auto& l : merge_lines) + lines.push_back({l.x + merge_verts, l.y + merge_verts}); + positions.insert( + positions.end(), merge_positions.begin(), merge_positions.end()); + tangents.insert(tangents.end(), merge_tangents.begin(), merge_tangents.end()); + texcoords.insert( + texcoords.end(), merge_texturecoords.begin(), merge_texturecoords.end()); + radius.insert(radius.end(), merge_radius.begin(), merge_radius.end()); +} +void merge_triangles(vector& triangles, vector& positions, + vector& normals, vector& texcoords, + const vector& merge_triangles, const vector& merge_positions, + const vector& merge_normals, + const vector& merge_texturecoords) { + auto merge_verts = (int)positions.size(); + for (auto& t : merge_triangles) + triangles.push_back( + {t.x + merge_verts, t.y + merge_verts, t.z + merge_verts}); + positions.insert( + positions.end(), merge_positions.begin(), merge_positions.end()); + normals.insert(normals.end(), merge_normals.begin(), merge_normals.end()); + texcoords.insert( + texcoords.end(), merge_texturecoords.begin(), merge_texturecoords.end()); +} +void merge_quads(vector& quads, vector& positions, + vector& normals, vector& texcoords, + const vector& merge_quads, const vector& merge_positions, + const vector& merge_normals, + const vector& merge_texturecoords) { + auto merge_verts = (int)positions.size(); + for (auto& q : merge_quads) + quads.push_back({q.x + merge_verts, q.y + merge_verts, q.z + merge_verts, + q.w + merge_verts}); + positions.insert( + positions.end(), merge_positions.begin(), merge_positions.end()); + normals.insert(normals.end(), merge_normals.begin(), merge_normals.end()); + texcoords.insert( + texcoords.end(), merge_texturecoords.begin(), merge_texturecoords.end()); } -// Generate a uvcylinder -void make_uvcylinder(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec3i& steps, - const vec2f& scale, const vec3f& uvscale) { - auto qquads = vector{}; - auto qpositions = vector{}; - auto qnormals = vector{}; - auto qtexcoords = vector{}; - // side - make_rect(qquads, qpositions, qnormals, qtexcoords, {steps.x, steps.y}, - {1, 1}, {1, 1}); - for (auto i = 0; i < qpositions.size(); i++) { - auto uv = qtexcoords[i]; - auto phi = 2 * pif * uv.x; - qpositions[i] = { - cos(phi) * scale.x, sin(phi) * scale.x, (2 * uv.y - 1) * scale.y}; - qnormals[i] = {cos(phi), sin(phi), 0}; - qtexcoords[i] = uv * vec2f{uvscale.x, uvscale.y}; - } - for (auto& q : qquads) std::swap(q.y, q.w); - merge_quads(quads, positions, normals, texcoords, qquads, qpositions, - qnormals, qtexcoords); - // top - make_rect(qquads, qpositions, qnormals, qtexcoords, {steps.x, steps.z}, - {1, 1}, {1, 1}); - for (auto i = 0; i < qpositions.size(); i++) { - auto uv = qtexcoords[i]; - auto phi = 2 * pif * uv.x; - qpositions[i] = {cos(phi) * uv.y * scale.x, sin(phi) * uv.y * scale.x, 0}; - qnormals[i] = {0, 0, 1}; - qtexcoords[i] = uv * vec2f{uvscale.x, uvscale.z}; - qpositions[i].z = scale.y; +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMPLEMENTATION OF SHAPE SUBDIVISION +// ----------------------------------------------------------------------------- +namespace yocto { + +// Subdivide lines. +template +static pair, vector> subdivide_lines_impl( + const vector& lines, const vector& vertices) { + // early exit + if (lines.empty() || vertices.empty()) return {lines, vertices}; + // create vertices + auto tvertices = vector{}; + tvertices.reserve(vertices.size() + lines.size()); + for (auto& vertex : vertices) tvertices.push_back(vertex); + for (auto& line : lines) { + tvertices.push_back((vertices[line.x] + vertices[line.y]) / 2); } - merge_quads(quads, positions, normals, texcoords, qquads, qpositions, - qnormals, qtexcoords); - // bottom - make_rect(qquads, qpositions, qnormals, qtexcoords, {steps.x, steps.z}, - {1, 1}, {1, 1}); - for (auto i = 0; i < qpositions.size(); i++) { - auto uv = qtexcoords[i]; - auto phi = 2 * pif * uv.x; - qpositions[i] = {cos(phi) * uv.y * scale.x, sin(phi) * uv.y * scale.x, 0}; - qnormals[i] = {0, 0, 1}; - qtexcoords[i] = uv * vec2f{uvscale.x, uvscale.z}; - qpositions[i].z = -scale.y; - qnormals[i] = -qnormals[i]; + // create lines + auto tlines = vector{}; + tlines.reserve(lines.size() * 2); + auto line_vertex = [nverts = (int)vertices.size()]( + size_t line_id) { return nverts + (int)line_id; }; + for (auto&& [line_id, line] : enumerate(lines)) { + tlines.push_back({line.x, line_vertex(line_id)}); + tlines.push_back({line_vertex(line_id), line.y}); } - for (auto& qquad : qquads) swap(qquad.x, qquad.z); - merge_quads(quads, positions, normals, texcoords, qquads, qpositions, - qnormals, qtexcoords); + // done + return {tlines, tvertices}; } -// Generate a uvcylinder -void make_rounded_uvcylinder(vector& quads, vector& positions, - vector& normals, vector& texcoords, const vec3i& steps, - const vec2f& scale, const vec3f& uvscale, float radius) { - make_uvcylinder(quads, positions, normals, texcoords, steps, scale, uvscale); - if (radius != 0) { - radius = min(radius, min(scale)); - auto c = scale - radius; - for (auto i = 0; i < positions.size(); i++) { - auto phi = atan2(positions[i].y, positions[i].x); - auto r = length(vec2f{positions[i].x, positions[i].y}); - auto z = positions[i].z; - auto pc = vec2f{r, abs(z)}; - auto ps = (z < 0) ? -1.0f : 1.0f; - if (pc.x >= c.x && pc.y >= c.y) { - auto pn = normalize(pc - c); - positions[i] = {cos(phi) * (c.x + radius * pn.x), - sin(phi) * (c.x + radius * pn.x), ps * (c.y + radius * pn.y)}; - normals[i] = {cos(phi) * pn.x, sin(phi) * pn.x, ps * pn.y}; - } else { - continue; - } - } +// Subdivide triangle. +template +static pair, vector> subdivide_triangles_impl( + const vector& triangles, const vector& vertices) { + // early exit + if (triangles.empty() || vertices.empty()) return {triangles, vertices}; + // get edges + auto emap = make_edge_map(triangles); + auto edges = get_edges(emap); + // create vertices + auto tvertices = vector{}; + tvertices.reserve(vertices.size() + edges.size()); + for (auto& vertex : vertices) tvertices.push_back(vertex); + for (auto& edge : edges) + tvertices.push_back((vertices[edge.x] + vertices[edge.y]) / 2); + // create triangles + auto ttriangles = vector{}; + ttriangles.reserve(triangles.size() * 4); + auto edge_vertex = [&emap, nverts = (int)vertices.size()](const vec2i& edge) { + return nverts + edge_index(emap, edge); + }; + for (auto& triangle : triangles) { + ttriangles.push_back({triangle.x, edge_vertex({triangle.x, triangle.y}), + edge_vertex({triangle.z, triangle.x})}); + ttriangles.push_back({triangle.y, edge_vertex({triangle.y, triangle.z}), + edge_vertex({triangle.x, triangle.y})}); + ttriangles.push_back({triangle.z, edge_vertex({triangle.z, triangle.x}), + edge_vertex({triangle.y, triangle.z})}); + ttriangles.push_back({edge_vertex({triangle.x, triangle.y}), + edge_vertex({triangle.y, triangle.z}), + edge_vertex({triangle.z, triangle.x})}); } + // done + return {ttriangles, tvertices}; } -// Generate lines set along a quad. -void make_lines(vector& lines, vector& positions, - vector& normals, vector& texcoords, vector& radius, - const vec2i& steps, const vec2f& size, const vec2f& uvscale, - const vec2f& rad) { - positions.resize((steps.x + 1) * steps.y); - normals.resize((steps.x + 1) * steps.y); - texcoords.resize((steps.x + 1) * steps.y); - radius.resize((steps.x + 1) * steps.y); - if (steps.y > 1) { - for (auto j = 0; j < steps.y; j++) { - for (auto i = 0; i <= steps.x; i++) { - auto uv = vec2f{i / (float)steps.x, j / (float)(steps.y - 1)}; - positions[j * (steps.x + 1) + i] = { - (uv.x - 0.5f) * size.x, (uv.y - 0.5f) * size.y, 0}; - normals[j * (steps.x + 1) + i] = {1, 0, 0}; - texcoords[j * (steps.x + 1) + i] = uv * uvscale; - radius[j * (steps.x + 1) + i] = lerp(rad.x, rad.y, uv.x); - } +// Subdivide quads. +template +static pair, vector> subdivide_quads_impl( + const vector& quads, const vector& vertices) { + // early exit + if (quads.empty() || vertices.empty()) return {quads, vertices}; + // get edges + auto emap = make_edge_map(quads); + auto edges = get_edges(emap); + // create vertices + auto tvertices = vector{}; + tvertices.reserve(vertices.size() + edges.size() + quads.size()); + for (auto& vertex : vertices) tvertices.push_back(vertex); + for (auto& edge : edges) + tvertices.push_back((vertices[edge.x] + vertices[edge.y]) / 2); + for (auto& quad : quads) { + if (quad.z != quad.w) { + tvertices.push_back((vertices[quad.x] + vertices[quad.y] + + vertices[quad.z] + vertices[quad.w]) / + 4); + } else { + tvertices.push_back( + (vertices[quad.x] + vertices[quad.y] + vertices[quad.z]) / 3); } - } else { - for (auto i = 0; i <= steps.x; i++) { - auto uv = vec2f{i / (float)steps.x, 0}; - positions[i] = {(uv.x - 0.5f) * size.x, 0, 0}; - normals[i] = {1, 0, 0}; - texcoords[i] = uv * uvscale; - radius[i] = lerp(rad.x, rad.y, uv.x); + } + // create quads + auto tquads = vector{}; + tquads.reserve(quads.size() * 4); + auto edge_vertex = [&emap, nverts = (int)vertices.size()](const vec2i& edge) { + return nverts + edge_index(emap, edge); + }; + auto quad_vertex = [nverts = (int)vertices.size(), + nedges = (int)edges.size()](size_t quad_id) { + return nverts + nedges + (int)quad_id; + }; + for (auto&& [quad_id, quad] : enumerate(quads)) { + if (quad.z != quad.w) { + tquads.push_back({quad.x, edge_vertex({quad.x, quad.y}), + quad_vertex(quad_id), edge_vertex({quad.w, quad.x})}); + tquads.push_back({quad.y, edge_vertex({quad.y, quad.z}), + quad_vertex(quad_id), edge_vertex({quad.x, quad.y})}); + tquads.push_back({quad.z, edge_vertex({quad.z, quad.w}), + quad_vertex(quad_id), edge_vertex({quad.y, quad.z})}); + tquads.push_back({quad.w, edge_vertex({quad.w, quad.x}), + quad_vertex(quad_id), edge_vertex({quad.z, quad.w})}); + } else { + tquads.push_back({quad.x, edge_vertex({quad.x, quad.y}), + quad_vertex(quad_id), edge_vertex({quad.z, quad.x})}); + tquads.push_back({quad.y, edge_vertex({quad.y, quad.z}), + quad_vertex(quad_id), edge_vertex({quad.x, quad.y})}); + tquads.push_back({quad.z, edge_vertex({quad.z, quad.x}), + quad_vertex(quad_id), edge_vertex({quad.y, quad.z})}); } } + // done + return {tquads, tvertices}; +} - lines.resize(steps.x * steps.y); - for (int j = 0; j < steps.y; j++) { - for (int i = 0; i < steps.x; i++) { - lines[j * steps.x + i] = { - j * (steps.x + 1) + i, j * (steps.x + 1) + i + 1}; +// Subdivide beziers. +template +static pair, vector> subdivide_beziers_impl( + const vector& beziers, const vector& vertices) { + // early exit + if (beziers.empty() || vertices.empty()) return {beziers, vertices}; + // get edges + auto vmap = unordered_map(); + auto tvertices = vector(); + auto tbeziers = vector(); + for (auto& bezier : beziers) { + if (vmap.find(bezier.x) == vmap.end()) { + vmap[bezier.x] = (int)tvertices.size(); + tvertices.push_back(vertices[bezier.x]); + } + if (vmap.find(bezier.w) == vmap.end()) { + vmap[bezier.w] = (int)tvertices.size(); + tvertices.push_back(vertices[bezier.w]); } + auto bo = (int)tvertices.size(); + tbeziers.push_back({vmap.at(bezier.x), bo + 0, bo + 1, bo + 2}); + tbeziers.push_back({bo + 2, bo + 3, bo + 4, vmap.at(bezier.w)}); + tvertices.push_back(vertices[bezier.x] / 2 + vertices[bezier.y] / 2); + tvertices.push_back(vertices[bezier.x] / 4 + vertices[bezier.y] / 2 + + vertices[bezier.z] / 4); + tvertices.push_back( + vertices[bezier.x] / 8 + vertices[bezier.y] * ((float)3 / (float)8) + + vertices[bezier.z] * ((float)3 / (float)8) + vertices[bezier.w] / 8); + tvertices.push_back(vertices[bezier.y] / 4 + vertices[bezier.z] / 2 + + vertices[bezier.w] / 4); + tvertices.push_back(vertices[bezier.z] / 2 + vertices[bezier.w] / 2); } + + // done + return {tbeziers, tvertices}; } -// Generate a point at the origin. -void make_point(vector& points, vector& positions, - vector& normals, vector& texcoords, vector& radius, - float point_radius) { - points = {0}; - positions = {{0, 0, 0}}; - normals = {{0, 0, 1}}; - texcoords = {{0, 0}}; - radius = {point_radius}; -} +// Subdivide catmullclark. +template +static pair, vector> subdivide_catmullclark_impl( + const vector& quads, const vector& vertices, bool lock_boundary) { + // early exit + if (quads.empty() || vertices.empty()) return {quads, vertices}; + // get edges + auto emap = make_edge_map(quads); + auto edges = get_edges(emap); + auto boundary = get_boundary(emap); -// Generate a point set with points placed at the origin with texcoords -// varying along u. -void make_points(vector& points, vector& positions, - vector& normals, vector& texcoords, vector& radius, - int num, float uvscale, float point_radius) { - points.resize(num); - for (auto i = 0; i < num; i++) points[i] = i; - positions.assign(num, {0, 0, 0}); - normals.assign(num, {0, 0, 1}); - texcoords.assign(num, {0, 0}); - radius.assign(num, point_radius); - for (auto i = 0; i < texcoords.size(); i++) - texcoords[i] = {(float)i / (float)num, 0}; -} + // split elements ------------------------------------ + // create vertices + auto tvertices = vector{}; + tvertices.reserve(vertices.size() + edges.size() + quads.size()); + for (auto& vertex : vertices) tvertices.push_back(vertex); + for (auto& edge : edges) + tvertices.push_back((vertices[edge.x] + vertices[edge.y]) / 2); + for (auto& quad : quads) { + if (quad.z != quad.w) { + tvertices.push_back((vertices[quad.x] + vertices[quad.y] + + vertices[quad.z] + vertices[quad.w]) / + 4); + } else { + tvertices.push_back( + (vertices[quad.x] + vertices[quad.y] + vertices[quad.z]) / 3); + } + } + // create quads + auto tquads = vector{}; + tquads.reserve(quads.size() * 4); + auto edge_vertex = [&emap, nverts = (int)vertices.size()](const vec2i& edge) { + return nverts + edge_index(emap, edge); + }; + auto quad_vertex = [nverts = (int)vertices.size(), + nedges = (int)edges.size()](size_t quad_id) { + return nverts + nedges + (int)quad_id; + }; + for (auto&& [quad_id, quad] : enumerate(quads)) { + if (quad.z != quad.w) { + tquads.push_back({quad.x, edge_vertex({quad.x, quad.y}), + quad_vertex(quad_id), edge_vertex({quad.w, quad.x})}); + tquads.push_back({quad.y, edge_vertex({quad.y, quad.z}), + quad_vertex(quad_id), edge_vertex({quad.x, quad.y})}); + tquads.push_back({quad.z, edge_vertex({quad.z, quad.w}), + quad_vertex(quad_id), edge_vertex({quad.y, quad.z})}); + tquads.push_back({quad.w, edge_vertex({quad.w, quad.x}), + quad_vertex(quad_id), edge_vertex({quad.z, quad.w})}); + } else { + tquads.push_back({quad.x, edge_vertex({quad.x, quad.y}), + quad_vertex(quad_id), edge_vertex({quad.z, quad.x})}); + tquads.push_back({quad.y, edge_vertex({quad.y, quad.z}), + quad_vertex(quad_id), edge_vertex({quad.x, quad.y})}); + tquads.push_back({quad.z, edge_vertex({quad.z, quad.x}), + quad_vertex(quad_id), edge_vertex({quad.y, quad.z})}); + } + } -// Generate a point set along a quad. -void make_points(vector& points, vector& positions, - vector& normals, vector& texcoords, vector& radius, - const vec2i& steps, const vec2f& size, const vec2f& uvscale, - const vec2f& rad) { - auto quads = vector{}; - make_rect(quads, positions, normals, texcoords, steps, size, uvscale); - points.resize(positions.size()); - for (auto i = 0; i < (int)positions.size(); i++) points[i] = i; - radius.resize(positions.size()); - for (auto i = 0; i < (int)texcoords.size(); i++) - radius[i] = lerp(rad.x, rad.y, texcoords[i].y / uvscale.y); + // split boundary + auto tboundary = vector{}; + tboundary.reserve(boundary.size()); + for (auto& edge : boundary) { + tboundary.push_back({edge.x, edge_vertex(edge)}); + tboundary.push_back({edge_vertex(edge), edge.y}); + } + + // setup creases ----------------------------------- + auto tcrease_edges = vector{}; + auto tcrease_verts = vector{}; + if (lock_boundary) { + for (auto& b : tboundary) { + tcrease_verts.push_back(b.x); + tcrease_verts.push_back(b.y); + } + } else { + for (auto& b : tboundary) tcrease_edges.push_back(b); + } + + // define vertices valence --------------------------- + auto tvert_val = vector(tvertices.size(), 2); + for (auto& edge : tboundary) { + tvert_val[edge.x] = (lock_boundary) ? 0 : 1; + tvert_val[edge.y] = (lock_boundary) ? 0 : 1; + } + + // averaging pass ---------------------------------- + auto avert = vector(tvertices.size(), T()); + auto acount = vector(tvertices.size(), 0); + for (auto& point : tcrease_verts) { + if (tvert_val[point] != 0) continue; + avert[point] += tvertices[point]; + acount[point] += 1; + } + for (auto& edge : tcrease_edges) { + auto centroid = (tvertices[edge.x] + tvertices[edge.y]) / 2; + for (auto vid : {edge.x, edge.y}) { + if (tvert_val[vid] != 1) continue; + avert[vid] += centroid; + acount[vid] += 1; + } + } + for (auto& quad : tquads) { + auto centroid = (tvertices[quad.x] + tvertices[quad.y] + tvertices[quad.z] + + tvertices[quad.w]) / + 4; + for (auto vid : {quad.x, quad.y, quad.z, quad.w}) { + if (tvert_val[vid] != 2) continue; + avert[vid] += centroid; + acount[vid] += 1; + } + } + for (auto i = 0; i < (int)tvertices.size(); i++) avert[i] /= (float)acount[i]; + + // correction pass ---------------------------------- + // p = p + (avg_p - p) * (4/avg_count) + for (auto i = 0; i < (int)tvertices.size(); i++) { + if (tvert_val[i] != 2) continue; + avert[i] = tvertices[i] + + (avert[i] - tvertices[i]) * (4 / (float)acount[i]); + } + tvertices = avert; + + // done + return {tquads, tvertices}; } -// Generate a point set. -void make_random_points(vector& points, vector& positions, - vector& normals, vector& texcoords, vector& radius, - int num, const vec3f& size, float uvscale, float point_radius, - uint64_t seed) { - make_points(points, positions, normals, texcoords, radius, num, uvscale, - point_radius); - auto rng = make_rng(seed); - for (auto& position : positions) position = (2 * rand3f(rng) - 1) * size; - for (auto& texcoord : texcoords) texcoord = rand2f(rng); +pair, vector> subdivide_lines( + const vector& lines, const vector& vertices) { + return subdivide_lines_impl(lines, vertices); +} +pair, vector> subdivide_lines( + const vector& lines, const vector& vertices) { + return subdivide_lines_impl(lines, vertices); +} +pair, vector> subdivide_lines( + const vector& lines, const vector& vertices) { + return subdivide_lines_impl(lines, vertices); +} +pair, vector> subdivide_lines( + const vector& lines, const vector& vertices) { + return subdivide_lines_impl(lines, vertices); } -// Make a bezier circle. Returns bezier, pos. -void make_bezier_circle( - vector& beziers, vector& positions, float size) { - // constant from http://spencermortensen.com/articles/bezier-circle/ - const auto c = 0.551915024494f; - static auto circle_pos = vector{{1, 0, 0}, {1, c, 0}, {c, 1, 0}, - {0, 1, 0}, {-c, 1, 0}, {-1, c, 0}, {-1, 0, 0}, {-1, -c, 0}, {-c, -1, 0}, - {0, -1, 0}, {c, -1, 0}, {1, -c, 0}}; - static auto circle_beziers = vector{ - {0, 1, 2, 3}, {3, 4, 5, 6}, {6, 7, 8, 9}, {9, 10, 11, 0}}; - positions = circle_pos; - beziers = circle_beziers; - for (auto& p : positions) p *= size; +pair, vector> subdivide_triangles( + const vector& triangles, const vector& vertices) { + return subdivide_triangles_impl(triangles, vertices); +} +pair, vector> subdivide_triangles( + const vector& triangles, const vector& vertices) { + return subdivide_triangles_impl(triangles, vertices); +} +pair, vector> subdivide_triangles( + const vector& triangles, const vector& vertices) { + return subdivide_triangles_impl(triangles, vertices); +} +pair, vector> subdivide_triangles( + const vector& triangles, const vector& vertices) { + return subdivide_triangles_impl(triangles, vertices); } -// Make fvquad -void make_fvrect(vector& quadspos, vector& quadsnorm, - vector& quadstexcoord, vector& positions, - vector& normals, vector& texcoords, const vec2i& steps, - const vec2f& size, const vec2f& uvscale) { - make_rect(quadspos, positions, normals, texcoords, steps, size, uvscale); - quadsnorm = quadspos; - quadstexcoord = quadspos; +pair, vector> subdivide_quads( + const vector& quads, const vector& vertices) { + return subdivide_quads_impl(quads, vertices); } -void make_fvbox(vector& quadspos, vector& quadsnorm, - vector& quadstexcoord, vector& positions, - vector& normals, vector& texcoords, const vec3i& steps, - const vec3f& size, const vec3f& uvscale) { - make_box(quadspos, positions, normals, texcoords, steps, size, uvscale); - quadsnorm = quadspos; - quadstexcoord = quadspos; - std::tie(quadspos, positions) = weld_quads( - quadspos, positions, 0.1f * min(size) / max(steps)); +pair, vector> subdivide_quads( + const vector& quads, const vector& vertices) { + return subdivide_quads_impl(quads, vertices); } -void make_fvsphere(vector& quadspos, vector& quadsnorm, - vector& quadstexcoord, vector& positions, - vector& normals, vector& texcoords, int steps, float size, - float uvscale) { - make_fvbox(quadspos, quadsnorm, quadstexcoord, positions, normals, texcoords, - {steps, steps, steps}, {size, size, size}, {uvscale, uvscale, uvscale}); - quadsnorm = quadspos; - normals = positions; - for (auto& n : normals) n = normalize(n); +pair, vector> subdivide_quads( + const vector& quads, const vector& vertices) { + return subdivide_quads_impl(quads, vertices); +} +pair, vector> subdivide_quads( + const vector& quads, const vector& vertices) { + return subdivide_quads_impl(quads, vertices); } -vector suzanne_positions = vector{{0.4375, 0.1640625, 0.765625}, - {-0.4375, 0.1640625, 0.765625}, {0.5, 0.09375, 0.6875}, - {-0.5, 0.09375, 0.6875}, {0.546875, 0.0546875, 0.578125}, - {-0.546875, 0.0546875, 0.578125}, {0.3515625, -0.0234375, 0.6171875}, - {-0.3515625, -0.0234375, 0.6171875}, {0.3515625, 0.03125, 0.71875}, - {-0.3515625, 0.03125, 0.71875}, {0.3515625, 0.1328125, 0.78125}, - {-0.3515625, 0.1328125, 0.78125}, {0.2734375, 0.1640625, 0.796875}, - {-0.2734375, 0.1640625, 0.796875}, {0.203125, 0.09375, 0.7421875}, - {-0.203125, 0.09375, 0.7421875}, {0.15625, 0.0546875, 0.6484375}, - {-0.15625, 0.0546875, 0.6484375}, {0.078125, 0.2421875, 0.65625}, - {-0.078125, 0.2421875, 0.65625}, {0.140625, 0.2421875, 0.7421875}, - {-0.140625, 0.2421875, 0.7421875}, {0.2421875, 0.2421875, 0.796875}, - {-0.2421875, 0.2421875, 0.796875}, {0.2734375, 0.328125, 0.796875}, - {-0.2734375, 0.328125, 0.796875}, {0.203125, 0.390625, 0.7421875}, - {-0.203125, 0.390625, 0.7421875}, {0.15625, 0.4375, 0.6484375}, - {-0.15625, 0.4375, 0.6484375}, {0.3515625, 0.515625, 0.6171875}, - {-0.3515625, 0.515625, 0.6171875}, {0.3515625, 0.453125, 0.71875}, - {-0.3515625, 0.453125, 0.71875}, {0.3515625, 0.359375, 0.78125}, - {-0.3515625, 0.359375, 0.78125}, {0.4375, 0.328125, 0.765625}, - {-0.4375, 0.328125, 0.765625}, {0.5, 0.390625, 0.6875}, - {-0.5, 0.390625, 0.6875}, {0.546875, 0.4375, 0.578125}, - {-0.546875, 0.4375, 0.578125}, {0.625, 0.2421875, 0.5625}, - {-0.625, 0.2421875, 0.5625}, {0.5625, 0.2421875, 0.671875}, - {-0.5625, 0.2421875, 0.671875}, {0.46875, 0.2421875, 0.7578125}, - {-0.46875, 0.2421875, 0.7578125}, {0.4765625, 0.2421875, 0.7734375}, - {-0.4765625, 0.2421875, 0.7734375}, {0.4453125, 0.3359375, 0.78125}, - {-0.4453125, 0.3359375, 0.78125}, {0.3515625, 0.375, 0.8046875}, - {-0.3515625, 0.375, 0.8046875}, {0.265625, 0.3359375, 0.8203125}, - {-0.265625, 0.3359375, 0.8203125}, {0.2265625, 0.2421875, 0.8203125}, - {-0.2265625, 0.2421875, 0.8203125}, {0.265625, 0.15625, 0.8203125}, - {-0.265625, 0.15625, 0.8203125}, {0.3515625, 0.2421875, 0.828125}, - {-0.3515625, 0.2421875, 0.828125}, {0.3515625, 0.1171875, 0.8046875}, - {-0.3515625, 0.1171875, 0.8046875}, {0.4453125, 0.15625, 0.78125}, - {-0.4453125, 0.15625, 0.78125}, {0.0, 0.4296875, 0.7421875}, - {0.0, 0.3515625, 0.8203125}, {0.0, -0.6796875, 0.734375}, - {0.0, -0.3203125, 0.78125}, {0.0, -0.1875, 0.796875}, - {0.0, -0.7734375, 0.71875}, {0.0, 0.40625, 0.6015625}, - {0.0, 0.5703125, 0.5703125}, {0.0, 0.8984375, -0.546875}, - {0.0, 0.5625, -0.8515625}, {0.0, 0.0703125, -0.828125}, - {0.0, -0.3828125, -0.3515625}, {0.203125, -0.1875, 0.5625}, - {-0.203125, -0.1875, 0.5625}, {0.3125, -0.4375, 0.5703125}, - {-0.3125, -0.4375, 0.5703125}, {0.3515625, -0.6953125, 0.5703125}, - {-0.3515625, -0.6953125, 0.5703125}, {0.3671875, -0.890625, 0.53125}, - {-0.3671875, -0.890625, 0.53125}, {0.328125, -0.9453125, 0.5234375}, - {-0.328125, -0.9453125, 0.5234375}, {0.1796875, -0.96875, 0.5546875}, - {-0.1796875, -0.96875, 0.5546875}, {0.0, -0.984375, 0.578125}, - {0.4375, -0.140625, 0.53125}, {-0.4375, -0.140625, 0.53125}, - {0.6328125, -0.0390625, 0.5390625}, {-0.6328125, -0.0390625, 0.5390625}, - {0.828125, 0.1484375, 0.4453125}, {-0.828125, 0.1484375, 0.4453125}, - {0.859375, 0.4296875, 0.59375}, {-0.859375, 0.4296875, 0.59375}, - {0.7109375, 0.484375, 0.625}, {-0.7109375, 0.484375, 0.625}, - {0.4921875, 0.6015625, 0.6875}, {-0.4921875, 0.6015625, 0.6875}, - {0.3203125, 0.7578125, 0.734375}, {-0.3203125, 0.7578125, 0.734375}, - {0.15625, 0.71875, 0.7578125}, {-0.15625, 0.71875, 0.7578125}, - {0.0625, 0.4921875, 0.75}, {-0.0625, 0.4921875, 0.75}, - {0.1640625, 0.4140625, 0.7734375}, {-0.1640625, 0.4140625, 0.7734375}, - {0.125, 0.3046875, 0.765625}, {-0.125, 0.3046875, 0.765625}, - {0.203125, 0.09375, 0.7421875}, {-0.203125, 0.09375, 0.7421875}, - {0.375, 0.015625, 0.703125}, {-0.375, 0.015625, 0.703125}, - {0.4921875, 0.0625, 0.671875}, {-0.4921875, 0.0625, 0.671875}, - {0.625, 0.1875, 0.6484375}, {-0.625, 0.1875, 0.6484375}, - {0.640625, 0.296875, 0.6484375}, {-0.640625, 0.296875, 0.6484375}, - {0.6015625, 0.375, 0.6640625}, {-0.6015625, 0.375, 0.6640625}, - {0.4296875, 0.4375, 0.71875}, {-0.4296875, 0.4375, 0.71875}, - {0.25, 0.46875, 0.7578125}, {-0.25, 0.46875, 0.7578125}, - {0.0, -0.765625, 0.734375}, {0.109375, -0.71875, 0.734375}, - {-0.109375, -0.71875, 0.734375}, {0.1171875, -0.8359375, 0.7109375}, - {-0.1171875, -0.8359375, 0.7109375}, {0.0625, -0.8828125, 0.6953125}, - {-0.0625, -0.8828125, 0.6953125}, {0.0, -0.890625, 0.6875}, - {0.0, -0.1953125, 0.75}, {0.0, -0.140625, 0.7421875}, - {0.1015625, -0.1484375, 0.7421875}, {-0.1015625, -0.1484375, 0.7421875}, - {0.125, -0.2265625, 0.75}, {-0.125, -0.2265625, 0.75}, - {0.0859375, -0.2890625, 0.7421875}, {-0.0859375, -0.2890625, 0.7421875}, - {0.3984375, -0.046875, 0.671875}, {-0.3984375, -0.046875, 0.671875}, - {0.6171875, 0.0546875, 0.625}, {-0.6171875, 0.0546875, 0.625}, - {0.7265625, 0.203125, 0.6015625}, {-0.7265625, 0.203125, 0.6015625}, - {0.7421875, 0.375, 0.65625}, {-0.7421875, 0.375, 0.65625}, - {0.6875, 0.4140625, 0.7265625}, {-0.6875, 0.4140625, 0.7265625}, - {0.4375, 0.546875, 0.796875}, {-0.4375, 0.546875, 0.796875}, - {0.3125, 0.640625, 0.8359375}, {-0.3125, 0.640625, 0.8359375}, - {0.203125, 0.6171875, 0.8515625}, {-0.203125, 0.6171875, 0.8515625}, - {0.1015625, 0.4296875, 0.84375}, {-0.1015625, 0.4296875, 0.84375}, - {0.125, -0.1015625, 0.8125}, {-0.125, -0.1015625, 0.8125}, - {0.2109375, -0.4453125, 0.7109375}, {-0.2109375, -0.4453125, 0.7109375}, - {0.25, -0.703125, 0.6875}, {-0.25, -0.703125, 0.6875}, - {0.265625, -0.8203125, 0.6640625}, {-0.265625, -0.8203125, 0.6640625}, - {0.234375, -0.9140625, 0.6328125}, {-0.234375, -0.9140625, 0.6328125}, - {0.1640625, -0.9296875, 0.6328125}, {-0.1640625, -0.9296875, 0.6328125}, - {0.0, -0.9453125, 0.640625}, {0.0, 0.046875, 0.7265625}, - {0.0, 0.2109375, 0.765625}, {0.328125, 0.4765625, 0.7421875}, - {-0.328125, 0.4765625, 0.7421875}, {0.1640625, 0.140625, 0.75}, - {-0.1640625, 0.140625, 0.75}, {0.1328125, 0.2109375, 0.7578125}, - {-0.1328125, 0.2109375, 0.7578125}, {0.1171875, -0.6875, 0.734375}, - {-0.1171875, -0.6875, 0.734375}, {0.078125, -0.4453125, 0.75}, - {-0.078125, -0.4453125, 0.75}, {0.0, -0.4453125, 0.75}, - {0.0, -0.328125, 0.7421875}, {0.09375, -0.2734375, 0.78125}, - {-0.09375, -0.2734375, 0.78125}, {0.1328125, -0.2265625, 0.796875}, - {-0.1328125, -0.2265625, 0.796875}, {0.109375, -0.1328125, 0.78125}, - {-0.109375, -0.1328125, 0.78125}, {0.0390625, -0.125, 0.78125}, - {-0.0390625, -0.125, 0.78125}, {0.0, -0.203125, 0.828125}, - {0.046875, -0.1484375, 0.8125}, {-0.046875, -0.1484375, 0.8125}, - {0.09375, -0.15625, 0.8125}, {-0.09375, -0.15625, 0.8125}, - {0.109375, -0.2265625, 0.828125}, {-0.109375, -0.2265625, 0.828125}, - {0.078125, -0.25, 0.8046875}, {-0.078125, -0.25, 0.8046875}, - {0.0, -0.2890625, 0.8046875}, {0.2578125, -0.3125, 0.5546875}, - {-0.2578125, -0.3125, 0.5546875}, {0.1640625, -0.2421875, 0.7109375}, - {-0.1640625, -0.2421875, 0.7109375}, {0.1796875, -0.3125, 0.7109375}, - {-0.1796875, -0.3125, 0.7109375}, {0.234375, -0.25, 0.5546875}, - {-0.234375, -0.25, 0.5546875}, {0.0, -0.875, 0.6875}, - {0.046875, -0.8671875, 0.6875}, {-0.046875, -0.8671875, 0.6875}, - {0.09375, -0.8203125, 0.7109375}, {-0.09375, -0.8203125, 0.7109375}, - {0.09375, -0.7421875, 0.7265625}, {-0.09375, -0.7421875, 0.7265625}, - {0.0, -0.78125, 0.65625}, {0.09375, -0.75, 0.6640625}, - {-0.09375, -0.75, 0.6640625}, {0.09375, -0.8125, 0.640625}, - {-0.09375, -0.8125, 0.640625}, {0.046875, -0.8515625, 0.6328125}, - {-0.046875, -0.8515625, 0.6328125}, {0.0, -0.859375, 0.6328125}, - {0.171875, 0.21875, 0.78125}, {-0.171875, 0.21875, 0.78125}, - {0.1875, 0.15625, 0.7734375}, {-0.1875, 0.15625, 0.7734375}, - {0.3359375, 0.4296875, 0.7578125}, {-0.3359375, 0.4296875, 0.7578125}, - {0.2734375, 0.421875, 0.7734375}, {-0.2734375, 0.421875, 0.7734375}, - {0.421875, 0.3984375, 0.7734375}, {-0.421875, 0.3984375, 0.7734375}, - {0.5625, 0.3515625, 0.6953125}, {-0.5625, 0.3515625, 0.6953125}, - {0.5859375, 0.2890625, 0.6875}, {-0.5859375, 0.2890625, 0.6875}, - {0.578125, 0.1953125, 0.6796875}, {-0.578125, 0.1953125, 0.6796875}, - {0.4765625, 0.1015625, 0.71875}, {-0.4765625, 0.1015625, 0.71875}, - {0.375, 0.0625, 0.7421875}, {-0.375, 0.0625, 0.7421875}, - {0.2265625, 0.109375, 0.78125}, {-0.2265625, 0.109375, 0.78125}, - {0.1796875, 0.296875, 0.78125}, {-0.1796875, 0.296875, 0.78125}, - {0.2109375, 0.375, 0.78125}, {-0.2109375, 0.375, 0.78125}, - {0.234375, 0.359375, 0.7578125}, {-0.234375, 0.359375, 0.7578125}, - {0.1953125, 0.296875, 0.7578125}, {-0.1953125, 0.296875, 0.7578125}, - {0.2421875, 0.125, 0.7578125}, {-0.2421875, 0.125, 0.7578125}, - {0.375, 0.0859375, 0.7265625}, {-0.375, 0.0859375, 0.7265625}, - {0.4609375, 0.1171875, 0.703125}, {-0.4609375, 0.1171875, 0.703125}, - {0.546875, 0.2109375, 0.671875}, {-0.546875, 0.2109375, 0.671875}, - {0.5546875, 0.28125, 0.671875}, {-0.5546875, 0.28125, 0.671875}, - {0.53125, 0.3359375, 0.6796875}, {-0.53125, 0.3359375, 0.6796875}, - {0.4140625, 0.390625, 0.75}, {-0.4140625, 0.390625, 0.75}, - {0.28125, 0.3984375, 0.765625}, {-0.28125, 0.3984375, 0.765625}, - {0.3359375, 0.40625, 0.75}, {-0.3359375, 0.40625, 0.75}, - {0.203125, 0.171875, 0.75}, {-0.203125, 0.171875, 0.75}, - {0.1953125, 0.2265625, 0.75}, {-0.1953125, 0.2265625, 0.75}, - {0.109375, 0.4609375, 0.609375}, {-0.109375, 0.4609375, 0.609375}, - {0.1953125, 0.6640625, 0.6171875}, {-0.1953125, 0.6640625, 0.6171875}, - {0.3359375, 0.6875, 0.59375}, {-0.3359375, 0.6875, 0.59375}, - {0.484375, 0.5546875, 0.5546875}, {-0.484375, 0.5546875, 0.5546875}, - {0.6796875, 0.453125, 0.4921875}, {-0.6796875, 0.453125, 0.4921875}, - {0.796875, 0.40625, 0.4609375}, {-0.796875, 0.40625, 0.4609375}, - {0.7734375, 0.1640625, 0.375}, {-0.7734375, 0.1640625, 0.375}, - {0.6015625, 0.0, 0.4140625}, {-0.6015625, 0.0, 0.4140625}, - {0.4375, -0.09375, 0.46875}, {-0.4375, -0.09375, 0.46875}, - {0.0, 0.8984375, 0.2890625}, {0.0, 0.984375, -0.078125}, - {0.0, -0.1953125, -0.671875}, {0.0, -0.4609375, 0.1875}, - {0.0, -0.9765625, 0.4609375}, {0.0, -0.8046875, 0.34375}, - {0.0, -0.5703125, 0.3203125}, {0.0, -0.484375, 0.28125}, - {0.8515625, 0.234375, 0.0546875}, {-0.8515625, 0.234375, 0.0546875}, - {0.859375, 0.3203125, -0.046875}, {-0.859375, 0.3203125, -0.046875}, - {0.7734375, 0.265625, -0.4375}, {-0.7734375, 0.265625, -0.4375}, - {0.4609375, 0.4375, -0.703125}, {-0.4609375, 0.4375, -0.703125}, - {0.734375, -0.046875, 0.0703125}, {-0.734375, -0.046875, 0.0703125}, - {0.59375, -0.125, -0.1640625}, {-0.59375, -0.125, -0.1640625}, - {0.640625, -0.0078125, -0.4296875}, {-0.640625, -0.0078125, -0.4296875}, - {0.3359375, 0.0546875, -0.6640625}, {-0.3359375, 0.0546875, -0.6640625}, - {0.234375, -0.3515625, 0.40625}, {-0.234375, -0.3515625, 0.40625}, - {0.1796875, -0.4140625, 0.2578125}, {-0.1796875, -0.4140625, 0.2578125}, - {0.2890625, -0.7109375, 0.3828125}, {-0.2890625, -0.7109375, 0.3828125}, - {0.25, -0.5, 0.390625}, {-0.25, -0.5, 0.390625}, - {0.328125, -0.9140625, 0.3984375}, {-0.328125, -0.9140625, 0.3984375}, - {0.140625, -0.7578125, 0.3671875}, {-0.140625, -0.7578125, 0.3671875}, - {0.125, -0.5390625, 0.359375}, {-0.125, -0.5390625, 0.359375}, - {0.1640625, -0.9453125, 0.4375}, {-0.1640625, -0.9453125, 0.4375}, - {0.21875, -0.28125, 0.4296875}, {-0.21875, -0.28125, 0.4296875}, - {0.2109375, -0.2265625, 0.46875}, {-0.2109375, -0.2265625, 0.46875}, - {0.203125, -0.171875, 0.5}, {-0.203125, -0.171875, 0.5}, - {0.2109375, -0.390625, 0.1640625}, {-0.2109375, -0.390625, 0.1640625}, - {0.296875, -0.3125, -0.265625}, {-0.296875, -0.3125, -0.265625}, - {0.34375, -0.1484375, -0.5390625}, {-0.34375, -0.1484375, -0.5390625}, - {0.453125, 0.8671875, -0.3828125}, {-0.453125, 0.8671875, -0.3828125}, - {0.453125, 0.9296875, -0.0703125}, {-0.453125, 0.9296875, -0.0703125}, - {0.453125, 0.8515625, 0.234375}, {-0.453125, 0.8515625, 0.234375}, - {0.4609375, 0.5234375, 0.4296875}, {-0.4609375, 0.5234375, 0.4296875}, - {0.7265625, 0.40625, 0.3359375}, {-0.7265625, 0.40625, 0.3359375}, - {0.6328125, 0.453125, 0.28125}, {-0.6328125, 0.453125, 0.28125}, - {0.640625, 0.703125, 0.0546875}, {-0.640625, 0.703125, 0.0546875}, - {0.796875, 0.5625, 0.125}, {-0.796875, 0.5625, 0.125}, - {0.796875, 0.6171875, -0.1171875}, {-0.796875, 0.6171875, -0.1171875}, - {0.640625, 0.75, -0.1953125}, {-0.640625, 0.75, -0.1953125}, - {0.640625, 0.6796875, -0.4453125}, {-0.640625, 0.6796875, -0.4453125}, - {0.796875, 0.5390625, -0.359375}, {-0.796875, 0.5390625, -0.359375}, - {0.6171875, 0.328125, -0.5859375}, {-0.6171875, 0.328125, -0.5859375}, - {0.484375, 0.0234375, -0.546875}, {-0.484375, 0.0234375, -0.546875}, - {0.8203125, 0.328125, -0.203125}, {-0.8203125, 0.328125, -0.203125}, - {0.40625, -0.171875, 0.1484375}, {-0.40625, -0.171875, 0.1484375}, - {0.4296875, -0.1953125, -0.2109375}, {-0.4296875, -0.1953125, -0.2109375}, - {0.890625, 0.40625, -0.234375}, {-0.890625, 0.40625, -0.234375}, - {0.7734375, -0.140625, -0.125}, {-0.7734375, -0.140625, -0.125}, - {1.0390625, -0.1015625, -0.328125}, {-1.0390625, -0.1015625, -0.328125}, - {1.28125, 0.0546875, -0.4296875}, {-1.28125, 0.0546875, -0.4296875}, - {1.3515625, 0.3203125, -0.421875}, {-1.3515625, 0.3203125, -0.421875}, - {1.234375, 0.5078125, -0.421875}, {-1.234375, 0.5078125, -0.421875}, - {1.0234375, 0.4765625, -0.3125}, {-1.0234375, 0.4765625, -0.3125}, - {1.015625, 0.4140625, -0.2890625}, {-1.015625, 0.4140625, -0.2890625}, - {1.1875, 0.4375, -0.390625}, {-1.1875, 0.4375, -0.390625}, - {1.265625, 0.2890625, -0.40625}, {-1.265625, 0.2890625, -0.40625}, - {1.2109375, 0.078125, -0.40625}, {-1.2109375, 0.078125, -0.40625}, - {1.03125, -0.0390625, -0.3046875}, {-1.03125, -0.0390625, -0.3046875}, - {0.828125, -0.0703125, -0.1328125}, {-0.828125, -0.0703125, -0.1328125}, - {0.921875, 0.359375, -0.21875}, {-0.921875, 0.359375, -0.21875}, - {0.9453125, 0.3046875, -0.2890625}, {-0.9453125, 0.3046875, -0.2890625}, - {0.8828125, -0.0234375, -0.2109375}, {-0.8828125, -0.0234375, -0.2109375}, - {1.0390625, 0.0, -0.3671875}, {-1.0390625, 0.0, -0.3671875}, - {1.1875, 0.09375, -0.4453125}, {-1.1875, 0.09375, -0.4453125}, - {1.234375, 0.25, -0.4453125}, {-1.234375, 0.25, -0.4453125}, - {1.171875, 0.359375, -0.4375}, {-1.171875, 0.359375, -0.4375}, - {1.0234375, 0.34375, -0.359375}, {-1.0234375, 0.34375, -0.359375}, - {0.84375, 0.2890625, -0.2109375}, {-0.84375, 0.2890625, -0.2109375}, - {0.8359375, 0.171875, -0.2734375}, {-0.8359375, 0.171875, -0.2734375}, - {0.7578125, 0.09375, -0.2734375}, {-0.7578125, 0.09375, -0.2734375}, - {0.8203125, 0.0859375, -0.2734375}, {-0.8203125, 0.0859375, -0.2734375}, - {0.84375, 0.015625, -0.2734375}, {-0.84375, 0.015625, -0.2734375}, - {0.8125, -0.015625, -0.2734375}, {-0.8125, -0.015625, -0.2734375}, - {0.7265625, 0.0, -0.0703125}, {-0.7265625, 0.0, -0.0703125}, - {0.71875, -0.0234375, -0.171875}, {-0.71875, -0.0234375, -0.171875}, - {0.71875, 0.0390625, -0.1875}, {-0.71875, 0.0390625, -0.1875}, - {0.796875, 0.203125, -0.2109375}, {-0.796875, 0.203125, -0.2109375}, - {0.890625, 0.2421875, -0.265625}, {-0.890625, 0.2421875, -0.265625}, - {0.890625, 0.234375, -0.3203125}, {-0.890625, 0.234375, -0.3203125}, - {0.8125, -0.015625, -0.3203125}, {-0.8125, -0.015625, -0.3203125}, - {0.8515625, 0.015625, -0.3203125}, {-0.8515625, 0.015625, -0.3203125}, - {0.828125, 0.078125, -0.3203125}, {-0.828125, 0.078125, -0.3203125}, - {0.765625, 0.09375, -0.3203125}, {-0.765625, 0.09375, -0.3203125}, - {0.84375, 0.171875, -0.3203125}, {-0.84375, 0.171875, -0.3203125}, - {1.0390625, 0.328125, -0.4140625}, {-1.0390625, 0.328125, -0.4140625}, - {1.1875, 0.34375, -0.484375}, {-1.1875, 0.34375, -0.484375}, - {1.2578125, 0.2421875, -0.4921875}, {-1.2578125, 0.2421875, -0.4921875}, - {1.2109375, 0.0859375, -0.484375}, {-1.2109375, 0.0859375, -0.484375}, - {1.046875, 0.0, -0.421875}, {-1.046875, 0.0, -0.421875}, - {0.8828125, -0.015625, -0.265625}, {-0.8828125, -0.015625, -0.265625}, - {0.953125, 0.2890625, -0.34375}, {-0.953125, 0.2890625, -0.34375}, - {0.890625, 0.109375, -0.328125}, {-0.890625, 0.109375, -0.328125}, - {0.9375, 0.0625, -0.3359375}, {-0.9375, 0.0625, -0.3359375}, - {1.0, 0.125, -0.3671875}, {-1.0, 0.125, -0.3671875}, - {0.9609375, 0.171875, -0.3515625}, {-0.9609375, 0.171875, -0.3515625}, - {1.015625, 0.234375, -0.375}, {-1.015625, 0.234375, -0.375}, - {1.0546875, 0.1875, -0.3828125}, {-1.0546875, 0.1875, -0.3828125}, - {1.109375, 0.2109375, -0.390625}, {-1.109375, 0.2109375, -0.390625}, - {1.0859375, 0.2734375, -0.390625}, {-1.0859375, 0.2734375, -0.390625}, - {1.0234375, 0.4375, -0.484375}, {-1.0234375, 0.4375, -0.484375}, - {1.25, 0.46875, -0.546875}, {-1.25, 0.46875, -0.546875}, - {1.3671875, 0.296875, -0.5}, {-1.3671875, 0.296875, -0.5}, - {1.3125, 0.0546875, -0.53125}, {-1.3125, 0.0546875, -0.53125}, - {1.0390625, -0.0859375, -0.4921875}, {-1.0390625, -0.0859375, -0.4921875}, - {0.7890625, -0.125, -0.328125}, {-0.7890625, -0.125, -0.328125}, - {0.859375, 0.3828125, -0.3828125}, {-0.859375, 0.3828125, -0.3828125}}; +pair, vector> subdivide_beziers( + const vector& beziers, const vector& vertices) { + return subdivide_beziers_impl(beziers, vertices); +} +pair, vector> subdivide_beziers( + const vector& beziers, const vector& vertices) { + return subdivide_beziers_impl(beziers, vertices); +} +pair, vector> subdivide_beziers( + const vector& beziers, const vector& vertices) { + return subdivide_beziers_impl(beziers, vertices); +} +pair, vector> subdivide_beziers( + const vector& beziers, const vector& vertices) { + return subdivide_beziers_impl(beziers, vertices); +} + +pair, vector> subdivide_catmullclark( + const vector& quads, const vector& vertices, + bool lock_boundary) { + return subdivide_catmullclark_impl(quads, vertices, lock_boundary); +} +pair, vector> subdivide_catmullclark( + const vector& quads, const vector& vertices, + bool lock_boundary) { + return subdivide_catmullclark_impl(quads, vertices, lock_boundary); +} +pair, vector> subdivide_catmullclark( + const vector& quads, const vector& vertices, + bool lock_boundary) { + return subdivide_catmullclark_impl(quads, vertices, lock_boundary); +} +pair, vector> subdivide_catmullclark( + const vector& quads, const vector& vertices, + bool lock_boundary) { + return subdivide_catmullclark_impl(quads, vertices, lock_boundary); +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMPLEMENTATION OF SHAPE SAMPLING +// ----------------------------------------------------------------------------- +namespace yocto { + +// Pick a point in a point set uniformly. +int sample_points(int npoints, float re) { return sample_uniform(npoints, re); } +int sample_points(const vector& cdf, float re) { + return sample_discrete(cdf, re); +} +vector sample_points_cdf(int npoints) { + auto cdf = vector(npoints); + for (auto i = 0; i < cdf.size(); i++) cdf[i] = 1 + (i != 0 ? cdf[i - 1] : 0); + return cdf; +} +void sample_points_cdf(vector& cdf, int npoints) { + for (auto i = 0; i < cdf.size(); i++) cdf[i] = 1 + (i != 0 ? cdf[i - 1] : 0); +} + +// Pick a point on lines uniformly. +pair sample_lines(const vector& cdf, float re, float ru) { + return {sample_discrete(cdf, re), ru}; +} +vector sample_lines_cdf( + const vector& lines, const vector& positions) { + auto cdf = vector(lines.size()); + for (auto i = 0; i < cdf.size(); i++) { + auto& l = lines[i]; + auto w = line_length(positions[l.x], positions[l.y]); + cdf[i] = w + (i != 0 ? cdf[i - 1] : 0); + } + return cdf; +} +void sample_lines_cdf(vector& cdf, const vector& lines, + const vector& positions) { + for (auto i = 0; i < cdf.size(); i++) { + auto& l = lines[i]; + auto w = line_length(positions[l.x], positions[l.y]); + cdf[i] = w + (i != 0 ? cdf[i - 1] : 0); + } +} + +// Pick a point on a triangle mesh uniformly. +pair sample_triangles( + const vector& cdf, float re, const vec2f& ruv) { + return {sample_discrete(cdf, re), sample_triangle(ruv)}; +} +vector sample_triangles_cdf( + const vector& triangles, const vector& positions) { + auto cdf = vector(triangles.size()); + for (auto i = 0; i < cdf.size(); i++) { + auto& t = triangles[i]; + auto w = triangle_area(positions[t.x], positions[t.y], positions[t.z]); + cdf[i] = w + (i != 0 ? cdf[i - 1] : 0); + } + return cdf; +} +void sample_triangles_cdf(vector& cdf, const vector& triangles, + const vector& positions) { + for (auto i = 0; i < cdf.size(); i++) { + auto& t = triangles[i]; + auto w = triangle_area(positions[t.x], positions[t.y], positions[t.z]); + cdf[i] = w + (i != 0 ? cdf[i - 1] : 0); + } +} + +// Pick a point on a quad mesh uniformly. +pair sample_quads( + const vector& cdf, float re, const vec2f& ruv) { + return {sample_discrete(cdf, re), ruv}; +} +pair sample_quads(const vector& quads, + const vector& cdf, float re, const vec2f& ruv) { + auto element = sample_discrete(cdf, re); + if (quads[element].z == quads[element].w) { + return {element, sample_triangle(ruv)}; + } else { + return {element, ruv}; + } +} +vector sample_quads_cdf( + const vector& quads, const vector& positions) { + auto cdf = vector(quads.size()); + for (auto i = 0; i < cdf.size(); i++) { + auto& q = quads[i]; + auto w = quad_area( + positions[q.x], positions[q.y], positions[q.z], positions[q.w]); + cdf[i] = w + (i ? cdf[i - 1] : 0); + } + return cdf; +} +void sample_quads_cdf(vector& cdf, const vector& quads, + const vector& positions) { + for (auto i = 0; i < cdf.size(); i++) { + auto& q = quads[i]; + auto w = quad_area( + positions[q.x], positions[q.y], positions[q.z], positions[q.w]); + cdf[i] = w + (i ? cdf[i - 1] : 0); + } +} + +// Samples a set of points over a triangle mesh uniformly. The rng function +// takes the point index and returns vec3f numbers uniform directibuted in +// [0,1]^3. unorm and texcoord are optional. +void sample_triangles(vector& sampled_positions, + vector& sampled_normals, vector& sampled_texcoords, + const vector& triangles, const vector& positions, + const vector& normals, const vector& texcoords, int npoints, + int seed) { + sampled_positions.resize(npoints); + sampled_normals.resize(npoints); + sampled_texcoords.resize(npoints); + auto cdf = sample_triangles_cdf(triangles, positions); + auto rng = make_rng(seed); + for (auto i = 0; i < npoints; i++) { + auto sample = sample_triangles(cdf, rand1f(rng), rand2f(rng)); + auto& t = triangles[sample.first]; + auto uv = sample.second; + sampled_positions[i] = interpolate_triangle( + positions[t.x], positions[t.y], positions[t.z], uv); + if (!sampled_normals.empty()) { + sampled_normals[i] = normalize( + interpolate_triangle(normals[t.x], normals[t.y], normals[t.z], uv)); + } else { + sampled_normals[i] = triangle_normal( + positions[t.x], positions[t.y], positions[t.z]); + } + if (!sampled_texcoords.empty()) { + sampled_texcoords[i] = interpolate_triangle( + texcoords[t.x], texcoords[t.y], texcoords[t.z], uv); + } else { + sampled_texcoords[i] = zero2f; + } + } +} + +// Samples a set of points over a triangle mesh uniformly. The rng function +// takes the point index and returns vec3f numbers uniform directibuted in +// [0,1]^3. unorm and texcoord are optional. +void sample_quads(vector& sampled_positions, + vector& sampled_normals, vector& sampled_texcoords, + const vector& quads, const vector& positions, + const vector& normals, const vector& texcoords, int npoints, + int seed) { + sampled_positions.resize(npoints); + sampled_normals.resize(npoints); + sampled_texcoords.resize(npoints); + auto cdf = sample_quads_cdf(quads, positions); + auto rng = make_rng(seed); + for (auto i = 0; i < npoints; i++) { + auto sample = sample_quads(cdf, rand1f(rng), rand2f(rng)); + auto& q = quads[sample.first]; + auto uv = sample.second; + sampled_positions[i] = interpolate_quad( + positions[q.x], positions[q.y], positions[q.z], positions[q.w], uv); + if (!sampled_normals.empty()) { + sampled_normals[i] = normalize(interpolate_quad( + normals[q.x], normals[q.y], normals[q.z], normals[q.w], uv)); + } else { + sampled_normals[i] = quad_normal( + positions[q.x], positions[q.y], positions[q.z], positions[q.w]); + } + if (!sampled_texcoords.empty()) { + sampled_texcoords[i] = interpolate_quad( + texcoords[q.x], texcoords[q.y], texcoords[q.z], texcoords[q.w], uv); + } else { + sampled_texcoords[i] = zero2f; + } + } +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// EXAMPLE SHAPES +// ----------------------------------------------------------------------------- +namespace yocto { + +// Make a quad. +void make_rect(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + const vec2f& scale, const vec2f& uvscale) { + positions.resize((steps.x + 1) * (steps.y + 1)); + normals.resize((steps.x + 1) * (steps.y + 1)); + texcoords.resize((steps.x + 1) * (steps.y + 1)); + for (auto j = 0; j <= steps.y; j++) { + for (auto i = 0; i <= steps.x; i++) { + auto uv = vec2f{i / (float)steps.x, j / (float)steps.y}; + positions[j * (steps.x + 1) + i] = { + (2 * uv.x - 1) * scale.x, (2 * uv.y - 1) * scale.y, 0}; + normals[j * (steps.x + 1) + i] = {0, 0, 1}; + texcoords[j * (steps.x + 1) + i] = vec2f{uv.x, 1 - uv.y} * uvscale; + } + } + + quads.resize(steps.x * steps.y); + for (auto j = 0; j < steps.y; j++) { + for (auto i = 0; i < steps.x; i++) { + quads[j * steps.x + i] = {j * (steps.x + 1) + i, + j * (steps.x + 1) + i + 1, (j + 1) * (steps.x + 1) + i + 1, + (j + 1) * (steps.x + 1) + i}; + } + } +} + +void make_bulged_rect(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + const vec2f& scale, const vec2f& uvscale, float height) { + make_rect(quads, positions, normals, texcoords, steps, scale, uvscale); + if (height != 0) { + height = min(height, min(scale)); + auto radius = (1 + height * height) / (2 * height); + auto center = vec3f{0, 0, -radius + height}; + for (auto i = 0; i < positions.size(); i++) { + auto pn = normalize(positions[i] - center); + positions[i] = center + pn * radius; + normals[i] = pn; + } + } +} + +// Make a quad. +void make_recty(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + const vec2f& scale, const vec2f& uvscale) { + make_rect(quads, positions, normals, texcoords, steps, scale, uvscale); + for (auto& position : positions) + position = {position.x, position.z, -position.y}; + for (auto& normal : normals) normal = {normal.x, normal.z, normal.y}; +} + +void make_bulged_recty(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + const vec2f& scale, const vec2f& uvscale, float height) { + make_bulged_rect( + quads, positions, normals, texcoords, steps, scale, uvscale, height); + for (auto& position : positions) + position = {position.x, position.z, -position.y}; + for (auto& normal : normals) normal = {normal.x, normal.z, normal.y}; +} + +// Make a cube. +void make_box(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec3i& steps, + const vec3f& scale, const vec3f& uvscale) { + quads.clear(); + positions.clear(); + normals.clear(); + texcoords.clear(); + auto qquads = vector{}; + auto qpositions = vector{}; + auto qnormals = vector{}; + auto qtexturecoords = vector{}; + // + z + make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.x, steps.y}, + {scale.x, scale.y}, {uvscale.x, uvscale.y}); + for (auto& p : qpositions) p = {p.x, p.y, scale.z}; + for (auto& n : qnormals) n = {0, 0, 1}; + merge_quads(quads, positions, normals, texcoords, qquads, qpositions, + qnormals, qtexturecoords); + // - z + make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.x, steps.y}, + {scale.x, scale.y}, {uvscale.x, uvscale.y}); + for (auto& p : qpositions) p = {-p.x, p.y, -scale.z}; + for (auto& n : qnormals) n = {0, 0, -1}; + merge_quads(quads, positions, normals, texcoords, qquads, qpositions, + qnormals, qtexturecoords); + // + x + make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.z, steps.y}, + {scale.z, scale.y}, {uvscale.z, uvscale.y}); + for (auto& p : qpositions) p = {scale.x, p.y, -p.x}; + for (auto& n : qnormals) n = {1, 0, 0}; + merge_quads(quads, positions, normals, texcoords, qquads, qpositions, + qnormals, qtexturecoords); + // - x + make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.z, steps.y}, + {scale.z, scale.y}, {uvscale.z, uvscale.y}); + for (auto& p : qpositions) p = {-scale.x, p.y, p.x}; + for (auto& n : qnormals) n = {-1, 0, 0}; + merge_quads(quads, positions, normals, texcoords, qquads, qpositions, + qnormals, qtexturecoords); + // + y + make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.x, steps.z}, + {scale.x, scale.z}, {uvscale.x, uvscale.z}); + for (auto i = 0; i < qpositions.size(); i++) { + qpositions[i] = {qpositions[i].x, scale.y, -qpositions[i].y}; + qnormals[i] = {0, 1, 0}; + } + merge_quads(quads, positions, normals, texcoords, qquads, qpositions, + qnormals, qtexturecoords); + // - y + make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.x, steps.z}, + {scale.x, scale.z}, {uvscale.x, uvscale.z}); + for (auto i = 0; i < qpositions.size(); i++) { + qpositions[i] = {qpositions[i].x, -scale.y, qpositions[i].y}; + qnormals[i] = {0, -1, 0}; + } + merge_quads(quads, positions, normals, texcoords, qquads, qpositions, + qnormals, qtexturecoords); +} + +void make_rounded_box(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec3i& steps, + const vec3f& scale, const vec3f& uvscale, float radius) { + make_box(quads, positions, normals, texcoords, steps, scale, uvscale); + if (radius != 0) { + radius = min(radius, min(scale)); + auto c = scale - radius; + for (auto i = 0; i < positions.size(); i++) { + auto pc = vec3f{ + abs(positions[i].x), abs(positions[i].y), abs(positions[i].z)}; + auto ps = vec3f{positions[i].x < 0 ? -1.0f : 1.0f, + positions[i].y < 0 ? -1.0f : 1.0f, positions[i].z < 0 ? -1.0f : 1.0f}; + if (pc.x >= c.x && pc.y >= c.y && pc.z >= c.z) { + auto pn = normalize(pc - c); + positions[i] = c + radius * pn; + normals[i] = pn; + } else if (pc.x >= c.x && pc.y >= c.y) { + auto pn = normalize((pc - c) * vec3f{1, 1, 0}); + positions[i] = {c.x + radius * pn.x, c.y + radius * pn.y, pc.z}; + normals[i] = pn; + } else if (pc.x >= c.x && pc.z >= c.z) { + auto pn = normalize((pc - c) * vec3f{1, 0, 1}); + positions[i] = {c.x + radius * pn.x, pc.y, c.z + radius * pn.z}; + normals[i] = pn; + } else if (pc.y >= c.y && pc.z >= c.z) { + auto pn = normalize((pc - c) * vec3f{0, 1, 1}); + positions[i] = {pc.x, c.y + radius * pn.y, c.z + radius * pn.z}; + normals[i] = pn; + } else { + continue; + } + positions[i] *= ps; + normals[i] *= ps; + } + } +} + +void make_rect_stack(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec3i& steps, + const vec3f& scale, const vec2f& uvscale) { + auto qquads = vector{}; + auto qpositions = vector{}; + auto qnormals = vector{}; + auto qtexturecoords = vector{}; + for (auto i = 0; i <= steps.z; i++) { + make_rect(qquads, qpositions, qnormals, qtexturecoords, {steps.x, steps.y}, + {scale.x, scale.y}, uvscale); + for (auto& p : qpositions) p.z = (-1 + 2 * (float)i / steps.z) * scale.z; + merge_quads(quads, positions, normals, texcoords, qquads, qpositions, + qnormals, qtexturecoords); + } +} + +void make_floor(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + const vec2f& scale, const vec2f& uvscale) { + make_rect(quads, positions, normals, texcoords, steps, scale, uvscale); + for (auto& position : positions) + position = {position.x, position.z, -position.y}; + for (auto& normal : normals) normal = {normal.x, normal.z, normal.y}; +} + +void make_bent_floor(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + const vec2f& scale, const vec2f& uvscale, float radius) { + make_floor(quads, positions, normals, texcoords, steps, scale, uvscale); + if (radius != 0) { + radius = min(radius, scale.y); + auto start = (scale.y - radius) / 2; + auto end = start + radius; + for (auto i = 0; i < positions.size(); i++) { + if (positions[i].z < -end) { + positions[i] = {positions[i].x, -positions[i].z - end + radius, -end}; + normals[i] = {0, 0, 1}; + } else if (positions[i].z < -start && positions[i].z >= -end) { + auto phi = (pif / 2) * (-positions[i].z - start) / radius; + positions[i] = {positions[i].x, -cos(phi) * radius + radius, + -sin(phi) * radius - start}; + normals[i] = {0, cos(phi), sin(phi)}; + } else { + } + } + } +} + +// Generate a sphere +void make_sphere(vector& quads, vector& positions, + vector& normals, vector& texcoords, int steps, float scale, + float uvscale) { + make_box(quads, positions, normals, texcoords, {steps, steps, steps}, + {scale, scale, scale}, {uvscale, uvscale, uvscale}); + for (auto& p : positions) p = normalize(p) * scale; + normals = positions; + for (auto& n : normals) n = normalize(n); +} + +// Generate a uvsphere +void make_uvsphere(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + float scale, const vec2f& uvscale) { + make_rect(quads, positions, normals, texcoords, steps, {1, 1}, {1, 1}); + for (auto i = 0; i < positions.size(); i++) { + auto uv = texcoords[i]; + auto a = vec2f{2 * pif * uv.x, pif * (1 - uv.y)}; + positions[i] = vec3f{cos(a.x) * sin(a.y), sin(a.x) * sin(a.y), cos(a.y)} * + scale; + normals[i] = normalize(positions[i]); + texcoords[i] = uv * uvscale; + } +} + +void make_capped_uvsphere(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + float scale, const vec2f& uvscale, float cap) { + make_uvsphere(quads, positions, normals, texcoords, steps, scale, uvscale); + if (cap != 0) { + cap = min(cap, scale / 2); + auto zflip = (scale - cap); + for (auto i = 0; i < positions.size(); i++) { + if (positions[i].z > zflip) { + positions[i].z = 2 * zflip - positions[i].z; + normals[i].x = -normals[i].x; + normals[i].y = -normals[i].y; + } else if (positions[i].z < -zflip) { + positions[i].z = 2 * (-zflip) - positions[i].z; + normals[i].x = -normals[i].x; + normals[i].y = -normals[i].y; + } + } + } +} + +// Generate a uvsphere +void make_uvspherey(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + float scale, const vec2f& uvscale) { + make_uvsphere(quads, positions, normals, texcoords, steps, scale, uvscale); + for (auto& position : positions) + position = {position.x, position.z, position.y}; + for (auto& normal : normals) normal = {normal.x, normal.z, normal.y}; + for (auto& texcoord : texcoords) texcoord = {texcoord.x, 1 - texcoord.y}; + for (auto& quad : quads) quad = {quad.x, quad.w, quad.z, quad.y}; +} + +void make_capped_uvspherey(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + float scale, const vec2f& uvscale, float cap) { + make_capped_uvsphere( + quads, positions, normals, texcoords, steps, scale, uvscale, cap); + for (auto& position : positions) + position = {position.x, position.z, position.y}; + for (auto& normal : normals) normal = {normal.x, normal.z, normal.y}; + for (auto& texcoord : texcoords) texcoord = {texcoord.x, 1 - texcoord.y}; + for (auto& quad : quads) quad = {quad.x, quad.w, quad.z, quad.y}; +} + +// Generate a disk +void make_disk(vector& quads, vector& positions, + vector& normals, vector& texcoords, int steps, float scale, + float uvscale) { + make_rect(quads, positions, normals, texcoords, {steps, steps}, {1, 1}, + {uvscale, uvscale}); + for (auto& position : positions) { + // Analytical Methods for Squaring the Disc, by C. Fong + // https://arxiv.org/abs/1509.06344 + auto xy = vec2f{position.x, position.y}; + auto uv = vec2f{ + xy.x * sqrt(1 - xy.y * xy.y / 2), xy.y * sqrt(1 - xy.x * xy.x / 2)}; + position = vec3f{uv.x, uv.y, 0} * scale; + } +} + +void make_bulged_disk(vector& quads, vector& positions, + vector& normals, vector& texcoords, int steps, float scale, + float uvscale, float height) { + make_disk(quads, positions, normals, texcoords, steps, scale, uvscale); + if (height != 0) { + height = min(height, scale); + auto radius = (1 + height * height) / (2 * height); + auto center = vec3f{0, 0, -radius + height}; + for (auto i = 0; i < positions.size(); i++) { + auto pn = normalize(positions[i] - center); + positions[i] = center + pn * radius; + normals[i] = pn; + } + } +} + +// Generate a uvdisk +void make_uvdisk(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + float scale, const vec2f& uvscale) { + make_rect(quads, positions, normals, texcoords, steps, {1, 1}, {1, 1}); + for (auto i = 0; i < positions.size(); i++) { + auto uv = texcoords[i]; + auto phi = 2 * pif * uv.x; + positions[i] = vec3f{cos(phi) * uv.y, sin(phi) * uv.y, 0} * scale; + normals[i] = {0, 0, 1}; + texcoords[i] = uv * uvscale; + } +} + +// Generate a uvcylinder +void make_uvcylinder(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec3i& steps, + const vec2f& scale, const vec3f& uvscale) { + auto qquads = vector{}; + auto qpositions = vector{}; + auto qnormals = vector{}; + auto qtexcoords = vector{}; + // side + make_rect(qquads, qpositions, qnormals, qtexcoords, {steps.x, steps.y}, + {1, 1}, {1, 1}); + for (auto i = 0; i < qpositions.size(); i++) { + auto uv = qtexcoords[i]; + auto phi = 2 * pif * uv.x; + qpositions[i] = { + cos(phi) * scale.x, sin(phi) * scale.x, (2 * uv.y - 1) * scale.y}; + qnormals[i] = {cos(phi), sin(phi), 0}; + qtexcoords[i] = uv * vec2f{uvscale.x, uvscale.y}; + } + for (auto& quad : qquads) quad = {quad.x, quad.w, quad.z, quad.y}; + merge_quads(quads, positions, normals, texcoords, qquads, qpositions, + qnormals, qtexcoords); + // top + make_rect(qquads, qpositions, qnormals, qtexcoords, {steps.x, steps.z}, + {1, 1}, {1, 1}); + for (auto i = 0; i < qpositions.size(); i++) { + auto uv = qtexcoords[i]; + auto phi = 2 * pif * uv.x; + qpositions[i] = {cos(phi) * uv.y * scale.x, sin(phi) * uv.y * scale.x, 0}; + qnormals[i] = {0, 0, 1}; + qtexcoords[i] = uv * vec2f{uvscale.x, uvscale.z}; + qpositions[i].z = scale.y; + } + merge_quads(quads, positions, normals, texcoords, qquads, qpositions, + qnormals, qtexcoords); + // bottom + make_rect(qquads, qpositions, qnormals, qtexcoords, {steps.x, steps.z}, + {1, 1}, {1, 1}); + for (auto i = 0; i < qpositions.size(); i++) { + auto uv = qtexcoords[i]; + auto phi = 2 * pif * uv.x; + qpositions[i] = {cos(phi) * uv.y * scale.x, sin(phi) * uv.y * scale.x, 0}; + qnormals[i] = {0, 0, 1}; + qtexcoords[i] = uv * vec2f{uvscale.x, uvscale.z}; + qpositions[i].z = -scale.y; + qnormals[i] = -qnormals[i]; + } + for (auto& qquad : qquads) swap(qquad.x, qquad.z); + merge_quads(quads, positions, normals, texcoords, qquads, qpositions, + qnormals, qtexcoords); +} + +// Generate a uvcylinder +void make_rounded_uvcylinder(vector& quads, vector& positions, + vector& normals, vector& texcoords, const vec3i& steps, + const vec2f& scale, const vec3f& uvscale, float radius) { + make_uvcylinder(quads, positions, normals, texcoords, steps, scale, uvscale); + if (radius != 0) { + radius = min(radius, min(scale)); + auto c = scale - radius; + for (auto i = 0; i < positions.size(); i++) { + auto phi = atan2(positions[i].y, positions[i].x); + auto r = length(vec2f{positions[i].x, positions[i].y}); + auto z = positions[i].z; + auto pc = vec2f{r, abs(z)}; + auto ps = (z < 0) ? -1.0f : 1.0f; + if (pc.x >= c.x && pc.y >= c.y) { + auto pn = normalize(pc - c); + positions[i] = {cos(phi) * (c.x + radius * pn.x), + sin(phi) * (c.x + radius * pn.x), ps * (c.y + radius * pn.y)}; + normals[i] = {cos(phi) * pn.x, sin(phi) * pn.x, ps * pn.y}; + } else { + continue; + } + } + } +} + +// Generate lines set along a quad. +void make_lines(vector& lines, vector& positions, + vector& normals, vector& texcoords, vector& radius, + const vec2i& steps, const vec2f& size, const vec2f& uvscale, + const vec2f& rad) { + positions.resize((steps.x + 1) * steps.y); + normals.resize((steps.x + 1) * steps.y); + texcoords.resize((steps.x + 1) * steps.y); + radius.resize((steps.x + 1) * steps.y); + if (steps.y > 1) { + for (auto j = 0; j < steps.y; j++) { + for (auto i = 0; i <= steps.x; i++) { + auto uv = vec2f{i / (float)steps.x, j / (float)(steps.y - 1)}; + positions[j * (steps.x + 1) + i] = { + (uv.x - 0.5f) * size.x, (uv.y - 0.5f) * size.y, 0}; + normals[j * (steps.x + 1) + i] = {1, 0, 0}; + texcoords[j * (steps.x + 1) + i] = uv * uvscale; + radius[j * (steps.x + 1) + i] = lerp(rad.x, rad.y, uv.x); + } + } + } else { + for (auto i = 0; i <= steps.x; i++) { + auto uv = vec2f{i / (float)steps.x, 0}; + positions[i] = {(uv.x - 0.5f) * size.x, 0, 0}; + normals[i] = {1, 0, 0}; + texcoords[i] = uv * uvscale; + radius[i] = lerp(rad.x, rad.y, uv.x); + } + } -vector suzanne_quads = vector{{46, 0, 2, 44}, {3, 1, 47, 45}, - {44, 2, 4, 42}, {5, 3, 45, 43}, {2, 8, 6, 4}, {7, 9, 3, 5}, {0, 10, 8, 2}, - {9, 11, 1, 3}, {10, 12, 14, 8}, {15, 13, 11, 9}, {8, 14, 16, 6}, - {17, 15, 9, 7}, {14, 20, 18, 16}, {19, 21, 15, 17}, {12, 22, 20, 14}, - {21, 23, 13, 15}, {22, 24, 26, 20}, {27, 25, 23, 21}, {20, 26, 28, 18}, - {29, 27, 21, 19}, {26, 32, 30, 28}, {31, 33, 27, 29}, {24, 34, 32, 26}, - {33, 35, 25, 27}, {34, 36, 38, 32}, {39, 37, 35, 33}, {32, 38, 40, 30}, - {41, 39, 33, 31}, {38, 44, 42, 40}, {43, 45, 39, 41}, {36, 46, 44, 38}, - {45, 47, 37, 39}, {46, 36, 50, 48}, {51, 37, 47, 49}, {36, 34, 52, 50}, - {53, 35, 37, 51}, {34, 24, 54, 52}, {55, 25, 35, 53}, {24, 22, 56, 54}, - {57, 23, 25, 55}, {22, 12, 58, 56}, {59, 13, 23, 57}, {12, 10, 62, 58}, - {63, 11, 13, 59}, {10, 0, 64, 62}, {65, 1, 11, 63}, {0, 46, 48, 64}, - {49, 47, 1, 65}, {88, 173, 175, 90}, {175, 174, 89, 90}, {86, 171, 173, 88}, - {174, 172, 87, 89}, {84, 169, 171, 86}, {172, 170, 85, 87}, - {82, 167, 169, 84}, {170, 168, 83, 85}, {80, 165, 167, 82}, - {168, 166, 81, 83}, {78, 91, 145, 163}, {146, 92, 79, 164}, - {91, 93, 147, 145}, {148, 94, 92, 146}, {93, 95, 149, 147}, - {150, 96, 94, 148}, {95, 97, 151, 149}, {152, 98, 96, 150}, - {97, 99, 153, 151}, {154, 100, 98, 152}, {99, 101, 155, 153}, - {156, 102, 100, 154}, {101, 103, 157, 155}, {158, 104, 102, 156}, - {103, 105, 159, 157}, {160, 106, 104, 158}, {105, 107, 161, 159}, - {162, 108, 106, 160}, {107, 66, 67, 161}, {67, 66, 108, 162}, - {109, 127, 159, 161}, {160, 128, 110, 162}, {127, 178, 157, 159}, - {158, 179, 128, 160}, {125, 155, 157, 178}, {158, 156, 126, 179}, - {123, 153, 155, 125}, {156, 154, 124, 126}, {121, 151, 153, 123}, - {154, 152, 122, 124}, {119, 149, 151, 121}, {152, 150, 120, 122}, - {117, 147, 149, 119}, {150, 148, 118, 120}, {115, 145, 147, 117}, - {148, 146, 116, 118}, {113, 163, 145, 115}, {146, 164, 114, 116}, - {113, 180, 176, 163}, {176, 181, 114, 164}, {109, 161, 67, 111}, - {67, 162, 110, 112}, {111, 67, 177, 182}, {177, 67, 112, 183}, - {176, 180, 182, 177}, {183, 181, 176, 177}, {134, 136, 175, 173}, - {175, 136, 135, 174}, {132, 134, 173, 171}, {174, 135, 133, 172}, - {130, 132, 171, 169}, {172, 133, 131, 170}, {165, 186, 184, 167}, - {185, 187, 166, 168}, {130, 169, 167, 184}, {168, 170, 131, 185}, - {143, 189, 188, 186}, {188, 189, 144, 187}, {184, 186, 188, 68}, - {188, 187, 185, 68}, {129, 130, 184, 68}, {185, 131, 129, 68}, - {141, 192, 190, 143}, {191, 193, 142, 144}, {139, 194, 192, 141}, - {193, 195, 140, 142}, {138, 196, 194, 139}, {195, 197, 138, 140}, - {137, 70, 196, 138}, {197, 70, 137, 138}, {189, 143, 190, 69}, - {191, 144, 189, 69}, {69, 190, 205, 207}, {206, 191, 69, 207}, - {70, 198, 199, 196}, {200, 198, 70, 197}, {196, 199, 201, 194}, - {202, 200, 197, 195}, {194, 201, 203, 192}, {204, 202, 195, 193}, - {192, 203, 205, 190}, {206, 204, 193, 191}, {198, 203, 201, 199}, - {202, 204, 198, 200}, {198, 207, 205, 203}, {206, 207, 198, 204}, - {138, 139, 163, 176}, {164, 140, 138, 176}, {139, 141, 210, 163}, - {211, 142, 140, 164}, {141, 143, 212, 210}, {213, 144, 142, 211}, - {143, 186, 165, 212}, {166, 187, 144, 213}, {80, 208, 212, 165}, - {213, 209, 81, 166}, {208, 214, 210, 212}, {211, 215, 209, 213}, - {78, 163, 210, 214}, {211, 164, 79, 215}, {130, 129, 71, 221}, - {71, 129, 131, 222}, {132, 130, 221, 219}, {222, 131, 133, 220}, - {134, 132, 219, 217}, {220, 133, 135, 218}, {136, 134, 217, 216}, - {218, 135, 136, 216}, {216, 217, 228, 230}, {229, 218, 216, 230}, - {217, 219, 226, 228}, {227, 220, 218, 229}, {219, 221, 224, 226}, - {225, 222, 220, 227}, {221, 71, 223, 224}, {223, 71, 222, 225}, - {223, 230, 228, 224}, {229, 230, 223, 225}, {182, 180, 233, 231}, - {234, 181, 183, 232}, {111, 182, 231, 253}, {232, 183, 112, 254}, - {109, 111, 253, 255}, {254, 112, 110, 256}, {180, 113, 251, 233}, - {252, 114, 181, 234}, {113, 115, 249, 251}, {250, 116, 114, 252}, - {115, 117, 247, 249}, {248, 118, 116, 250}, {117, 119, 245, 247}, - {246, 120, 118, 248}, {119, 121, 243, 245}, {244, 122, 120, 246}, - {121, 123, 241, 243}, {242, 124, 122, 244}, {123, 125, 239, 241}, - {240, 126, 124, 242}, {125, 178, 235, 239}, {236, 179, 126, 240}, - {178, 127, 237, 235}, {238, 128, 179, 236}, {127, 109, 255, 237}, - {256, 110, 128, 238}, {237, 255, 257, 275}, {258, 256, 238, 276}, - {235, 237, 275, 277}, {276, 238, 236, 278}, {239, 235, 277, 273}, - {278, 236, 240, 274}, {241, 239, 273, 271}, {274, 240, 242, 272}, - {243, 241, 271, 269}, {272, 242, 244, 270}, {245, 243, 269, 267}, - {270, 244, 246, 268}, {247, 245, 267, 265}, {268, 246, 248, 266}, - {249, 247, 265, 263}, {266, 248, 250, 264}, {251, 249, 263, 261}, - {264, 250, 252, 262}, {233, 251, 261, 279}, {262, 252, 234, 280}, - {255, 253, 259, 257}, {260, 254, 256, 258}, {253, 231, 281, 259}, - {282, 232, 254, 260}, {231, 233, 279, 281}, {280, 234, 232, 282}, - {66, 107, 283, 72}, {284, 108, 66, 72}, {107, 105, 285, 283}, - {286, 106, 108, 284}, {105, 103, 287, 285}, {288, 104, 106, 286}, - {103, 101, 289, 287}, {290, 102, 104, 288}, {101, 99, 291, 289}, - {292, 100, 102, 290}, {99, 97, 293, 291}, {294, 98, 100, 292}, - {97, 95, 295, 293}, {296, 96, 98, 294}, {95, 93, 297, 295}, - {298, 94, 96, 296}, {93, 91, 299, 297}, {300, 92, 94, 298}, - {307, 308, 327, 337}, {328, 308, 307, 338}, {306, 307, 337, 335}, - {338, 307, 306, 336}, {305, 306, 335, 339}, {336, 306, 305, 340}, - {88, 90, 305, 339}, {305, 90, 89, 340}, {86, 88, 339, 333}, - {340, 89, 87, 334}, {84, 86, 333, 329}, {334, 87, 85, 330}, - {82, 84, 329, 331}, {330, 85, 83, 332}, {329, 335, 337, 331}, - {338, 336, 330, 332}, {329, 333, 339, 335}, {340, 334, 330, 336}, - {325, 331, 337, 327}, {338, 332, 326, 328}, {80, 82, 331, 325}, - {332, 83, 81, 326}, {208, 341, 343, 214}, {344, 342, 209, 215}, - {80, 325, 341, 208}, {342, 326, 81, 209}, {78, 214, 343, 345}, - {344, 215, 79, 346}, {78, 345, 299, 91}, {300, 346, 79, 92}, - {76, 323, 351, 303}, {352, 324, 76, 303}, {303, 351, 349, 77}, - {350, 352, 303, 77}, {77, 349, 347, 304}, {348, 350, 77, 304}, - {304, 347, 327, 308}, {328, 348, 304, 308}, {325, 327, 347, 341}, - {348, 328, 326, 342}, {295, 297, 317, 309}, {318, 298, 296, 310}, - {75, 315, 323, 76}, {324, 316, 75, 76}, {301, 357, 355, 302}, - {356, 358, 301, 302}, {302, 355, 353, 74}, {354, 356, 302, 74}, - {74, 353, 315, 75}, {316, 354, 74, 75}, {291, 293, 361, 363}, - {362, 294, 292, 364}, {363, 361, 367, 365}, {368, 362, 364, 366}, - {365, 367, 369, 371}, {370, 368, 366, 372}, {371, 369, 375, 373}, - {376, 370, 372, 374}, {313, 377, 373, 375}, {374, 378, 314, 376}, - {315, 353, 373, 377}, {374, 354, 316, 378}, {353, 355, 371, 373}, - {372, 356, 354, 374}, {355, 357, 365, 371}, {366, 358, 356, 372}, - {357, 359, 363, 365}, {364, 360, 358, 366}, {289, 291, 363, 359}, - {364, 292, 290, 360}, {73, 359, 357, 301}, {358, 360, 73, 301}, - {283, 285, 287, 289}, {288, 286, 284, 290}, {283, 289, 359, 73}, - {360, 290, 284, 73}, {293, 295, 309, 361}, {310, 296, 294, 362}, - {309, 311, 367, 361}, {368, 312, 310, 362}, {311, 381, 369, 367}, - {370, 382, 312, 368}, {313, 375, 369, 381}, {370, 376, 314, 382}, - {347, 349, 385, 383}, {386, 350, 348, 384}, {317, 383, 385, 319}, - {386, 384, 318, 320}, {297, 299, 383, 317}, {384, 300, 298, 318}, - {299, 343, 341, 383}, {342, 344, 300, 384}, {313, 321, 379, 377}, - {380, 322, 314, 378}, {315, 377, 379, 323}, {380, 378, 316, 324}, - {319, 385, 379, 321}, {380, 386, 320, 322}, {349, 351, 379, 385}, - {380, 352, 350, 386}, {399, 387, 413, 401}, {414, 388, 400, 402}, - {399, 401, 403, 397}, {404, 402, 400, 398}, {397, 403, 405, 395}, - {406, 404, 398, 396}, {395, 405, 407, 393}, {408, 406, 396, 394}, - {393, 407, 409, 391}, {410, 408, 394, 392}, {391, 409, 411, 389}, - {412, 410, 392, 390}, {409, 419, 417, 411}, {418, 420, 410, 412}, - {407, 421, 419, 409}, {420, 422, 408, 410}, {405, 423, 421, 407}, - {422, 424, 406, 408}, {403, 425, 423, 405}, {424, 426, 404, 406}, - {401, 427, 425, 403}, {426, 428, 402, 404}, {401, 413, 415, 427}, - {416, 414, 402, 428}, {317, 319, 443, 441}, {444, 320, 318, 442}, - {319, 389, 411, 443}, {412, 390, 320, 444}, {309, 317, 441, 311}, - {442, 318, 310, 312}, {381, 429, 413, 387}, {414, 430, 382, 388}, - {411, 417, 439, 443}, {440, 418, 412, 444}, {437, 445, 443, 439}, - {444, 446, 438, 440}, {433, 445, 437, 435}, {438, 446, 434, 436}, - {431, 447, 445, 433}, {446, 448, 432, 434}, {429, 447, 431, 449}, - {432, 448, 430, 450}, {413, 429, 449, 415}, {450, 430, 414, 416}, - {311, 447, 429, 381}, {430, 448, 312, 382}, {311, 441, 445, 447}, - {446, 442, 312, 448}, {415, 449, 451, 475}, {452, 450, 416, 476}, - {449, 431, 461, 451}, {462, 432, 450, 452}, {431, 433, 459, 461}, - {460, 434, 432, 462}, {433, 435, 457, 459}, {458, 436, 434, 460}, - {435, 437, 455, 457}, {456, 438, 436, 458}, {437, 439, 453, 455}, - {454, 440, 438, 456}, {439, 417, 473, 453}, {474, 418, 440, 454}, - {427, 415, 475, 463}, {476, 416, 428, 464}, {425, 427, 463, 465}, - {464, 428, 426, 466}, {423, 425, 465, 467}, {466, 426, 424, 468}, - {421, 423, 467, 469}, {468, 424, 422, 470}, {419, 421, 469, 471}, - {470, 422, 420, 472}, {417, 419, 471, 473}, {472, 420, 418, 474}, - {457, 455, 479, 477}, {480, 456, 458, 478}, {477, 479, 481, 483}, - {482, 480, 478, 484}, {483, 481, 487, 485}, {488, 482, 484, 486}, - {485, 487, 489, 491}, {490, 488, 486, 492}, {463, 475, 485, 491}, - {486, 476, 464, 492}, {451, 483, 485, 475}, {486, 484, 452, 476}, - {451, 461, 477, 483}, {478, 462, 452, 484}, {457, 477, 461, 459}, - {462, 478, 458, 460}, {453, 473, 479, 455}, {480, 474, 454, 456}, - {471, 481, 479, 473}, {480, 482, 472, 474}, {469, 487, 481, 471}, - {482, 488, 470, 472}, {467, 489, 487, 469}, {488, 490, 468, 470}, - {465, 491, 489, 467}, {490, 492, 466, 468}, {391, 389, 503, 501}, - {504, 390, 392, 502}, {393, 391, 501, 499}, {502, 392, 394, 500}, - {395, 393, 499, 497}, {500, 394, 396, 498}, {397, 395, 497, 495}, - {498, 396, 398, 496}, {399, 397, 495, 493}, {496, 398, 400, 494}, - {387, 399, 493, 505}, {494, 400, 388, 506}, {493, 501, 503, 505}, - {504, 502, 494, 506}, {493, 495, 499, 501}, {500, 496, 494, 502}, - {313, 381, 387, 505}, {388, 382, 314, 506}, {313, 505, 503, 321}, - {504, 506, 314, 322}, {319, 321, 503, 389}, {504, 322, 320, 390}, - // ttriangles - {60, 64, 48, 48}, {49, 65, 61, 61}, {62, 64, 60, 60}, {61, 65, 63, 63}, - {60, 58, 62, 62}, {63, 59, 61, 61}, {60, 56, 58, 58}, {59, 57, 61, 61}, - {60, 54, 56, 56}, {57, 55, 61, 61}, {60, 52, 54, 54}, {55, 53, 61, 61}, - {60, 50, 52, 52}, {53, 51, 61, 61}, {60, 48, 50, 50}, {51, 49, 61, 61}, - {224, 228, 226, 226}, {227, 229, 225, 255}, {72, 283, 73, 73}, - {73, 284, 72, 72}, {341, 347, 383, 383}, {384, 348, 342, 342}, - {299, 345, 343, 343}, {344, 346, 300, 300}, {323, 379, 351, 351}, - {352, 380, 324, 324}, {441, 443, 445, 445}, {446, 444, 442, 442}, - {463, 491, 465, 465}, {466, 492, 464, 464}, {495, 497, 499, 499}, - {500, 498, 496, 496}}; + lines.resize(steps.x * steps.y); + for (int j = 0; j < steps.y; j++) { + for (int i = 0; i < steps.x; i++) { + lines[j * steps.x + i] = { + j * (steps.x + 1) + i, j * (steps.x + 1) + i + 1}; + } + } +} + +// Generate a point at the origin. +void make_point(vector& points, vector& positions, + vector& normals, vector& texcoords, vector& radius, + float point_radius) { + points = {0}; + positions = {{0, 0, 0}}; + normals = {{0, 0, 1}}; + texcoords = {{0, 0}}; + radius = {point_radius}; +} + +// Generate a point set with points placed at the origin with texcoords +// varying along u. +void make_points(vector& points, vector& positions, + vector& normals, vector& texcoords, vector& radius, + int num, float uvscale, float point_radius) { + points.resize(num); + for (auto i = 0; i < num; i++) points[i] = i; + positions.assign(num, {0, 0, 0}); + normals.assign(num, {0, 0, 1}); + texcoords.assign(num, {0, 0}); + radius.assign(num, point_radius); + for (auto i = 0; i < texcoords.size(); i++) + texcoords[i] = {(float)i / (float)num, 0}; +} + +// Generate a point set along a quad. +void make_points(vector& points, vector& positions, + vector& normals, vector& texcoords, vector& radius, + const vec2i& steps, const vec2f& size, const vec2f& uvscale, + const vec2f& rad) { + auto quads = vector{}; + make_rect(quads, positions, normals, texcoords, steps, size, uvscale); + points.resize(positions.size()); + for (auto i = 0; i < (int)positions.size(); i++) points[i] = i; + radius.resize(positions.size()); + for (auto i = 0; i < (int)texcoords.size(); i++) + radius[i] = lerp(rad.x, rad.y, texcoords[i].y / uvscale.y); +} + +// Generate a point set. +void make_random_points(vector& points, vector& positions, + vector& normals, vector& texcoords, vector& radius, + int num, const vec3f& size, float uvscale, float point_radius, + uint64_t seed) { + make_points(points, positions, normals, texcoords, radius, num, uvscale, + point_radius); + auto rng = make_rng(seed); + for (auto& position : positions) position = (2 * rand3f(rng) - 1) * size; + for (auto& texcoord : texcoords) texcoord = rand2f(rng); +} + +// Make a bezier circle. Returns bezier, pos. +void make_bezier_circle( + vector& beziers, vector& positions, float size) { + // constant from http://spencermortensen.com/articles/bezier-circle/ + const auto c = 0.551915024494f; + static auto circle_pos = vector{{1, 0, 0}, {1, c, 0}, {c, 1, 0}, + {0, 1, 0}, {-c, 1, 0}, {-1, c, 0}, {-1, 0, 0}, {-1, -c, 0}, {-c, -1, 0}, + {0, -1, 0}, {c, -1, 0}, {1, -c, 0}}; + static auto circle_beziers = vector{ + {0, 1, 2, 3}, {3, 4, 5, 6}, {6, 7, 8, 9}, {9, 10, 11, 0}}; + positions = circle_pos; + beziers = circle_beziers; + for (auto& p : positions) p *= size; +} + +// Make fvquad +void make_fvrect(vector& quadspos, vector& quadsnorm, + vector& quadstexcoord, vector& positions, + vector& normals, vector& texcoords, const vec2i& steps, + const vec2f& size, const vec2f& uvscale) { + make_rect(quadspos, positions, normals, texcoords, steps, size, uvscale); + quadsnorm = quadspos; + quadstexcoord = quadspos; +} +void make_fvbox(vector& quadspos, vector& quadsnorm, + vector& quadstexcoord, vector& positions, + vector& normals, vector& texcoords, const vec3i& steps, + const vec3f& size, const vec3f& uvscale) { + make_box(quadspos, positions, normals, texcoords, steps, size, uvscale); + quadsnorm = quadspos; + quadstexcoord = quadspos; + std::tie(quadspos, positions) = weld_quads( + quadspos, positions, 0.1f * min(size) / max(steps)); +} +void make_fvsphere(vector& quadspos, vector& quadsnorm, + vector& quadstexcoord, vector& positions, + vector& normals, vector& texcoords, int steps, float size, + float uvscale) { + make_fvbox(quadspos, quadsnorm, quadstexcoord, positions, normals, texcoords, + {steps, steps, steps}, {size, size, size}, {uvscale, uvscale, uvscale}); + quadsnorm = quadspos; + normals = positions; + for (auto& n : normals) n = normalize(n); +} // Predefined meshes void make_monkey(vector& quads, vector& positions, float scale, int subdivisions) { + extern vector suzanne_positions; + extern vector suzanne_quads; + if (subdivisions == 0) { quads = suzanne_quads; positions = suzanne_positions; @@ -3254,29 +4231,455 @@ void lines_to_cylinders(vector& quads, vector& positions, } } -void lines_to_cylinders(vector& quads, vector& positions, - vector& normals, vector& texcoords, - const vector& lines, const vector& vertices, int steps, - float scale) { - auto cylinder_quads = vector{}; - auto cylinder_positions = vector{}; - auto cylinder_normals = vector{}; - auto cylinder_texcoords = vector{}; - make_uvcylinder(cylinder_quads, cylinder_positions, cylinder_normals, - cylinder_texcoords, {steps, 1, 1}, {scale, 1}, {1, 1, 1}); - for (auto& line : lines) { - auto frame = frame_fromz((vertices[line.x] + vertices[line.y]) / 2, - vertices[line.x] - vertices[line.y]); - auto length = distance(vertices[line.x], vertices[line.y]); - auto transformed_positions = cylinder_positions; - auto transformed_normals = cylinder_normals; - for (auto& position : transformed_positions) - position = transform_point(frame, position * vec3f{1, 1, length / 2}); - for (auto& normal : transformed_normals) - normal = transform_direction(frame, normal); - merge_quads(quads, positions, normals, texcoords, cylinder_quads, - transformed_positions, cylinder_normals, cylinder_texcoords); - } -} +void lines_to_cylinders(vector& quads, vector& positions, + vector& normals, vector& texcoords, + const vector& lines, const vector& vertices, int steps, + float scale) { + auto cylinder_quads = vector{}; + auto cylinder_positions = vector{}; + auto cylinder_normals = vector{}; + auto cylinder_texcoords = vector{}; + make_uvcylinder(cylinder_quads, cylinder_positions, cylinder_normals, + cylinder_texcoords, {steps, 1, 1}, {scale, 1}, {1, 1, 1}); + for (auto& line : lines) { + auto frame = frame_fromz((vertices[line.x] + vertices[line.y]) / 2, + vertices[line.x] - vertices[line.y]); + auto length = distance(vertices[line.x], vertices[line.y]); + auto transformed_positions = cylinder_positions; + auto transformed_normals = cylinder_normals; + for (auto& position : transformed_positions) + position = transform_point(frame, position * vec3f{1, 1, length / 2}); + for (auto& normal : transformed_normals) + normal = transform_direction(frame, normal); + merge_quads(quads, positions, normals, texcoords, cylinder_quads, + transformed_positions, cylinder_normals, cylinder_texcoords); + } +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// SHAPE DATA +// ----------------------------------------------------------------------------- +namespace yocto { + +vector suzanne_positions = vector{{0.4375, 0.1640625, 0.765625}, + {-0.4375, 0.1640625, 0.765625}, {0.5, 0.09375, 0.6875}, + {-0.5, 0.09375, 0.6875}, {0.546875, 0.0546875, 0.578125}, + {-0.546875, 0.0546875, 0.578125}, {0.3515625, -0.0234375, 0.6171875}, + {-0.3515625, -0.0234375, 0.6171875}, {0.3515625, 0.03125, 0.71875}, + {-0.3515625, 0.03125, 0.71875}, {0.3515625, 0.1328125, 0.78125}, + {-0.3515625, 0.1328125, 0.78125}, {0.2734375, 0.1640625, 0.796875}, + {-0.2734375, 0.1640625, 0.796875}, {0.203125, 0.09375, 0.7421875}, + {-0.203125, 0.09375, 0.7421875}, {0.15625, 0.0546875, 0.6484375}, + {-0.15625, 0.0546875, 0.6484375}, {0.078125, 0.2421875, 0.65625}, + {-0.078125, 0.2421875, 0.65625}, {0.140625, 0.2421875, 0.7421875}, + {-0.140625, 0.2421875, 0.7421875}, {0.2421875, 0.2421875, 0.796875}, + {-0.2421875, 0.2421875, 0.796875}, {0.2734375, 0.328125, 0.796875}, + {-0.2734375, 0.328125, 0.796875}, {0.203125, 0.390625, 0.7421875}, + {-0.203125, 0.390625, 0.7421875}, {0.15625, 0.4375, 0.6484375}, + {-0.15625, 0.4375, 0.6484375}, {0.3515625, 0.515625, 0.6171875}, + {-0.3515625, 0.515625, 0.6171875}, {0.3515625, 0.453125, 0.71875}, + {-0.3515625, 0.453125, 0.71875}, {0.3515625, 0.359375, 0.78125}, + {-0.3515625, 0.359375, 0.78125}, {0.4375, 0.328125, 0.765625}, + {-0.4375, 0.328125, 0.765625}, {0.5, 0.390625, 0.6875}, + {-0.5, 0.390625, 0.6875}, {0.546875, 0.4375, 0.578125}, + {-0.546875, 0.4375, 0.578125}, {0.625, 0.2421875, 0.5625}, + {-0.625, 0.2421875, 0.5625}, {0.5625, 0.2421875, 0.671875}, + {-0.5625, 0.2421875, 0.671875}, {0.46875, 0.2421875, 0.7578125}, + {-0.46875, 0.2421875, 0.7578125}, {0.4765625, 0.2421875, 0.7734375}, + {-0.4765625, 0.2421875, 0.7734375}, {0.4453125, 0.3359375, 0.78125}, + {-0.4453125, 0.3359375, 0.78125}, {0.3515625, 0.375, 0.8046875}, + {-0.3515625, 0.375, 0.8046875}, {0.265625, 0.3359375, 0.8203125}, + {-0.265625, 0.3359375, 0.8203125}, {0.2265625, 0.2421875, 0.8203125}, + {-0.2265625, 0.2421875, 0.8203125}, {0.265625, 0.15625, 0.8203125}, + {-0.265625, 0.15625, 0.8203125}, {0.3515625, 0.2421875, 0.828125}, + {-0.3515625, 0.2421875, 0.828125}, {0.3515625, 0.1171875, 0.8046875}, + {-0.3515625, 0.1171875, 0.8046875}, {0.4453125, 0.15625, 0.78125}, + {-0.4453125, 0.15625, 0.78125}, {0.0, 0.4296875, 0.7421875}, + {0.0, 0.3515625, 0.8203125}, {0.0, -0.6796875, 0.734375}, + {0.0, -0.3203125, 0.78125}, {0.0, -0.1875, 0.796875}, + {0.0, -0.7734375, 0.71875}, {0.0, 0.40625, 0.6015625}, + {0.0, 0.5703125, 0.5703125}, {0.0, 0.8984375, -0.546875}, + {0.0, 0.5625, -0.8515625}, {0.0, 0.0703125, -0.828125}, + {0.0, -0.3828125, -0.3515625}, {0.203125, -0.1875, 0.5625}, + {-0.203125, -0.1875, 0.5625}, {0.3125, -0.4375, 0.5703125}, + {-0.3125, -0.4375, 0.5703125}, {0.3515625, -0.6953125, 0.5703125}, + {-0.3515625, -0.6953125, 0.5703125}, {0.3671875, -0.890625, 0.53125}, + {-0.3671875, -0.890625, 0.53125}, {0.328125, -0.9453125, 0.5234375}, + {-0.328125, -0.9453125, 0.5234375}, {0.1796875, -0.96875, 0.5546875}, + {-0.1796875, -0.96875, 0.5546875}, {0.0, -0.984375, 0.578125}, + {0.4375, -0.140625, 0.53125}, {-0.4375, -0.140625, 0.53125}, + {0.6328125, -0.0390625, 0.5390625}, {-0.6328125, -0.0390625, 0.5390625}, + {0.828125, 0.1484375, 0.4453125}, {-0.828125, 0.1484375, 0.4453125}, + {0.859375, 0.4296875, 0.59375}, {-0.859375, 0.4296875, 0.59375}, + {0.7109375, 0.484375, 0.625}, {-0.7109375, 0.484375, 0.625}, + {0.4921875, 0.6015625, 0.6875}, {-0.4921875, 0.6015625, 0.6875}, + {0.3203125, 0.7578125, 0.734375}, {-0.3203125, 0.7578125, 0.734375}, + {0.15625, 0.71875, 0.7578125}, {-0.15625, 0.71875, 0.7578125}, + {0.0625, 0.4921875, 0.75}, {-0.0625, 0.4921875, 0.75}, + {0.1640625, 0.4140625, 0.7734375}, {-0.1640625, 0.4140625, 0.7734375}, + {0.125, 0.3046875, 0.765625}, {-0.125, 0.3046875, 0.765625}, + {0.203125, 0.09375, 0.7421875}, {-0.203125, 0.09375, 0.7421875}, + {0.375, 0.015625, 0.703125}, {-0.375, 0.015625, 0.703125}, + {0.4921875, 0.0625, 0.671875}, {-0.4921875, 0.0625, 0.671875}, + {0.625, 0.1875, 0.6484375}, {-0.625, 0.1875, 0.6484375}, + {0.640625, 0.296875, 0.6484375}, {-0.640625, 0.296875, 0.6484375}, + {0.6015625, 0.375, 0.6640625}, {-0.6015625, 0.375, 0.6640625}, + {0.4296875, 0.4375, 0.71875}, {-0.4296875, 0.4375, 0.71875}, + {0.25, 0.46875, 0.7578125}, {-0.25, 0.46875, 0.7578125}, + {0.0, -0.765625, 0.734375}, {0.109375, -0.71875, 0.734375}, + {-0.109375, -0.71875, 0.734375}, {0.1171875, -0.8359375, 0.7109375}, + {-0.1171875, -0.8359375, 0.7109375}, {0.0625, -0.8828125, 0.6953125}, + {-0.0625, -0.8828125, 0.6953125}, {0.0, -0.890625, 0.6875}, + {0.0, -0.1953125, 0.75}, {0.0, -0.140625, 0.7421875}, + {0.1015625, -0.1484375, 0.7421875}, {-0.1015625, -0.1484375, 0.7421875}, + {0.125, -0.2265625, 0.75}, {-0.125, -0.2265625, 0.75}, + {0.0859375, -0.2890625, 0.7421875}, {-0.0859375, -0.2890625, 0.7421875}, + {0.3984375, -0.046875, 0.671875}, {-0.3984375, -0.046875, 0.671875}, + {0.6171875, 0.0546875, 0.625}, {-0.6171875, 0.0546875, 0.625}, + {0.7265625, 0.203125, 0.6015625}, {-0.7265625, 0.203125, 0.6015625}, + {0.7421875, 0.375, 0.65625}, {-0.7421875, 0.375, 0.65625}, + {0.6875, 0.4140625, 0.7265625}, {-0.6875, 0.4140625, 0.7265625}, + {0.4375, 0.546875, 0.796875}, {-0.4375, 0.546875, 0.796875}, + {0.3125, 0.640625, 0.8359375}, {-0.3125, 0.640625, 0.8359375}, + {0.203125, 0.6171875, 0.8515625}, {-0.203125, 0.6171875, 0.8515625}, + {0.1015625, 0.4296875, 0.84375}, {-0.1015625, 0.4296875, 0.84375}, + {0.125, -0.1015625, 0.8125}, {-0.125, -0.1015625, 0.8125}, + {0.2109375, -0.4453125, 0.7109375}, {-0.2109375, -0.4453125, 0.7109375}, + {0.25, -0.703125, 0.6875}, {-0.25, -0.703125, 0.6875}, + {0.265625, -0.8203125, 0.6640625}, {-0.265625, -0.8203125, 0.6640625}, + {0.234375, -0.9140625, 0.6328125}, {-0.234375, -0.9140625, 0.6328125}, + {0.1640625, -0.9296875, 0.6328125}, {-0.1640625, -0.9296875, 0.6328125}, + {0.0, -0.9453125, 0.640625}, {0.0, 0.046875, 0.7265625}, + {0.0, 0.2109375, 0.765625}, {0.328125, 0.4765625, 0.7421875}, + {-0.328125, 0.4765625, 0.7421875}, {0.1640625, 0.140625, 0.75}, + {-0.1640625, 0.140625, 0.75}, {0.1328125, 0.2109375, 0.7578125}, + {-0.1328125, 0.2109375, 0.7578125}, {0.1171875, -0.6875, 0.734375}, + {-0.1171875, -0.6875, 0.734375}, {0.078125, -0.4453125, 0.75}, + {-0.078125, -0.4453125, 0.75}, {0.0, -0.4453125, 0.75}, + {0.0, -0.328125, 0.7421875}, {0.09375, -0.2734375, 0.78125}, + {-0.09375, -0.2734375, 0.78125}, {0.1328125, -0.2265625, 0.796875}, + {-0.1328125, -0.2265625, 0.796875}, {0.109375, -0.1328125, 0.78125}, + {-0.109375, -0.1328125, 0.78125}, {0.0390625, -0.125, 0.78125}, + {-0.0390625, -0.125, 0.78125}, {0.0, -0.203125, 0.828125}, + {0.046875, -0.1484375, 0.8125}, {-0.046875, -0.1484375, 0.8125}, + {0.09375, -0.15625, 0.8125}, {-0.09375, -0.15625, 0.8125}, + {0.109375, -0.2265625, 0.828125}, {-0.109375, -0.2265625, 0.828125}, + {0.078125, -0.25, 0.8046875}, {-0.078125, -0.25, 0.8046875}, + {0.0, -0.2890625, 0.8046875}, {0.2578125, -0.3125, 0.5546875}, + {-0.2578125, -0.3125, 0.5546875}, {0.1640625, -0.2421875, 0.7109375}, + {-0.1640625, -0.2421875, 0.7109375}, {0.1796875, -0.3125, 0.7109375}, + {-0.1796875, -0.3125, 0.7109375}, {0.234375, -0.25, 0.5546875}, + {-0.234375, -0.25, 0.5546875}, {0.0, -0.875, 0.6875}, + {0.046875, -0.8671875, 0.6875}, {-0.046875, -0.8671875, 0.6875}, + {0.09375, -0.8203125, 0.7109375}, {-0.09375, -0.8203125, 0.7109375}, + {0.09375, -0.7421875, 0.7265625}, {-0.09375, -0.7421875, 0.7265625}, + {0.0, -0.78125, 0.65625}, {0.09375, -0.75, 0.6640625}, + {-0.09375, -0.75, 0.6640625}, {0.09375, -0.8125, 0.640625}, + {-0.09375, -0.8125, 0.640625}, {0.046875, -0.8515625, 0.6328125}, + {-0.046875, -0.8515625, 0.6328125}, {0.0, -0.859375, 0.6328125}, + {0.171875, 0.21875, 0.78125}, {-0.171875, 0.21875, 0.78125}, + {0.1875, 0.15625, 0.7734375}, {-0.1875, 0.15625, 0.7734375}, + {0.3359375, 0.4296875, 0.7578125}, {-0.3359375, 0.4296875, 0.7578125}, + {0.2734375, 0.421875, 0.7734375}, {-0.2734375, 0.421875, 0.7734375}, + {0.421875, 0.3984375, 0.7734375}, {-0.421875, 0.3984375, 0.7734375}, + {0.5625, 0.3515625, 0.6953125}, {-0.5625, 0.3515625, 0.6953125}, + {0.5859375, 0.2890625, 0.6875}, {-0.5859375, 0.2890625, 0.6875}, + {0.578125, 0.1953125, 0.6796875}, {-0.578125, 0.1953125, 0.6796875}, + {0.4765625, 0.1015625, 0.71875}, {-0.4765625, 0.1015625, 0.71875}, + {0.375, 0.0625, 0.7421875}, {-0.375, 0.0625, 0.7421875}, + {0.2265625, 0.109375, 0.78125}, {-0.2265625, 0.109375, 0.78125}, + {0.1796875, 0.296875, 0.78125}, {-0.1796875, 0.296875, 0.78125}, + {0.2109375, 0.375, 0.78125}, {-0.2109375, 0.375, 0.78125}, + {0.234375, 0.359375, 0.7578125}, {-0.234375, 0.359375, 0.7578125}, + {0.1953125, 0.296875, 0.7578125}, {-0.1953125, 0.296875, 0.7578125}, + {0.2421875, 0.125, 0.7578125}, {-0.2421875, 0.125, 0.7578125}, + {0.375, 0.0859375, 0.7265625}, {-0.375, 0.0859375, 0.7265625}, + {0.4609375, 0.1171875, 0.703125}, {-0.4609375, 0.1171875, 0.703125}, + {0.546875, 0.2109375, 0.671875}, {-0.546875, 0.2109375, 0.671875}, + {0.5546875, 0.28125, 0.671875}, {-0.5546875, 0.28125, 0.671875}, + {0.53125, 0.3359375, 0.6796875}, {-0.53125, 0.3359375, 0.6796875}, + {0.4140625, 0.390625, 0.75}, {-0.4140625, 0.390625, 0.75}, + {0.28125, 0.3984375, 0.765625}, {-0.28125, 0.3984375, 0.765625}, + {0.3359375, 0.40625, 0.75}, {-0.3359375, 0.40625, 0.75}, + {0.203125, 0.171875, 0.75}, {-0.203125, 0.171875, 0.75}, + {0.1953125, 0.2265625, 0.75}, {-0.1953125, 0.2265625, 0.75}, + {0.109375, 0.4609375, 0.609375}, {-0.109375, 0.4609375, 0.609375}, + {0.1953125, 0.6640625, 0.6171875}, {-0.1953125, 0.6640625, 0.6171875}, + {0.3359375, 0.6875, 0.59375}, {-0.3359375, 0.6875, 0.59375}, + {0.484375, 0.5546875, 0.5546875}, {-0.484375, 0.5546875, 0.5546875}, + {0.6796875, 0.453125, 0.4921875}, {-0.6796875, 0.453125, 0.4921875}, + {0.796875, 0.40625, 0.4609375}, {-0.796875, 0.40625, 0.4609375}, + {0.7734375, 0.1640625, 0.375}, {-0.7734375, 0.1640625, 0.375}, + {0.6015625, 0.0, 0.4140625}, {-0.6015625, 0.0, 0.4140625}, + {0.4375, -0.09375, 0.46875}, {-0.4375, -0.09375, 0.46875}, + {0.0, 0.8984375, 0.2890625}, {0.0, 0.984375, -0.078125}, + {0.0, -0.1953125, -0.671875}, {0.0, -0.4609375, 0.1875}, + {0.0, -0.9765625, 0.4609375}, {0.0, -0.8046875, 0.34375}, + {0.0, -0.5703125, 0.3203125}, {0.0, -0.484375, 0.28125}, + {0.8515625, 0.234375, 0.0546875}, {-0.8515625, 0.234375, 0.0546875}, + {0.859375, 0.3203125, -0.046875}, {-0.859375, 0.3203125, -0.046875}, + {0.7734375, 0.265625, -0.4375}, {-0.7734375, 0.265625, -0.4375}, + {0.4609375, 0.4375, -0.703125}, {-0.4609375, 0.4375, -0.703125}, + {0.734375, -0.046875, 0.0703125}, {-0.734375, -0.046875, 0.0703125}, + {0.59375, -0.125, -0.1640625}, {-0.59375, -0.125, -0.1640625}, + {0.640625, -0.0078125, -0.4296875}, {-0.640625, -0.0078125, -0.4296875}, + {0.3359375, 0.0546875, -0.6640625}, {-0.3359375, 0.0546875, -0.6640625}, + {0.234375, -0.3515625, 0.40625}, {-0.234375, -0.3515625, 0.40625}, + {0.1796875, -0.4140625, 0.2578125}, {-0.1796875, -0.4140625, 0.2578125}, + {0.2890625, -0.7109375, 0.3828125}, {-0.2890625, -0.7109375, 0.3828125}, + {0.25, -0.5, 0.390625}, {-0.25, -0.5, 0.390625}, + {0.328125, -0.9140625, 0.3984375}, {-0.328125, -0.9140625, 0.3984375}, + {0.140625, -0.7578125, 0.3671875}, {-0.140625, -0.7578125, 0.3671875}, + {0.125, -0.5390625, 0.359375}, {-0.125, -0.5390625, 0.359375}, + {0.1640625, -0.9453125, 0.4375}, {-0.1640625, -0.9453125, 0.4375}, + {0.21875, -0.28125, 0.4296875}, {-0.21875, -0.28125, 0.4296875}, + {0.2109375, -0.2265625, 0.46875}, {-0.2109375, -0.2265625, 0.46875}, + {0.203125, -0.171875, 0.5}, {-0.203125, -0.171875, 0.5}, + {0.2109375, -0.390625, 0.1640625}, {-0.2109375, -0.390625, 0.1640625}, + {0.296875, -0.3125, -0.265625}, {-0.296875, -0.3125, -0.265625}, + {0.34375, -0.1484375, -0.5390625}, {-0.34375, -0.1484375, -0.5390625}, + {0.453125, 0.8671875, -0.3828125}, {-0.453125, 0.8671875, -0.3828125}, + {0.453125, 0.9296875, -0.0703125}, {-0.453125, 0.9296875, -0.0703125}, + {0.453125, 0.8515625, 0.234375}, {-0.453125, 0.8515625, 0.234375}, + {0.4609375, 0.5234375, 0.4296875}, {-0.4609375, 0.5234375, 0.4296875}, + {0.7265625, 0.40625, 0.3359375}, {-0.7265625, 0.40625, 0.3359375}, + {0.6328125, 0.453125, 0.28125}, {-0.6328125, 0.453125, 0.28125}, + {0.640625, 0.703125, 0.0546875}, {-0.640625, 0.703125, 0.0546875}, + {0.796875, 0.5625, 0.125}, {-0.796875, 0.5625, 0.125}, + {0.796875, 0.6171875, -0.1171875}, {-0.796875, 0.6171875, -0.1171875}, + {0.640625, 0.75, -0.1953125}, {-0.640625, 0.75, -0.1953125}, + {0.640625, 0.6796875, -0.4453125}, {-0.640625, 0.6796875, -0.4453125}, + {0.796875, 0.5390625, -0.359375}, {-0.796875, 0.5390625, -0.359375}, + {0.6171875, 0.328125, -0.5859375}, {-0.6171875, 0.328125, -0.5859375}, + {0.484375, 0.0234375, -0.546875}, {-0.484375, 0.0234375, -0.546875}, + {0.8203125, 0.328125, -0.203125}, {-0.8203125, 0.328125, -0.203125}, + {0.40625, -0.171875, 0.1484375}, {-0.40625, -0.171875, 0.1484375}, + {0.4296875, -0.1953125, -0.2109375}, {-0.4296875, -0.1953125, -0.2109375}, + {0.890625, 0.40625, -0.234375}, {-0.890625, 0.40625, -0.234375}, + {0.7734375, -0.140625, -0.125}, {-0.7734375, -0.140625, -0.125}, + {1.0390625, -0.1015625, -0.328125}, {-1.0390625, -0.1015625, -0.328125}, + {1.28125, 0.0546875, -0.4296875}, {-1.28125, 0.0546875, -0.4296875}, + {1.3515625, 0.3203125, -0.421875}, {-1.3515625, 0.3203125, -0.421875}, + {1.234375, 0.5078125, -0.421875}, {-1.234375, 0.5078125, -0.421875}, + {1.0234375, 0.4765625, -0.3125}, {-1.0234375, 0.4765625, -0.3125}, + {1.015625, 0.4140625, -0.2890625}, {-1.015625, 0.4140625, -0.2890625}, + {1.1875, 0.4375, -0.390625}, {-1.1875, 0.4375, -0.390625}, + {1.265625, 0.2890625, -0.40625}, {-1.265625, 0.2890625, -0.40625}, + {1.2109375, 0.078125, -0.40625}, {-1.2109375, 0.078125, -0.40625}, + {1.03125, -0.0390625, -0.3046875}, {-1.03125, -0.0390625, -0.3046875}, + {0.828125, -0.0703125, -0.1328125}, {-0.828125, -0.0703125, -0.1328125}, + {0.921875, 0.359375, -0.21875}, {-0.921875, 0.359375, -0.21875}, + {0.9453125, 0.3046875, -0.2890625}, {-0.9453125, 0.3046875, -0.2890625}, + {0.8828125, -0.0234375, -0.2109375}, {-0.8828125, -0.0234375, -0.2109375}, + {1.0390625, 0.0, -0.3671875}, {-1.0390625, 0.0, -0.3671875}, + {1.1875, 0.09375, -0.4453125}, {-1.1875, 0.09375, -0.4453125}, + {1.234375, 0.25, -0.4453125}, {-1.234375, 0.25, -0.4453125}, + {1.171875, 0.359375, -0.4375}, {-1.171875, 0.359375, -0.4375}, + {1.0234375, 0.34375, -0.359375}, {-1.0234375, 0.34375, -0.359375}, + {0.84375, 0.2890625, -0.2109375}, {-0.84375, 0.2890625, -0.2109375}, + {0.8359375, 0.171875, -0.2734375}, {-0.8359375, 0.171875, -0.2734375}, + {0.7578125, 0.09375, -0.2734375}, {-0.7578125, 0.09375, -0.2734375}, + {0.8203125, 0.0859375, -0.2734375}, {-0.8203125, 0.0859375, -0.2734375}, + {0.84375, 0.015625, -0.2734375}, {-0.84375, 0.015625, -0.2734375}, + {0.8125, -0.015625, -0.2734375}, {-0.8125, -0.015625, -0.2734375}, + {0.7265625, 0.0, -0.0703125}, {-0.7265625, 0.0, -0.0703125}, + {0.71875, -0.0234375, -0.171875}, {-0.71875, -0.0234375, -0.171875}, + {0.71875, 0.0390625, -0.1875}, {-0.71875, 0.0390625, -0.1875}, + {0.796875, 0.203125, -0.2109375}, {-0.796875, 0.203125, -0.2109375}, + {0.890625, 0.2421875, -0.265625}, {-0.890625, 0.2421875, -0.265625}, + {0.890625, 0.234375, -0.3203125}, {-0.890625, 0.234375, -0.3203125}, + {0.8125, -0.015625, -0.3203125}, {-0.8125, -0.015625, -0.3203125}, + {0.8515625, 0.015625, -0.3203125}, {-0.8515625, 0.015625, -0.3203125}, + {0.828125, 0.078125, -0.3203125}, {-0.828125, 0.078125, -0.3203125}, + {0.765625, 0.09375, -0.3203125}, {-0.765625, 0.09375, -0.3203125}, + {0.84375, 0.171875, -0.3203125}, {-0.84375, 0.171875, -0.3203125}, + {1.0390625, 0.328125, -0.4140625}, {-1.0390625, 0.328125, -0.4140625}, + {1.1875, 0.34375, -0.484375}, {-1.1875, 0.34375, -0.484375}, + {1.2578125, 0.2421875, -0.4921875}, {-1.2578125, 0.2421875, -0.4921875}, + {1.2109375, 0.0859375, -0.484375}, {-1.2109375, 0.0859375, -0.484375}, + {1.046875, 0.0, -0.421875}, {-1.046875, 0.0, -0.421875}, + {0.8828125, -0.015625, -0.265625}, {-0.8828125, -0.015625, -0.265625}, + {0.953125, 0.2890625, -0.34375}, {-0.953125, 0.2890625, -0.34375}, + {0.890625, 0.109375, -0.328125}, {-0.890625, 0.109375, -0.328125}, + {0.9375, 0.0625, -0.3359375}, {-0.9375, 0.0625, -0.3359375}, + {1.0, 0.125, -0.3671875}, {-1.0, 0.125, -0.3671875}, + {0.9609375, 0.171875, -0.3515625}, {-0.9609375, 0.171875, -0.3515625}, + {1.015625, 0.234375, -0.375}, {-1.015625, 0.234375, -0.375}, + {1.0546875, 0.1875, -0.3828125}, {-1.0546875, 0.1875, -0.3828125}, + {1.109375, 0.2109375, -0.390625}, {-1.109375, 0.2109375, -0.390625}, + {1.0859375, 0.2734375, -0.390625}, {-1.0859375, 0.2734375, -0.390625}, + {1.0234375, 0.4375, -0.484375}, {-1.0234375, 0.4375, -0.484375}, + {1.25, 0.46875, -0.546875}, {-1.25, 0.46875, -0.546875}, + {1.3671875, 0.296875, -0.5}, {-1.3671875, 0.296875, -0.5}, + {1.3125, 0.0546875, -0.53125}, {-1.3125, 0.0546875, -0.53125}, + {1.0390625, -0.0859375, -0.4921875}, {-1.0390625, -0.0859375, -0.4921875}, + {0.7890625, -0.125, -0.328125}, {-0.7890625, -0.125, -0.328125}, + {0.859375, 0.3828125, -0.3828125}, {-0.859375, 0.3828125, -0.3828125}}; + +vector suzanne_quads = vector{{46, 0, 2, 44}, {3, 1, 47, 45}, + {44, 2, 4, 42}, {5, 3, 45, 43}, {2, 8, 6, 4}, {7, 9, 3, 5}, {0, 10, 8, 2}, + {9, 11, 1, 3}, {10, 12, 14, 8}, {15, 13, 11, 9}, {8, 14, 16, 6}, + {17, 15, 9, 7}, {14, 20, 18, 16}, {19, 21, 15, 17}, {12, 22, 20, 14}, + {21, 23, 13, 15}, {22, 24, 26, 20}, {27, 25, 23, 21}, {20, 26, 28, 18}, + {29, 27, 21, 19}, {26, 32, 30, 28}, {31, 33, 27, 29}, {24, 34, 32, 26}, + {33, 35, 25, 27}, {34, 36, 38, 32}, {39, 37, 35, 33}, {32, 38, 40, 30}, + {41, 39, 33, 31}, {38, 44, 42, 40}, {43, 45, 39, 41}, {36, 46, 44, 38}, + {45, 47, 37, 39}, {46, 36, 50, 48}, {51, 37, 47, 49}, {36, 34, 52, 50}, + {53, 35, 37, 51}, {34, 24, 54, 52}, {55, 25, 35, 53}, {24, 22, 56, 54}, + {57, 23, 25, 55}, {22, 12, 58, 56}, {59, 13, 23, 57}, {12, 10, 62, 58}, + {63, 11, 13, 59}, {10, 0, 64, 62}, {65, 1, 11, 63}, {0, 46, 48, 64}, + {49, 47, 1, 65}, {88, 173, 175, 90}, {175, 174, 89, 90}, {86, 171, 173, 88}, + {174, 172, 87, 89}, {84, 169, 171, 86}, {172, 170, 85, 87}, + {82, 167, 169, 84}, {170, 168, 83, 85}, {80, 165, 167, 82}, + {168, 166, 81, 83}, {78, 91, 145, 163}, {146, 92, 79, 164}, + {91, 93, 147, 145}, {148, 94, 92, 146}, {93, 95, 149, 147}, + {150, 96, 94, 148}, {95, 97, 151, 149}, {152, 98, 96, 150}, + {97, 99, 153, 151}, {154, 100, 98, 152}, {99, 101, 155, 153}, + {156, 102, 100, 154}, {101, 103, 157, 155}, {158, 104, 102, 156}, + {103, 105, 159, 157}, {160, 106, 104, 158}, {105, 107, 161, 159}, + {162, 108, 106, 160}, {107, 66, 67, 161}, {67, 66, 108, 162}, + {109, 127, 159, 161}, {160, 128, 110, 162}, {127, 178, 157, 159}, + {158, 179, 128, 160}, {125, 155, 157, 178}, {158, 156, 126, 179}, + {123, 153, 155, 125}, {156, 154, 124, 126}, {121, 151, 153, 123}, + {154, 152, 122, 124}, {119, 149, 151, 121}, {152, 150, 120, 122}, + {117, 147, 149, 119}, {150, 148, 118, 120}, {115, 145, 147, 117}, + {148, 146, 116, 118}, {113, 163, 145, 115}, {146, 164, 114, 116}, + {113, 180, 176, 163}, {176, 181, 114, 164}, {109, 161, 67, 111}, + {67, 162, 110, 112}, {111, 67, 177, 182}, {177, 67, 112, 183}, + {176, 180, 182, 177}, {183, 181, 176, 177}, {134, 136, 175, 173}, + {175, 136, 135, 174}, {132, 134, 173, 171}, {174, 135, 133, 172}, + {130, 132, 171, 169}, {172, 133, 131, 170}, {165, 186, 184, 167}, + {185, 187, 166, 168}, {130, 169, 167, 184}, {168, 170, 131, 185}, + {143, 189, 188, 186}, {188, 189, 144, 187}, {184, 186, 188, 68}, + {188, 187, 185, 68}, {129, 130, 184, 68}, {185, 131, 129, 68}, + {141, 192, 190, 143}, {191, 193, 142, 144}, {139, 194, 192, 141}, + {193, 195, 140, 142}, {138, 196, 194, 139}, {195, 197, 138, 140}, + {137, 70, 196, 138}, {197, 70, 137, 138}, {189, 143, 190, 69}, + {191, 144, 189, 69}, {69, 190, 205, 207}, {206, 191, 69, 207}, + {70, 198, 199, 196}, {200, 198, 70, 197}, {196, 199, 201, 194}, + {202, 200, 197, 195}, {194, 201, 203, 192}, {204, 202, 195, 193}, + {192, 203, 205, 190}, {206, 204, 193, 191}, {198, 203, 201, 199}, + {202, 204, 198, 200}, {198, 207, 205, 203}, {206, 207, 198, 204}, + {138, 139, 163, 176}, {164, 140, 138, 176}, {139, 141, 210, 163}, + {211, 142, 140, 164}, {141, 143, 212, 210}, {213, 144, 142, 211}, + {143, 186, 165, 212}, {166, 187, 144, 213}, {80, 208, 212, 165}, + {213, 209, 81, 166}, {208, 214, 210, 212}, {211, 215, 209, 213}, + {78, 163, 210, 214}, {211, 164, 79, 215}, {130, 129, 71, 221}, + {71, 129, 131, 222}, {132, 130, 221, 219}, {222, 131, 133, 220}, + {134, 132, 219, 217}, {220, 133, 135, 218}, {136, 134, 217, 216}, + {218, 135, 136, 216}, {216, 217, 228, 230}, {229, 218, 216, 230}, + {217, 219, 226, 228}, {227, 220, 218, 229}, {219, 221, 224, 226}, + {225, 222, 220, 227}, {221, 71, 223, 224}, {223, 71, 222, 225}, + {223, 230, 228, 224}, {229, 230, 223, 225}, {182, 180, 233, 231}, + {234, 181, 183, 232}, {111, 182, 231, 253}, {232, 183, 112, 254}, + {109, 111, 253, 255}, {254, 112, 110, 256}, {180, 113, 251, 233}, + {252, 114, 181, 234}, {113, 115, 249, 251}, {250, 116, 114, 252}, + {115, 117, 247, 249}, {248, 118, 116, 250}, {117, 119, 245, 247}, + {246, 120, 118, 248}, {119, 121, 243, 245}, {244, 122, 120, 246}, + {121, 123, 241, 243}, {242, 124, 122, 244}, {123, 125, 239, 241}, + {240, 126, 124, 242}, {125, 178, 235, 239}, {236, 179, 126, 240}, + {178, 127, 237, 235}, {238, 128, 179, 236}, {127, 109, 255, 237}, + {256, 110, 128, 238}, {237, 255, 257, 275}, {258, 256, 238, 276}, + {235, 237, 275, 277}, {276, 238, 236, 278}, {239, 235, 277, 273}, + {278, 236, 240, 274}, {241, 239, 273, 271}, {274, 240, 242, 272}, + {243, 241, 271, 269}, {272, 242, 244, 270}, {245, 243, 269, 267}, + {270, 244, 246, 268}, {247, 245, 267, 265}, {268, 246, 248, 266}, + {249, 247, 265, 263}, {266, 248, 250, 264}, {251, 249, 263, 261}, + {264, 250, 252, 262}, {233, 251, 261, 279}, {262, 252, 234, 280}, + {255, 253, 259, 257}, {260, 254, 256, 258}, {253, 231, 281, 259}, + {282, 232, 254, 260}, {231, 233, 279, 281}, {280, 234, 232, 282}, + {66, 107, 283, 72}, {284, 108, 66, 72}, {107, 105, 285, 283}, + {286, 106, 108, 284}, {105, 103, 287, 285}, {288, 104, 106, 286}, + {103, 101, 289, 287}, {290, 102, 104, 288}, {101, 99, 291, 289}, + {292, 100, 102, 290}, {99, 97, 293, 291}, {294, 98, 100, 292}, + {97, 95, 295, 293}, {296, 96, 98, 294}, {95, 93, 297, 295}, + {298, 94, 96, 296}, {93, 91, 299, 297}, {300, 92, 94, 298}, + {307, 308, 327, 337}, {328, 308, 307, 338}, {306, 307, 337, 335}, + {338, 307, 306, 336}, {305, 306, 335, 339}, {336, 306, 305, 340}, + {88, 90, 305, 339}, {305, 90, 89, 340}, {86, 88, 339, 333}, + {340, 89, 87, 334}, {84, 86, 333, 329}, {334, 87, 85, 330}, + {82, 84, 329, 331}, {330, 85, 83, 332}, {329, 335, 337, 331}, + {338, 336, 330, 332}, {329, 333, 339, 335}, {340, 334, 330, 336}, + {325, 331, 337, 327}, {338, 332, 326, 328}, {80, 82, 331, 325}, + {332, 83, 81, 326}, {208, 341, 343, 214}, {344, 342, 209, 215}, + {80, 325, 341, 208}, {342, 326, 81, 209}, {78, 214, 343, 345}, + {344, 215, 79, 346}, {78, 345, 299, 91}, {300, 346, 79, 92}, + {76, 323, 351, 303}, {352, 324, 76, 303}, {303, 351, 349, 77}, + {350, 352, 303, 77}, {77, 349, 347, 304}, {348, 350, 77, 304}, + {304, 347, 327, 308}, {328, 348, 304, 308}, {325, 327, 347, 341}, + {348, 328, 326, 342}, {295, 297, 317, 309}, {318, 298, 296, 310}, + {75, 315, 323, 76}, {324, 316, 75, 76}, {301, 357, 355, 302}, + {356, 358, 301, 302}, {302, 355, 353, 74}, {354, 356, 302, 74}, + {74, 353, 315, 75}, {316, 354, 74, 75}, {291, 293, 361, 363}, + {362, 294, 292, 364}, {363, 361, 367, 365}, {368, 362, 364, 366}, + {365, 367, 369, 371}, {370, 368, 366, 372}, {371, 369, 375, 373}, + {376, 370, 372, 374}, {313, 377, 373, 375}, {374, 378, 314, 376}, + {315, 353, 373, 377}, {374, 354, 316, 378}, {353, 355, 371, 373}, + {372, 356, 354, 374}, {355, 357, 365, 371}, {366, 358, 356, 372}, + {357, 359, 363, 365}, {364, 360, 358, 366}, {289, 291, 363, 359}, + {364, 292, 290, 360}, {73, 359, 357, 301}, {358, 360, 73, 301}, + {283, 285, 287, 289}, {288, 286, 284, 290}, {283, 289, 359, 73}, + {360, 290, 284, 73}, {293, 295, 309, 361}, {310, 296, 294, 362}, + {309, 311, 367, 361}, {368, 312, 310, 362}, {311, 381, 369, 367}, + {370, 382, 312, 368}, {313, 375, 369, 381}, {370, 376, 314, 382}, + {347, 349, 385, 383}, {386, 350, 348, 384}, {317, 383, 385, 319}, + {386, 384, 318, 320}, {297, 299, 383, 317}, {384, 300, 298, 318}, + {299, 343, 341, 383}, {342, 344, 300, 384}, {313, 321, 379, 377}, + {380, 322, 314, 378}, {315, 377, 379, 323}, {380, 378, 316, 324}, + {319, 385, 379, 321}, {380, 386, 320, 322}, {349, 351, 379, 385}, + {380, 352, 350, 386}, {399, 387, 413, 401}, {414, 388, 400, 402}, + {399, 401, 403, 397}, {404, 402, 400, 398}, {397, 403, 405, 395}, + {406, 404, 398, 396}, {395, 405, 407, 393}, {408, 406, 396, 394}, + {393, 407, 409, 391}, {410, 408, 394, 392}, {391, 409, 411, 389}, + {412, 410, 392, 390}, {409, 419, 417, 411}, {418, 420, 410, 412}, + {407, 421, 419, 409}, {420, 422, 408, 410}, {405, 423, 421, 407}, + {422, 424, 406, 408}, {403, 425, 423, 405}, {424, 426, 404, 406}, + {401, 427, 425, 403}, {426, 428, 402, 404}, {401, 413, 415, 427}, + {416, 414, 402, 428}, {317, 319, 443, 441}, {444, 320, 318, 442}, + {319, 389, 411, 443}, {412, 390, 320, 444}, {309, 317, 441, 311}, + {442, 318, 310, 312}, {381, 429, 413, 387}, {414, 430, 382, 388}, + {411, 417, 439, 443}, {440, 418, 412, 444}, {437, 445, 443, 439}, + {444, 446, 438, 440}, {433, 445, 437, 435}, {438, 446, 434, 436}, + {431, 447, 445, 433}, {446, 448, 432, 434}, {429, 447, 431, 449}, + {432, 448, 430, 450}, {413, 429, 449, 415}, {450, 430, 414, 416}, + {311, 447, 429, 381}, {430, 448, 312, 382}, {311, 441, 445, 447}, + {446, 442, 312, 448}, {415, 449, 451, 475}, {452, 450, 416, 476}, + {449, 431, 461, 451}, {462, 432, 450, 452}, {431, 433, 459, 461}, + {460, 434, 432, 462}, {433, 435, 457, 459}, {458, 436, 434, 460}, + {435, 437, 455, 457}, {456, 438, 436, 458}, {437, 439, 453, 455}, + {454, 440, 438, 456}, {439, 417, 473, 453}, {474, 418, 440, 454}, + {427, 415, 475, 463}, {476, 416, 428, 464}, {425, 427, 463, 465}, + {464, 428, 426, 466}, {423, 425, 465, 467}, {466, 426, 424, 468}, + {421, 423, 467, 469}, {468, 424, 422, 470}, {419, 421, 469, 471}, + {470, 422, 420, 472}, {417, 419, 471, 473}, {472, 420, 418, 474}, + {457, 455, 479, 477}, {480, 456, 458, 478}, {477, 479, 481, 483}, + {482, 480, 478, 484}, {483, 481, 487, 485}, {488, 482, 484, 486}, + {485, 487, 489, 491}, {490, 488, 486, 492}, {463, 475, 485, 491}, + {486, 476, 464, 492}, {451, 483, 485, 475}, {486, 484, 452, 476}, + {451, 461, 477, 483}, {478, 462, 452, 484}, {457, 477, 461, 459}, + {462, 478, 458, 460}, {453, 473, 479, 455}, {480, 474, 454, 456}, + {471, 481, 479, 473}, {480, 482, 472, 474}, {469, 487, 481, 471}, + {482, 488, 470, 472}, {467, 489, 487, 469}, {488, 490, 468, 470}, + {465, 491, 489, 467}, {490, 492, 466, 468}, {391, 389, 503, 501}, + {504, 390, 392, 502}, {393, 391, 501, 499}, {502, 392, 394, 500}, + {395, 393, 499, 497}, {500, 394, 396, 498}, {397, 395, 497, 495}, + {498, 396, 398, 496}, {399, 397, 495, 493}, {496, 398, 400, 494}, + {387, 399, 493, 505}, {494, 400, 388, 506}, {493, 501, 503, 505}, + {504, 502, 494, 506}, {493, 495, 499, 501}, {500, 496, 494, 502}, + {313, 381, 387, 505}, {388, 382, 314, 506}, {313, 505, 503, 321}, + {504, 506, 314, 322}, {319, 321, 503, 389}, {504, 322, 320, 390}, + // ttriangles + {60, 64, 48, 48}, {49, 65, 61, 61}, {62, 64, 60, 60}, {61, 65, 63, 63}, + {60, 58, 62, 62}, {63, 59, 61, 61}, {60, 56, 58, 58}, {59, 57, 61, 61}, + {60, 54, 56, 56}, {57, 55, 61, 61}, {60, 52, 54, 54}, {55, 53, 61, 61}, + {60, 50, 52, 52}, {53, 51, 61, 61}, {60, 48, 50, 50}, {51, 49, 61, 61}, + {224, 228, 226, 226}, {227, 229, 225, 255}, {72, 283, 73, 73}, + {73, 284, 72, 72}, {341, 347, 383, 383}, {384, 348, 342, 342}, + {299, 345, 343, 343}, {344, 346, 300, 300}, {323, 379, 351, 351}, + {352, 380, 324, 324}, {441, 443, 445, 445}, {446, 444, 442, 442}, + {463, 491, 465, 465}, {466, 492, 464, 464}, {495, 497, 499, 499}, + {500, 498, 496, 496}}; -} // namespace yocto +} // namespace yocto \ No newline at end of file diff --git a/libs/yocto/yocto_shape.h b/libs/yocto/yocto_shape.h index fdd82400d..ba1c904fd 100644 --- a/libs/yocto/yocto_shape.h +++ b/libs/yocto/yocto_shape.h @@ -64,6 +64,243 @@ using std::vector; } // namespace yocto +// ----------------------------------------------------------------------------- +// SHAPE DATA AND UTILITIES +// ----------------------------------------------------------------------------- +namespace yocto { + +// Shape data represented as indexed meshes of elements. +// May contain either points, lines, triangles and quads. +struct shape_data { + // element data + vector points = {}; + vector lines = {}; + vector triangles = {}; + vector quads = {}; + + // vertex data + vector positions = {}; + vector normals = {}; + vector texcoords = {}; + vector colors = {}; + vector radius = {}; + vector tangents = {}; +}; + +// Interpolate vertex data +vec3f eval_position(const shape_data& shape, int element, const vec2f& uv); +vec3f eval_normal(const shape_data& shape, int element, const vec2f& uv); +vec3f eval_tangent(const shape_data& shape, int element, const vec2f& uv); +vec2f eval_texcoord(const shape_data& shape, int element, const vec2f& uv); +vec4f eval_color(const shape_data& shape, int element, const vec2f& uv); +float eval_radius(const shape_data& shape, int element, const vec2f& uv); + +// Evaluate element normals +vec3f eval_element_normal(const shape_data& shape, int element); + +// Compute per-vertex normals/tangents for lines/triangles/quads. +vector compute_normals(const shape_data& shape); +void compute_normals(vector& normals, const shape_data& shape); + +// An unevaluated location on a shape +struct shape_point { + int element = 0; + vec2f uv = {0, 0}; +}; + +// Shape sampling +vector sample_shape_cdf(const shape_data& shape); +void sample_shape_cdf(vector& cdf, const shape_data& shape); +shape_point sample_shape(const shape_data& shape, const vector& cdf, + float rn, const vec2f& ruv); +vector sample_shape( + const shape_data& shape, int num_samples, uint64_t seed = 98729387); + +// Conversions +shape_data quads_to_triangles(const shape_data& shape); +void quads_to_triangles(shape_data& result, const shape_data& shape); + +// Subdivision +shape_data subdivide_shape( + const shape_data& shape, int subdivisions, bool catmullclark); + +// Shape statistics +vector shape_stats(const shape_data& shape, bool verbose = false); + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// FACE-VARYING SHAPE DATA AND UTILITIES +// ----------------------------------------------------------------------------- +namespace yocto { + +// Shape data stored as a face-varying mesh +struct fvshape_data { + // element data + vector quadspos = {}; + vector quadsnorm = {}; + vector quadstexcoord = {}; + + // vertex data + vector positions = {}; + vector normals = {}; + vector texcoords = {}; +}; + +// Interpolate vertex data +vec3f eval_position(const fvshape_data& shape, int element, const vec2f& uv); +vec3f eval_normal(const fvshape_data& shape, int element, const vec2f& uv); +vec2f eval_texcoord(const shape_data& shape, int element, const vec2f& uv); + +// Evaluate element normals +vec3f eval_element_normal(const fvshape_data& shape, int element); + +// Compute per-vertex normals/tangents for lines/triangles/quads. +vector compute_normals(const fvshape_data& shape); +void compute_normals(vector& normals, const fvshape_data& shape); + +// Conversions +shape_data fvshape_to_shape( + const fvshape_data& shape, bool as_triangles = false); +fvshape_data shape_to_fvshape(const shape_data& shape); + +// Subdivision +fvshape_data subdivide_fvshape( + const fvshape_data& shape, int subdivisions, bool catmullclark); + +// Shape statistics +vector fvshape_stats(const fvshape_data& shape, bool verbose = false); + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// EXAMPLE SHAPES +// ----------------------------------------------------------------------------- +namespace yocto { + +// Make a plane. +shape_data make_rect(const vec2i& steps = {1, 1}, const vec2f& scale = {1, 1}, + const vec2f& uvscale = {1, 1}); +shape_data make_bulged_rect(const vec2i& steps = {1, 1}, + const vec2f& scale = {1, 1}, const vec2f& uvscale = {1, 1}, + float radius = 0.3); +// Make a plane in the xz plane. +shape_data make_recty(const vec2i& steps = {1, 1}, const vec2f& scale = {1, 1}, + const vec2f& uvscale = {1, 1}); +shape_data make_bulged_recty(const vec2i& steps = {1, 1}, + const vec2f& scale = {1, 1}, const vec2f& uvscale = {1, 1}, + float radius = 0.3); +// Make a box. +shape_data make_box(const vec3i& steps = {1, 1, 1}, + const vec3f& scale = {1, 1, 1}, const vec3f& uvscale = {1, 1, 1}); +shape_data make_rounded_box(const vec3i& steps = {1, 1, 1}, + const vec3f& scale = {1, 1, 1}, const vec3f& uvscale = {1, 1, 1}, + float radius = 0.3); +// Make a quad stack +shape_data make_rect_stack(const vec3i& steps = {1, 1, 1}, + const vec3f& scale = {1, 1, 1}, const vec2f& uvscale = {1, 1}); +// Make a floor. +shape_data make_floor(const vec2i& steps = {1, 1}, + const vec2f& scale = {10, 10}, const vec2f& uvscale = {10, 10}); +shape_data make_bent_floor(const vec2i& steps = {1, 1}, + const vec2f& scale = {10, 10}, const vec2f& uvscale = {10, 10}, + float bent = 0.5); +// Make a sphere. +shape_data make_sphere(int steps = 32, float scale = 1, float uvscale = 1); +// Make a sphere. +shape_data make_uvsphere(const vec2i& steps = {32, 32}, float scale = 1, + const vec2f& uvscale = {1, 1}); +shape_data make_uvspherey(const vec2i& steps = {32, 32}, float scale = 1, + const vec2f& uvscale = {1, 1}); +// Make a sphere with slipped caps. +shape_data make_capped_uvsphere(const vec2i& steps = {32, 32}, float scale = 1, + const vec2f& uvscale = {1, 1}, float height = 0.3); +shape_data make_capped_uvspherey(const vec2i& steps = {32, 32}, float scale = 1, + const vec2f& uvscale = {1, 1}, float height = 0.3); +// Make a disk +shape_data make_disk(int steps = 32, float scale = 1, float uvscale = 1); +// Make a bulged disk +shape_data make_bulged_disk( + int steps = 32, float scale = 1, float uvscale = 1, float height = 0.3); +// Make a uv disk +shape_data make_uvdisk(const vec2i& steps = {32, 32}, float scale = 1, + const vec2f& uvscale = {1, 1}); +// Make a uv cylinder +shape_data make_uvcylinder(const vec3i& steps = {32, 32, 32}, + const vec2f& scale = {1, 1}, const vec3f& uvscale = {1, 1, 1}); +// Make a rounded uv cylinder +shape_data make_rounded_uvcylinder(const vec3i& steps = {32, 32, 32}, + const vec2f& scale = {1, 1}, const vec3f& uvscale = {1, 1, 1}, + float radius = 0.3); + +// Make a facevarying rect +fvshape_data make_fvrect(const vec2i& steps = {1, 1}, + const vec2f& scale = {1, 1}, const vec2f& uvscale = {1, 1}); +// Make a facevarying box +fvshape_data make_fvbox(const vec3i& steps = {1, 1, 1}, + const vec3f& scale = {1, 1, 1}, const vec3f& uvscale = {1, 1, 1}); +// Make a facevarying sphere +fvshape_data make_fvsphere(int steps = 32, float scale = 1, float uvscale = 1); + +// Generate lines set along a quad. Returns lines, pos, norm, texcoord, radius. +shape_data make_lines(const vec2i& steps = {4, 65536}, + const vec2f& scale = {1, 1}, const vec2f& uvscale = {1, 1}, + const vec2f& radius = {0.001f, 0.001f}); + +// Make a point primitive. Returns points, pos, norm, texcoord, radius. +shape_data make_point(float radius = 0.001f); +// Make a point set on a grid. Returns points, pos, norm, texcoord, radius. +shape_data make_points( + int num = 65536, float uvscale = 1, float radius = 0.001f); +shape_data make_points(const vec2i& steps = {256, 256}, + const vec2f& size = {1, 1}, const vec2f& uvscale = {1, 1}, + const vec2f& radius = {0.001f, 0.001f}); +// Make random points in a cube. Returns points, pos, norm, texcoord, radius. +shape_data make_random_points(int num = 65536, const vec3f& size = {1, 1, 1}, + float uvscale = 1, float radius = 0.001f, uint64_t seed = 17); + +// Predefined meshes +shape_data make_monkey(float scale = 1, int subdivisions = 0); +shape_data make_quad(float scale = 1, int subdivisions = 0); +shape_data make_quady(float scale = 1, int subdivisions = 0); +shape_data make_cube(float scale = 1, int subdivisions = 0); +fvshape_data make_fvcube(float scale = 1, int subdivisions = 0); +shape_data make_geosphere(float scale = 1, int subdivisions = 0); + +// Make a hair ball around a shape. +// length: minimum and maximum length +// rad: minimum and maximum radius from base to tip +// noise: noise added to hair (strength/scale) +// clump: clump added to hair (strength/number) +// rotation: rotation added to hair (angle/strength) +shape_data make_hair(const shape_data& shape, const vec2i& steps = {8, 65536}, + const vec2f& length = {0.1f, 0.1f}, const vec2f& radius = {0.001f, 0.001f}, + const vec2f& noise = {0, 10}, const vec2f& clump = {0, 128}, + const vec2f& rotation = {0, 0}, int seed = 7); + +// Grow hairs around a shape +shape_data make_hair2(const shape_data& shape, const vec2i& steps = {8, 65536}, + const vec2f& length = {0.1f, 0.1f}, const vec2f& radius = {0.001f, 0.001f}, + float noise = 0, float gravity = 0.001f, int seed = 7); + +// Convert points to small spheres and lines to small cylinders. This is +// intended for making very small primitives for display in interactive +// applications, so the spheres are low res. +shape_data points_to_spheres( + const vector& vertices, int steps = 2, float scale = 0.01f); +shape_data polyline_to_cylinders( + const vector& vertices, int steps = 4, float scale = 0.01f); +shape_data lines_to_cylinders( + const vector& vertices, int steps = 4, float scale = 0.01f); +shape_data lines_to_cylinders(const vector& lines, + const vector& positions, int steps = 4, float scale = 0.01f); + +// Make a heightfield mesh. +shape_data make_heightfield(const vec2i& size, const vector& height); +shape_data make_heightfield(const vec2i& size, const vector& color); + +} // namespace yocto + // ----------------------------------------------------------------------------- // COMPUTATION OF PER_VERTEX PROPERTIES // ----------------------------------------------------------------------------- diff --git a/libs/yocto/yocto_shapeio.cpp b/libs/yocto/yocto_shapeio.cpp new file mode 100644 index 000000000..42bbfb808 --- /dev/null +++ b/libs/yocto/yocto_shapeio.cpp @@ -0,0 +1,769 @@ +// +// Implementation for Yocto/Shape Input and Output functions. +// + +// +// LICENSE: +// +// Copyright (c) 2016 -- 2021 Fabio Pellacini +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#include "yocto_shapeio.h" + +#include +#include +#include +#include +#include + +#include "yocto_commonio.h" +#include "yocto_geometry.h" +#include "yocto_modelio.h" + +// ----------------------------------------------------------------------------- +// SHAPE IO +// ----------------------------------------------------------------------------- +namespace yocto { + +// Load ply mesh +shape_data load_shape(const string& filename, bool flip_texcoord) { + auto shape = shape_data{}; + load_shape(filename, shape, flip_texcoord); + return shape; +} +void load_shape(const string& filename, shape_data& shape, bool flip_texcoord) { + shape = {}; + + auto ext = path_extension(filename); + if (ext == ".ply" || ext == ".PLY") { + auto ply = ply_model{}; + load_ply(filename, ply); + get_positions(ply, shape.positions); + get_normals(ply, shape.normals); + get_texcoords(ply, shape.texcoords, flip_texcoord); + get_colors(ply, shape.colors); + get_radius(ply, shape.radius); + get_faces(ply, shape.triangles, shape.quads); + get_lines(ply, shape.lines); + get_points(ply, shape.points); + if (shape.points.empty() && shape.lines.empty() && + shape.triangles.empty() && shape.quads.empty()) + throw io_error::shape_error(filename); + } else if (ext == ".obj" || ext == ".OBJ") { + auto obj = obj_shape{}; + load_obj(filename, obj, false); + auto materials = vector{}; + get_positions(obj, shape.positions); + get_normals(obj, shape.normals); + get_texcoords(obj, shape.texcoords, flip_texcoord); + get_faces(obj, shape.triangles, shape.quads, materials); + get_lines(obj, shape.lines, materials); + get_points(obj, shape.points, materials); + if (shape.points.empty() && shape.lines.empty() && + shape.triangles.empty() && shape.quads.empty()) + throw io_error::shape_error(filename); + } else if (ext == ".stl" || ext == ".STL") { + auto stl = stl_model{}; + load_stl(filename, stl, true); + if (stl.shapes.size() != 1) throw io_error::shape_error(filename); + auto fnormals = vector{}; + if (!get_triangles(stl, 0, shape.triangles, shape.positions, fnormals)) + throw io_error::shape_error(filename); + } else if (ext == ".ypreset" || ext == ".YPRESET") { + shape = make_shape_preset(path_basename(filename)); + } else { + throw io_error::format_error(filename); + } +} + +// Save ply mesh +void save_shape(const string& filename, const shape_data& shape, + bool flip_texcoord, bool ascii) { + auto ext = path_extension(filename); + if (ext == ".ply" || ext == ".PLY") { + auto ply = ply_model{}; + add_positions(ply, shape.positions); + add_normals(ply, shape.normals); + add_texcoords(ply, shape.texcoords, flip_texcoord); + add_colors(ply, shape.colors); + add_radius(ply, shape.radius); + add_faces(ply, shape.triangles, shape.quads); + add_lines(ply, shape.lines); + add_points(ply, shape.points); + save_ply(filename, ply); + } else if (ext == ".obj" || ext == ".OBJ") { + auto obj = obj_shape{}; + add_positions(obj, shape.positions); + add_normals(obj, shape.normals); + add_texcoords(obj, shape.texcoords, flip_texcoord); + add_triangles(obj, shape.triangles, 0, !shape.normals.empty(), + !shape.texcoords.empty()); + add_quads( + obj, shape.quads, 0, !shape.normals.empty(), !shape.texcoords.empty()); + add_lines( + obj, shape.lines, 0, !shape.normals.empty(), !shape.texcoords.empty()); + add_points( + obj, shape.points, 0, !shape.normals.empty(), !shape.texcoords.empty()); + save_obj(filename, obj); + } else if (ext == ".stl" || ext == ".STL") { + auto stl = stl_model{}; + if (!shape.lines.empty()) throw io_error{filename, "lines not supported"}; + if (!shape.points.empty()) throw io_error{filename, "points not supported"}; + if (!shape.triangles.empty()) { + add_triangles(stl, shape.triangles, shape.positions, {}); + } else if (!shape.quads.empty()) { + add_triangles(stl, quads_to_triangles(shape.quads), shape.positions, {}); + } else { + throw io_error::shape_error(filename); + } + save_stl(filename, stl); + } else if (ext == ".cpp" || ext == ".CPP") { + auto to_cpp = [](const string& name, const string& vname, + const auto& values) -> string { + using T = typename std::remove_const_t< + std::remove_reference_t>::value_type; + if (values.empty()) return ""s; + auto str = "auto " + name + "_" + vname + " = "; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + for (auto& value : values) { + if constexpr (std::is_same_v || std::is_same_v) { + str += std::to_string(value) + ",\n"; + } else if constexpr (std::is_same_v || + std::is_same_v) { + str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + + "},\n"; + } else if constexpr (std::is_same_v || + std::is_same_v) { + str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + + "," + std::to_string(value.z) + "},\n"; + } else if constexpr (std::is_same_v || + std::is_same_v) { + str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + + "," + std::to_string(value.z) + "," + std::to_string(value.w) + + "},\n"; + } else { + throw std::invalid_argument{"cannot print this"}; + } + } + str += "};\n\n"; + return str; + }; + + auto name = string{"shape"}; + auto str = ""s; + str += to_cpp(name, "positions", shape.positions); + str += to_cpp(name, "normals", shape.normals); + str += to_cpp(name, "texcoords", shape.texcoords); + str += to_cpp(name, "colors", shape.colors); + str += to_cpp(name, "radius", shape.radius); + str += to_cpp(name, "points", shape.points); + str += to_cpp(name, "lines", shape.lines); + str += to_cpp(name, "triangles", shape.triangles); + str += to_cpp(name, "quads", shape.quads); + save_text(filename, str); + } else { + throw io_error::format_error(filename); + } +} + +// Load face-varying mesh +fvshape_data load_fvshape(const string& filename, bool flip_texcoord) { + auto shape = fvshape_data{}; + load_fvshape(filename, shape, flip_texcoord); + return shape; +} +void load_fvshape( + const string& filename, fvshape_data& shape, bool flip_texcoord) { + shape = {}; + + auto ext = path_extension(filename); + if (ext == ".ply" || ext == ".PLY") { + auto ply = ply_model{}; + load_ply(filename, ply); + get_positions(ply, shape.positions); + get_normals(ply, shape.normals); + get_texcoords(ply, shape.texcoords, flip_texcoord); + get_quads(ply, shape.quadspos); + if (!shape.normals.empty()) shape.quadsnorm = shape.quadspos; + if (!shape.texcoords.empty()) shape.quadstexcoord = shape.quadspos; + if (shape.quadspos.empty()) throw io_error::shape_error(filename); + } else if (ext == ".obj" || ext == ".OBJ") { + auto obj = obj_shape{}; + load_obj(filename, obj, true); + auto materials = vector{}; + get_positions(obj, shape.positions); + get_normals(obj, shape.normals); + get_texcoords(obj, shape.texcoords, flip_texcoord); + get_fvquads( + obj, shape.quadspos, shape.quadsnorm, shape.quadstexcoord, materials); + if (shape.quadspos.empty()) throw io_error::shape_error(filename); + } else if (ext == ".stl" || ext == ".STL") { + auto stl = stl_model{}; + load_stl(filename, stl, true); + if (stl.shapes.empty()) throw io_error::shape_error(filename); + if (stl.shapes.size() > 1) throw io_error::shape_error(filename); + auto fnormals = vector{}; + auto triangles = vector{}; + if (!get_triangles(stl, 0, triangles, shape.positions, fnormals)) + throw io_error::shape_error(filename); + shape.quadspos = triangles_to_quads(triangles); + } else if (ext == ".ypreset" || ext == ".YPRESET") { + shape = make_fvshape_preset(path_basename(filename)); + } else { + throw io_error::format_error(filename); + } +} + +// Save ply mesh +void save_fvshape(const string& filename, const fvshape_data& shape, + bool flip_texcoord, bool ascii) { + auto ext = path_extension(filename); + if (ext == ".ply" || ext == ".PLY") { + auto ply = ply_model{}; + auto split_quads = vector{}; + auto split_positions = vector{}; + auto split_normals = vector{}; + auto split_texcoords = vector{}; + split_facevarying(split_quads, split_positions, split_normals, + split_texcoords, shape.quadspos, shape.quadsnorm, shape.quadstexcoord, + shape.positions, shape.normals, shape.texcoords); + add_positions(ply, split_positions); + add_normals(ply, split_normals); + add_texcoords(ply, split_texcoords, flip_texcoord); + add_faces(ply, {}, split_quads); + save_ply(filename, ply); + } else if (ext == ".obj" || ext == ".OBJ") { + auto obj = obj_shape{}; + add_positions(obj, shape.positions); + add_normals(obj, shape.positions); + add_texcoords(obj, shape.texcoords, flip_texcoord); + add_fvquads(obj, shape.quadspos, shape.quadsnorm, shape.quadstexcoord, 0); + save_obj(filename, obj); + } else if (ext == ".stl" || ext == ".STL") { + auto stl = stl_model{}; + if (!shape.quadspos.empty()) { + auto split_quads = vector{}; + auto split_positions = vector{}; + auto split_normals = vector{}; + auto split_texcoords = vector{}; + split_facevarying(split_quads, split_positions, split_normals, + split_texcoords, shape.quadspos, shape.quadsnorm, shape.quadstexcoord, + shape.positions, shape.normals, shape.texcoords); + add_triangles(stl, quads_to_triangles(split_quads), split_positions, {}); + } else { + throw io_error::shape_error(filename); + } + save_stl(filename, stl); + } else if (ext == ".cpp" || ext == ".CPP") { + auto to_cpp = [](const string& name, const string& vname, + const auto& values) -> string { + using T = typename std::remove_const_t< + std::remove_reference_t>::value_type; + if (values.empty()) return ""s; + auto str = "auto " + name + "_" + vname + " = "; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + if constexpr (std::is_same_v) str += "vector{\n"; + for (auto& value : values) { + if constexpr (std::is_same_v || std::is_same_v) { + str += std::to_string(value) + ",\n"; + } else if constexpr (std::is_same_v || + std::is_same_v) { + str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + + "},\n"; + } else if constexpr (std::is_same_v || + std::is_same_v) { + str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + + "," + std::to_string(value.z) + "},\n"; + } else if constexpr (std::is_same_v || + std::is_same_v) { + str += "{" + std::to_string(value.x) + "," + std::to_string(value.y) + + "," + std::to_string(value.z) + "," + std::to_string(value.w) + + "},\n"; + } else { + throw std::invalid_argument{"cannot print this"}; + } + } + str += "};\n\n"; + return str; + }; + auto name = string{"shape"}; + auto str = ""s; + str += to_cpp(name, "positions", shape.positions); + str += to_cpp(name, "normals", shape.normals); + str += to_cpp(name, "texcoords", shape.texcoords); + str += to_cpp(name, "quadspos", shape.quadspos); + str += to_cpp(name, "quadsnorm", shape.quadsnorm); + str += to_cpp(name, "quadstexcoord", shape.quadstexcoord); + save_text(filename, str); + } else { + throw io_error::format_error(filename); + } +} + +// Shape presets used for testing. +shape_data make_shape_preset(const string& type) { + if (type == "default-quad") { + return make_rect(); + } else if (type == "default-quady") { + return make_recty(); + } else if (type == "default-cube") { + return make_box(); + } else if (type == "default-cube-rounded") { + return make_rounded_box(); + } else if (type == "default-sphere") { + return make_sphere(); + } else if (type == "default-matcube") { + return make_rounded_box(); + } else if (type == "default-matsphere") { + return make_uvspherey(); + } else if (type == "default-disk") { + return make_disk(); + } else if (type == "default-disk-bulged") { + return make_bulged_disk(); + } else if (type == "default-quad-bulged") { + return make_bulged_rect(); + } else if (type == "default-uvsphere") { + return make_uvsphere(); + } else if (type == "default-uvsphere-flipcap") { + return make_capped_uvsphere(); + } else if (type == "default-uvspherey") { + return make_uvspherey(); + } else if (type == "default-uvspherey-flipcap") { + return make_capped_uvspherey(); + } else if (type == "default-uvdisk") { + return make_uvdisk(); + } else if (type == "default-uvcylinder") { + return make_uvcylinder(); + } else if (type == "default-uvcylinder-rounded") { + return make_rounded_uvcylinder({32, 32, 32}); + } else if (type == "default-geosphere") { + return make_geosphere(); + } else if (type == "default-floor") { + return make_floor(); + } else if (type == "default-floor-bent") { + return make_bent_floor(); + } else if (type == "default-matball") { + return make_sphere(); + } else if (type == "default-hairball") { + auto base = make_sphere(pow2(5), 0.8f); + return make_hair(base, {4, 65536}, {0.2f, 0.2f}, {0.002f, 0.001f}); + } else if (type == "default-hairball-interior") { + return make_sphere(pow2(5), 0.8f); + } else if (type == "default-suzanne") { + return make_monkey(); + } else if (type == "default-cube-facevarying") { + return fvshape_to_shape(make_fvbox()); + } else if (type == "default-sphere-facevarying") { + return fvshape_to_shape(make_fvsphere()); + } else if (type == "default-quady-displaced") { + return make_recty({256, 256}); + } else if (type == "default-sphere-displaced") { + return make_sphere(128); + } else if (type == "test-cube") { + auto shape = make_rounded_box( + {32, 32, 32}, {0.075f, 0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-uvsphere") { + auto shape = make_uvsphere({32, 32}, 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-uvsphere-flipcap") { + auto shape = make_capped_uvsphere({32, 32}, 0.075f, {1, 1}, 0.3f * 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-uvspherey") { + auto shape = make_uvspherey({32, 32}, 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-uvspherey-flipcap") { + auto shape = make_capped_uvspherey({32, 32}, 0.075f, {1, 1}, 0.3f * 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-sphere") { + auto shape = make_sphere(32, 0.075f, 1); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-matcube") { + auto shape = make_rounded_box( + {32, 32, 32}, {0.075f, 0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-matsphere") { + auto shape = make_uvspherey({32, 32}, 0.075f, {2, 1}); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-sphere-displaced") { + auto shape = make_sphere(128, 0.075f, 1); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-smallsphere") { + auto shape = make_sphere(32, 0.015f, 1); + for (auto& p : shape.positions) p += {0, 0.015f, 0}; + return shape; + } else if (type == "test-disk") { + auto shape = make_disk(32, 0.075f, 1); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-uvcylinder") { + auto shape = make_rounded_uvcylinder( + {32, 32, 32}, {0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-floor") { + return make_floor({1, 1}, {2, 2}, {20, 20}); + } else if (type == "test-smallfloor") { + return make_floor({1, 1}, {0.5f, 0.5f}, {1, 1}); + } else if (type == "test-quad") { + return make_rect({1, 1}, {0.075f, 0.075f}, {1, 1}); + } else if (type == "test-quady") { + return make_recty({1, 1}, {0.075f, 0.075f}, {1, 1}); + } else if (type == "test-quad-displaced") { + return make_rect({256, 256}, {0.075f, 0.075f}, {1, 1}); + } else if (type == "test-quady-displaced") { + return make_recty({256, 256}, {0.075f, 0.075f}, {1, 1}); + } else if (type == "test-matball") { + auto shape = make_sphere(32, 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-geosphere") { + auto shape = make_geosphere(0.075f, 3); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-geosphere-flat") { + auto shape = make_geosphere(0.075f, 3); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + shape.normals = {}; + return shape; + } else if (type == "test-geosphere-subdivided") { + auto shape = make_geosphere(0.075f, 6); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-hairball1") { + auto base = make_sphere(32, 0.075f * 0.8f, 1); + for (auto& p : base.positions) p += {0, 0.075f, 0}; + return make_hair(base, {4, 65536}, {0.1f * 0.15f, 0.1f * 0.15f}, + {0.001f * 0.15f, 0.0005f * 0.15f}, {0.03f, 100}); + } else if (type == "test-hairball2") { + auto base = make_sphere(32, 0.075f * 0.8f, 1); + for (auto& p : base.positions) p += {0, 0.075f, 0}; + return make_hair(base, {4, 65536}, {0.1f * 0.15f, 0.1f * 0.15f}, + {0.001f * 0.15f, 0.0005f * 0.15f}); + } else if (type == "test-hairball3") { + auto base = make_sphere(32, 0.075f * 0.8f, 1); + for (auto& p : base.positions) p += {0, 0.075f, 0}; + return make_hair(base, {4, 65536}, {0.1f * 0.15f, 0.1f * 0.15f}, + {0.001f * 0.15f, 0.0005f * 0.15f}, {0, 0}, {0.5, 128}); + } else if (type == "test-hairball-interior") { + auto shape = make_sphere(32, 0.075f * 0.8f, 1); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-suzanne-subdiv") { + auto shape = make_monkey(0.075f * 0.8f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-cube-subdiv") { + auto fvshape = make_fvcube(0.075f); + auto shape = shape_data{}; + shape.quads = fvshape.quadspos; + shape.positions = fvshape.positions; + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-arealight1") { + return make_rect({1, 1}, {0.2f, 0.2f}); + } else if (type == "test-arealight2") { + return make_rect({1, 1}, {0.2f, 0.2f}); + } else if (type == "test-largearealight1") { + return make_rect({1, 1}, {0.4f, 0.4f}); + } else if (type == "test-largearealight2") { + return make_rect({1, 1}, {0.4f, 0.4f}); + } else if (type == "test-pointlight1") { + return make_point(0); + } else if (type == "test-pointlight2") { + return make_point(0); + } else if (type == "test-point") { + return make_points(1); + } else if (type == "test-points") { + return make_points(4096); + } else if (type == "test-points-random") { + auto shape = make_random_points(4096, {0.075f, 0.075f, 0.075f}); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape; + } else if (type == "test-points-grid") { + auto shape = make_points({256, 256}, {0.075f, 0.075f}); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + for (auto& r : shape.radius) r *= 0.075f; + return shape; + } else if (type == "test-lines-grid") { + auto shape = make_lines({256, 256}, {0.075f, 0.075f}); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + for (auto& r : shape.radius) r *= 0.075f; + return shape; + } else if (type == "test-thickpoints-grid") { + auto shape = make_points({16, 16}, {0.075f, 0.075f}); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + for (auto& r : shape.radius) r *= 0.075f * 10; + return shape; + } else if (type == "test-thicklines-grid") { + auto shape = make_lines({16, 16}, {0.075f, 0.075f}); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + for (auto& r : shape.radius) r *= 0.075f * 10; + return shape; + } else if (type == "test-particles") { + return make_points(4096); + } else if (type == "test-cloth") { + return make_rect({64, 64}, {0.2f, 0.2f}); + } else if (type == "test-clothy") { + return make_recty({64, 64}, {0.2f, 0.2f}); + } else { + throw io_error::preset_error(type); + } +} + +// Shape presets used for testing. +fvshape_data make_fvshape_preset(const string& type) { + if (type == "default-quad") { + return shape_to_fvshape(make_rect()); + } else if (type == "default-quady") { + return shape_to_fvshape(make_recty()); + } else if (type == "default-cube") { + return shape_to_fvshape(make_box()); + } else if (type == "default-cube-rounded") { + return shape_to_fvshape(make_rounded_box()); + } else if (type == "default-sphere") { + return shape_to_fvshape(make_sphere()); + } else if (type == "default-matcube") { + return shape_to_fvshape(make_rounded_box()); + } else if (type == "default-matsphere") { + return shape_to_fvshape(make_uvspherey()); + } else if (type == "default-disk") { + return shape_to_fvshape(make_disk()); + } else if (type == "default-disk-bulged") { + return shape_to_fvshape(make_bulged_disk()); + } else if (type == "default-quad-bulged") { + return shape_to_fvshape(make_bulged_rect()); + } else if (type == "default-uvsphere") { + return shape_to_fvshape(make_uvsphere()); + } else if (type == "default-uvsphere-flipcap") { + return shape_to_fvshape(make_capped_uvsphere()); + } else if (type == "default-uvspherey") { + return shape_to_fvshape(make_uvspherey()); + } else if (type == "default-uvspherey-flipcap") { + return shape_to_fvshape(make_capped_uvspherey()); + } else if (type == "default-uvdisk") { + return shape_to_fvshape(make_uvdisk()); + } else if (type == "default-uvcylinder") { + return shape_to_fvshape(make_uvcylinder()); + } else if (type == "default-uvcylinder-rounded") { + return shape_to_fvshape(make_rounded_uvcylinder({32, 32, 32})); + } else if (type == "default-geosphere") { + return shape_to_fvshape(make_geosphere()); + } else if (type == "default-floor") { + return shape_to_fvshape(make_floor()); + } else if (type == "default-floor-bent") { + return shape_to_fvshape(make_bent_floor()); + } else if (type == "default-matball") { + return shape_to_fvshape(make_sphere()); + } else if (type == "default-hairball-interior") { + return shape_to_fvshape(make_sphere(pow2(5), 0.8f)); + } else if (type == "default-suzanne") { + return shape_to_fvshape(make_monkey()); + } else if (type == "default-cube-facevarying") { + return make_fvbox(); + } else if (type == "default-sphere-facevarying") { + return make_fvsphere(); + } else if (type == "default-quady-displaced") { + return shape_to_fvshape(make_recty({256, 256})); + } else if (type == "default-sphere-displaced") { + return shape_to_fvshape(make_sphere(128)); + } else if (type == "test-cube") { + auto shape = make_rounded_box( + {32, 32, 32}, {0.075f, 0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-matsphere") { + auto shape = make_uvspherey({32, 32}, 0.075f, {2, 1}); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-uvsphere") { + auto shape = make_uvsphere({32, 32}, 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-uvsphere-flipcap") { + auto shape = make_capped_uvsphere({32, 32}, 0.075f, {1, 1}, 0.3f * 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-uvspherey") { + auto shape = make_uvspherey({32, 32}, 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-uvspherey-flipcap") { + auto shape = make_capped_uvspherey({32, 32}, 0.075f, {1, 1}, 0.3f * 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-sphere") { + auto shape = make_sphere(32, 0.075f, 1); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-sphere-displaced") { + auto shape = make_sphere(128, 0.075f, 1); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-matcube") { + auto shape = make_rounded_box( + {32, 32, 32}, {0.075f, 0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-disk") { + auto shape = make_disk(32, 0.075f, 1); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-uvcylinder") { + auto shape = make_rounded_uvcylinder( + {32, 32, 32}, {0.075f, 0.075f}, {1, 1, 1}, 0.3f * 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-floor") { + return shape_to_fvshape(make_floor({1, 1}, {2, 2}, {20, 20})); + } else if (type == "test-smallfloor") { + return shape_to_fvshape(make_floor({1, 1}, {0.5f, 0.5f}, {1, 1})); + } else if (type == "test-quad") { + return shape_to_fvshape(make_rect({1, 1}, {0.075f, 0.075f}, {1, 1})); + } else if (type == "test-quady") { + return shape_to_fvshape(make_recty({1, 1}, {0.075f, 0.075f}, {1, 1})); + } else if (type == "test-quad-displaced") { + return shape_to_fvshape(make_rect({256, 256}, {0.075f, 0.075f}, {1, 1})); + } else if (type == "test-quady-displaced") { + return shape_to_fvshape(make_recty({256, 256}, {0.075f, 0.075f}, {1, 1})); + } else if (type == "test-matball") { + auto shape = make_sphere(32, 0.075f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-suzanne-subdiv") { + auto shape = make_monkey(0.075f * 0.8f); + for (auto& p : shape.positions) p += {0, 0.075f, 0}; + return shape_to_fvshape(shape); + } else if (type == "test-cube-subdiv") { + auto fvshape = make_fvcube(0.075f); + for (auto& p : fvshape.positions) p += {0, 0.075f, 0}; + return fvshape; + } else if (type == "test-arealight1") { + return shape_to_fvshape(make_rect({1, 1}, {0.2f, 0.2f})); + } else if (type == "test-arealight2") { + return shape_to_fvshape(make_rect({1, 1}, {0.2f, 0.2f})); + } else if (type == "test-largearealight1") { + return shape_to_fvshape(make_rect({1, 1}, {0.4f, 0.4f})); + } else if (type == "test-largearealight2") { + return shape_to_fvshape(make_rect({1, 1}, {0.4f, 0.4f})); + } else if (type == "test-cloth") { + return shape_to_fvshape(make_rect({64, 64}, {0.2f, 0.2f})); + } else if (type == "test-clothy") { + return shape_to_fvshape(make_recty({64, 64}, {0.2f, 0.2f})); + } else { + throw io_error::preset_error(type); + } +} + +// Load ply mesh +bool load_shape(const string& filename, shape_data& shape, string& error, + bool flip_texcoord) { + try { + load_shape(filename, shape, flip_texcoord); + return true; + } catch (const io_error& exception) { + error = exception.what(); + return false; + } +} + +// Save ply mesh +bool save_shape(const string& filename, const shape_data& shape, string& error, + bool flip_texcoord, bool ascii) { + try { + save_shape(filename, shape, flip_texcoord, ascii); + return true; + } catch (const io_error& exception) { + error = exception.what(); + return false; + } +} + +// Load ply mesh +bool load_fvshape(const string& filename, fvshape_data& fvshape, string& error, + bool flip_texcoord) { + try { + load_fvshape(filename, fvshape, flip_texcoord); + return true; + } catch (const io_error& exception) { + error = exception.what(); + return false; + } +} + +// Save ply mesh +bool save_fvshape(const string& filename, const fvshape_data& fvshape, + string& error, bool flip_texcoord, bool ascii) { + try { + save_fvshape(filename, fvshape, flip_texcoord, ascii); + return true; + } catch (const io_error& exception) { + error = exception.what(); + return false; + } +} + +// Shape presets used ofr testing. +bool make_shape_preset(shape_data& shape, const string& type, string& error) { + try { + shape = make_shape_preset(type); + return true; + } catch (const io_error& exception) { + error = exception.what(); + return false; + } +} + +// Shape presets used for testing. +bool make_fvshape_preset( + fvshape_data& fvshape, const string& type, string& error) { + try { + fvshape = make_fvshape_preset(type); + return true; + } catch (const io_error& exception) { + error = exception.what(); + return false; + } +} + +} // namespace yocto diff --git a/libs/yocto/yocto_shapeio.h b/libs/yocto/yocto_shapeio.h new file mode 100644 index 000000000..79245b5d3 --- /dev/null +++ b/libs/yocto/yocto_shapeio.h @@ -0,0 +1,100 @@ +// +// # Yocto/ShapeIO: Shape serialization +// +// Yocto/ShapeIO supports loading and saving shapes from Ply, Obj, Stl. +// Yocto/ShapeIO is implemented in `yocto_shapeio.h` and `yocto_shapeio.cpp`. +// + +// +// LICENSE: +// +// Copyright (c) 2016 -- 2021 Fabio Pellacini +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +#ifndef _YOCTO_SHAPEIO_H_ +#define _YOCTO_SHAPEIO_H_ + +// ----------------------------------------------------------------------------- +// INCLUDES +// ----------------------------------------------------------------------------- + +#include +#include +#include +#include + +#include "yocto_shape.h" + +// ----------------------------------------------------------------------------- +// USING DIRECTIVES +// ----------------------------------------------------------------------------- +namespace yocto { + +// using directives +using std::pair; +using std::string; +using std::vector; + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// SHAPE IO +// ----------------------------------------------------------------------------- +namespace yocto { + +// Load/save a shape +shape_data load_shape(const string& filename, bool flip_texcoords = true); +void load_shape( + const string& filename, shape_data& shape, bool flip_texcoords = true); +void save_shape(const string& filename, const shape_data& shape, + bool flip_texcoords = true, bool ascii = false); + +// Load/save a subdiv +fvshape_data load_fvshape(const string& filename, bool flip_texcoords = true); +void load_fvshape( + const string& filename, fvshape_data& shape, bool flip_texcoords = true); +void save_fvshape(const string& filename, const fvshape_data& shape, + bool flip_texcoords = true, bool ascii = false); + +// Make presets. Supported mostly in IO. +shape_data make_shape_preset(const string& type); +fvshape_data make_fvshape_preset(const string& type); + +// Load/save a shape +bool load_shape(const string& filename, shape_data& shape, string& error, + bool flip_texcoords = true); +bool save_shape(const string& filename, const shape_data& shape, string& error, + bool flip_texcoords = true, bool ascii = false); + +// Load/save a subdiv +bool load_fvshape(const string& filename, fvshape_data& shape, string& error, + bool flip_texcoords = true); +bool save_fvshape(const string& filename, const fvshape_data& shape, + string& error, bool flip_texcoords = true, bool ascii = false); + +// Make presets. Supported mostly in IO. +bool make_shape_preset(shape_data& shape, const string& type, string& error); +bool make_fvshape_preset( + fvshape_data& shape, const string& type, string& error); + +} // namespace yocto + +#endif