From 3b17371648c8ccf1d128f83840f5b309bfe824a1 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Tue, 25 Oct 2016 01:30:40 +0900 Subject: [PATCH] Add cyhair2gltf converter. --- examples/cyhair_to_gltf/Makefile | 6 + examples/cyhair_to_gltf/README.md | 13 + examples/cyhair_to_gltf/cyhair2gltf.cc | 352 +++++++++++++++++++++++ examples/cyhair_to_gltf/cyhair_loader.cc | 305 ++++++++++++++++++++ examples/cyhair_to_gltf/cyhair_loader.h | 109 +++++++ 5 files changed, 785 insertions(+) create mode 100644 examples/cyhair_to_gltf/Makefile create mode 100644 examples/cyhair_to_gltf/README.md create mode 100644 examples/cyhair_to_gltf/cyhair2gltf.cc create mode 100644 examples/cyhair_to_gltf/cyhair_loader.cc create mode 100644 examples/cyhair_to_gltf/cyhair_loader.h diff --git a/examples/cyhair_to_gltf/Makefile b/examples/cyhair_to_gltf/Makefile new file mode 100644 index 0000000..1ee69b2 --- /dev/null +++ b/examples/cyhair_to_gltf/Makefile @@ -0,0 +1,6 @@ +# Suppress some C++ warnings(in clang). +EXTRA_CXXFLAGS := -Weverything -Werror + +all: + clang++ -g -o cyhair2gltf $(EXTRA_CXXFLAGS) cyhair2gltf.cc cyhair_loader.cc + diff --git a/examples/cyhair_to_gltf/README.md b/examples/cyhair_to_gltf/README.md new file mode 100644 index 0000000..4bea0bd --- /dev/null +++ b/examples/cyhair_to_gltf/README.md @@ -0,0 +1,13 @@ +# Simple CyHair to glTF converter + +For CyHair format, please refer: http://www.cemyuksel.com/cyCodeBase/code.html +For hair model with CyHair format, please refer for example: http://www.cemyuksel.com/research/hairmodels/ + +## Convert + + $ ./cyhair2gltf input.hair output.gltf + +## View + +You can view converted .gltf with glviwew exmaple(`example/glview`). + diff --git a/examples/cyhair_to_gltf/cyhair2gltf.cc b/examples/cyhair_to_gltf/cyhair2gltf.cc new file mode 100644 index 0000000..668b1b0 --- /dev/null +++ b/examples/cyhair_to_gltf/cyhair2gltf.cc @@ -0,0 +1,352 @@ +#include +#include +#include + +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wpadded" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wc++11-extensions" +#pragma clang diagnostic ignored "-Wconversion" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#pragma clang diagnostic ignored "-Wfloat-equal" +#pragma clang diagnostic ignored "-Wdeprecated" +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wextra-semi" +#pragma clang diagnostic ignored "-Wswitch-enum" +#pragma clang diagnostic ignored "-Wglobal-constructors" +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +#pragma clang diagnostic ignored "-Wdouble-promotion" +#pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + +#define PICOJSON_USE_INT64 +#include "../../picojson.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include "../../tiny_gltf_loader.h" // To import some TINYGLTF_*** macros. + +#include "cyhair_loader.h" + +// Curves are represented as an array of curve. +// i'th curve has nverts[i] points. +// TODO(syoyo) knots, order to support NURBS curve. +typedef struct +{ + std::vector points; + std::vector nverts; // # of vertices per strand(curve). +} Curves; + +// ---------------------------------------------------------------- +// writer module +// @todo { move writer code to tiny_gltf_writer.h } + +// http://www.adp-gmbh.ch/cpp/common/base64.html +static const char *base64_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +static std::string base64_encode(unsigned char const* bytes_to_encode, + size_t in_len) { + std::string ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) { + char_array_3[i++] = *(bytes_to_encode++); + if (i == 3) { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = static_cast( + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4)); + char_array_4[2] = static_cast( + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6)); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (i = 0; (i < 4); i++) ret += base64_chars[char_array_4[i]]; + i = 0; + } + } + + if (i) { + for (j = i; j < 3; j++) char_array_3[j] = '\0'; + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = static_cast( + ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4)); + char_array_4[2] = static_cast( + ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6)); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) ret += base64_chars[char_array_4[j]]; + + while ((i++ < 3)) ret += '='; + } + + return ret; +} + +static bool SaveCurvesToGLTF(const std::string& output_filename, + const Curves& curves) { + picojson::object root; + + { + picojson::object asset; + asset["generator"] = picojson::value("abc2gltf"); + asset["premultipliedAlpha"] = picojson::value(true); + asset["version"] = picojson::value(static_cast(1)); + picojson::object profile; + profile["api"] = picojson::value("WebGL"); + profile["version"] = picojson::value("1.0.2"); + asset["profile"] = picojson::value(profile); + root["assets"] = picojson::value(asset); + } + + { + picojson::object buffers; + { + { + std::string b64data = base64_encode(reinterpret_cast(curves.points.data()), curves.points.size() * sizeof(float)); + picojson::object buf; + + buf["type"] = picojson::value("arraybuffer"); + buf["uri"] = picojson::value( + std::string("data:application/octet-stream;base64,") + b64data); + buf["byteLength"] = + picojson::value(static_cast(curves.points.size() * sizeof(float))); + + buffers["points"] = picojson::value(buf); + } + + // Out extension + { + std::string b64data = base64_encode(reinterpret_cast(curves.nverts.data()), curves.nverts.size() * sizeof(int)); + picojson::object buf; + + buf["type"] = picojson::value("arraybuffer"); + buf["uri"] = picojson::value( + std::string("data:application/octet-stream;base64,") + b64data); + buf["byteLength"] = + picojson::value(static_cast(curves.nverts.size() * sizeof(int))); + + buffers["nverts"] = picojson::value(buf); + } + + } + root["buffers"] = picojson::value(buffers); + } + + { + picojson::object buffer_views; + { + { + picojson::object buffer_view_points; + buffer_view_points["buffer"] = picojson::value(std::string("points")); + buffer_view_points["byteLength"] = picojson::value(static_cast(curves.points.size() * sizeof(float))); + buffer_view_points["byteOffset"] = picojson::value(static_cast(0)); + buffer_view_points["target"] = picojson::value(static_cast(TINYGLTF_TARGET_ARRAY_BUFFER)); + buffer_views["bufferView_points"] = picojson::value(buffer_view_points); + } + + { + picojson::object buffer_view_nverts; + buffer_view_nverts["buffer"] = picojson::value(std::string("nverts")); + buffer_view_nverts["byteLength"] = picojson::value(static_cast(curves.nverts.size() * sizeof(int))); + buffer_view_nverts["byteOffset"] = picojson::value(static_cast(0)); + buffer_view_nverts["target"] = picojson::value(static_cast(TINYGLTF_TARGET_ARRAY_BUFFER)); + buffer_views["bufferView_nverts"] = picojson::value(buffer_view_nverts); + } + + } + + root["bufferViews"] = picojson::value(buffer_views); + } + + { + picojson::object attributes; + + attributes["POSITION"] = picojson::value(std::string("accessor_points")); + attributes["NVERTS"] = picojson::value(std::string("accessor_nverts")); + + // Extra information for curves primtive. + picojson::object extra; + extra["ext_mode"] = picojson::value("curves"); + + picojson::object primitive; + primitive["attributes"] = picojson::value(attributes); + //primitive["indices"] = picojson::value("accessor_indices"); + primitive["material"] = picojson::value("material_1"); + primitive["mode"] = picojson::value(static_cast(TINYGLTF_MODE_POINTS)); // Use GL_POINTS for backward compatibility + primitive["extras"] = picojson::value(extra); + + + picojson::array primitive_array; + primitive_array.push_back(picojson::value(primitive)); + + picojson::object m; + m["primitives"] = picojson::value(primitive_array); + + picojson::object meshes; + meshes["mesh_1"] = picojson::value(m); + + + root["meshes"] = picojson::value(meshes); + } + + { + picojson::object accessors; + + { + picojson::object accessor_points; + accessor_points["bufferView"] = picojson::value(std::string("bufferView_points")); + accessor_points["byteOffset"] = picojson::value(static_cast(0)); + accessor_points["byteStride"] = picojson::value(static_cast(3 * sizeof(float))); + accessor_points["componentType"] = picojson::value(static_cast(TINYGLTF_COMPONENT_TYPE_FLOAT)); + accessor_points["count"] = picojson::value(static_cast(curves.points.size())); + accessor_points["type"] = picojson::value(std::string("VEC3")); + accessors["accessor_points"] = picojson::value(accessor_points); + } + + { + picojson::object accessor_nverts; + accessor_nverts["bufferView"] = picojson::value(std::string("bufferView_nverts")); + accessor_nverts["byteOffset"] = picojson::value(static_cast(0)); + accessor_nverts["byteStride"] = picojson::value(static_cast(sizeof(int))); + accessor_nverts["componentType"] = picojson::value(static_cast(TINYGLTF_COMPONENT_TYPE_INT)); + accessor_nverts["count"] = picojson::value(static_cast(curves.nverts.size())); + accessor_nverts["type"] = picojson::value(std::string("SCALAR")); + accessors["accessor_nverts"] = picojson::value(accessor_nverts); + } + + picojson::object accessor_indices; + + root["accessors"] = picojson::value(accessors); + } + + { + // Use Default Material(Do not supply `material.technique`) + picojson::object default_material; + picojson::object materials; + + materials["material_1"] = picojson::value(default_material); + + root["materials"] = picojson::value(materials); + + } + + { + picojson::object nodes; + picojson::object node; + picojson::array meshes; + + meshes.push_back(picojson::value(std::string("mesh_1"))); + + node["meshes"] = picojson::value(meshes); + + nodes["node_1"] = picojson::value(node); + root["nodes"] = picojson::value(nodes); + } + + { + picojson::object defaultScene; + picojson::array nodes; + + nodes.push_back(picojson::value(std::string("node_1"))); + + defaultScene["nodes"] = picojson::value(nodes); + + root["scene"] = picojson::value("defaultScene"); + picojson::object scenes; + scenes["defaultScene"] = picojson::value(defaultScene); + root["scenes"] = picojson::value(scenes); + } + + + // @todo {} + picojson::object shaders; + picojson::object programs; + picojson::object techniques; + picojson::object materials; + picojson::object skins; + root["shaders"] = picojson::value(shaders); + root["programs"] = picojson::value(programs); + root["techniques"] = picojson::value(techniques); + root["materials"] = picojson::value(materials); + root["skins"] = picojson::value(skins); + + std::ofstream ifs(output_filename.c_str()); + if (ifs.bad()) { + std::cerr << "Failed to open " << output_filename << std::endl; + return false; + } + + picojson::value v = picojson::value(root); + + std::string s = v.serialize(/* pretty */true); + ifs.write(s.data(), static_cast(s.size())); + ifs.close(); + + return true; +} + + +int main(int argc, char** argv) { + std::string cyhair_filename; + std::string gltf_filename; + + if (argc < 3) { + std::cerr << "Usage: cyhair2abc input.hair output.gltf" << std::endl; + return EXIT_FAILURE; + } + + cyhair_filename = std::string(argv[1]); + gltf_filename = std::string(argv[2]); + + example::CyHair cyhair; + { + bool ret = cyhair.Load(cyhair_filename.c_str()); + if (!ret) { + std::cerr << "Failed to load " << cyhair_filename << std::endl; + return EXIT_FAILURE; + } + } + + // Convert to curves. + Curves curves; + { + // TODO(syoyo): thickness, colors, etc. + curves.points = cyhair.points_; + + // NVETS = numSegments + 1 + if (cyhair.segments_.empty()) { + for (size_t i = 0; i < cyhair.num_strands_; i++) { + curves.nverts.push_back(cyhair.default_segments_ + 1); + } + } else { + for (size_t i = 0; i < cyhair.segments_.size(); i++) { + curves.nverts.push_back(cyhair.segments_[i] + 1); + } + } + } + + bool ret = SaveCurvesToGLTF(gltf_filename, curves); + if (ret) { + std::cout << "Wrote " << gltf_filename << std::endl; + } else { + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/examples/cyhair_to_gltf/cyhair_loader.cc b/examples/cyhair_to_gltf/cyhair_loader.cc new file mode 100644 index 0000000..4f930aa --- /dev/null +++ b/examples/cyhair_to_gltf/cyhair_loader.cc @@ -0,0 +1,305 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Light Transport Entertainment, Inc. + +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. +*/ + +// Simple Cyhair loader. + +#include +#include +#include +#include +#include + +#include + +#include "cyhair_loader.h" + +namespace example { + +class real3 { + public: + real3() : x(0.0f), y(0.0f), z(0.0f) {} + real3(float v) : x(v), y(v), z(v) {} + real3(float xx, float yy, float zz) : x(xx), y(yy), z(zz) {} + //~real3() {} + + real3 operator+(const real3 &f2) const { + return real3(x + f2.x, y + f2.y, z + f2.z); + } + real3 operator*(const real3 &f2) const { + return real3(x * f2.x, y * f2.y, z * f2.z); + } + real3 operator/(const real3 &f2) const { + return real3(x / f2.x, y / f2.y, z / f2.z); + } + real3 operator/(const float f) const { return real3(x / f, y / f, z / f); } + + float x, y, z; +}; + +inline real3 operator*(float f, const real3 &v) { + return real3(v.x * f, v.y * f, v.z * f); +} + +static const float toC2B[4][4] = { + {0.0f, 6.0f / 6.0f, 0.0f, 0.0f}, + {-1.0f / 6.0f, 6.0f / 6.0f, 1.0f / 6.0f, 0.0f}, + {0.0f, 1.0f / 6.0f, 6.0f / 6.0f, -1.0f / 6.0f}, + {0.0f, 0.0, 6.0f / 6.0f, 0.0f}}; + +static const float toC2B0[4][4] = { + {0.0f, 6.0f / 6.0f, 0.0f, 0.0f}, + {0.0f, 3.0f / 6.0f, 4.0f / 6.0f, -1.0f / 6.0f}, + {0.0f, 1.0f / 6.0f, 6.0f / 6.0f, -1.0f / 6.0f}, + {0.0f, 0.0f, 6.0f / 6.0f, 0.0f}}; + +static const float toC2B1[4][4] = { + {0.0f, 6.0f / 6.0f, 0.0f, 0.0f}, + {-1.0f / 6.0f, 6.0f / 6.0f, 1.0f / 6.0f, 0.0f}, + {-1.0f / 6.0f, 4.0f / 6.0f, 3.0f / 6.0f, 0.0f}, + {0.0f, 0.0f, 6.0f / 6.0f, 0.0f}}; + +static void mul_matrix(real3 out[4], const float mat[4][4], const real3 pt[4]) { + for (int i = 0; i < 4; i++) { + out[i] = mat[i][0] * pt[0] + mat[i][1] * pt[1] + mat[i][2] * pt[2] + + mat[i][3] * pt[3]; + } +} + +static void CamullRomToCubicBezier(real3 Q[4], const real3 *cps, int cps_size, + int seg_idx) { + size_t sz = static_cast(cps_size); + if (sz == 2) { + Q[0] = cps[seg_idx]; + Q[1] = cps[seg_idx] * 2.0f / 3.0f + cps[seg_idx + 1] * 1.0f / 3.0f; + Q[2] = cps[seg_idx] * 1.0f / 3.0f + cps[seg_idx + 1] * 2.0f / 3.0f; + Q[3] = cps[seg_idx + 1]; + } else { + real3 P[4]; + if (seg_idx == 0) { + P[0] = real3(0.0f); + P[1] = cps[seg_idx + 0]; + P[2] = cps[seg_idx + 1]; + P[3] = cps[seg_idx + 2]; + mul_matrix(Q, toC2B0, P); + } else if (seg_idx == static_cast(sz - 2)) { + P[0] = cps[seg_idx - 1]; + P[1] = cps[seg_idx + 0]; + P[2] = cps[seg_idx + 1]; + P[3] = real3(0.0f); + mul_matrix(Q, toC2B1, P); + } else { + P[0] = cps[seg_idx - 1]; + P[1] = cps[seg_idx + 0]; + P[2] = cps[seg_idx + 1]; + P[3] = cps[seg_idx + 2]; + mul_matrix(Q, toC2B, P); + } + } +} + +bool CyHair::Load(const char *filename) { + FILE *fp = fopen(filename, "rb"); + if (!fp) { + return false; + } + + assert(sizeof(CyHairHeader) == 128); + CyHairHeader header; + + if (1 != fread(&header, 128, 1, fp)) { + fclose(fp); + return false; + } + if (memcmp(header.magic, "HAIR", 4) != 0) { + fclose(fp); + return false; + } + + flags_ = header.flags; + default_thickness_ = header.default_thickness; + default_transparency_ = header.default_transparency; + default_segments_ = static_cast(header.default_segments); + default_color_[0] = header.default_color[0]; + default_color_[1] = header.default_color[1]; + default_color_[2] = header.default_color[2]; + + const bool has_segments = flags_ & 0x1; + const bool has_points = flags_ & 0x2; + const bool has_thickness = flags_ & 0x4; + const bool has_transparency = flags_ & 0x8; + const bool has_color = flags_ & 0x10; + + num_strands_ = header.num_strands; + total_points_ = header.total_points; + + if (!has_points) { + std::cout << "No point data in CyHair." << std::endl; + return false; + } + + if ((default_segments_ < 1) && (!has_segments)) { + std::cout << "No valid segment information in CyHair." << std::endl; + return false; + } + + // First read all strand data from a file. + if (has_segments) { + segments_.resize(num_strands_); + if (1 != + fread(&segments_[0], sizeof(unsigned short) * num_strands_, 1, fp)) { + std::cout << "Failed to read CyHair segments data." << std::endl; + fclose(fp); + return false; + } + } + + if (has_points) { + points_.resize(3 * total_points_); + size_t n = fread(&points_[0], total_points_ * sizeof(float) * 3, 1, fp); + if (1 != n) { + std::cout << "Failed to read CyHair points data." << std::endl; + fclose(fp); + return false; + } + } + if (has_thickness) { + thicknesses_.resize(total_points_); + if (1 != fread(&thicknesses_[0], total_points_ * sizeof(float), 1, fp)) { + std::cout << "Failed to read CyHair thickness data." << std::endl; + fclose(fp); + return false; + } + } + + if (has_transparency) { + transparencies_.resize(total_points_); + if (1 != fread(&transparencies_[0], total_points_ * sizeof(float), 1, fp)) { + std::cout << "Failed to read CyHair transparencies data." << std::endl; + fclose(fp); + return false; + } + } + + if (has_color) { + colors_.resize(3 * total_points_); + if (1 != fread(&colors_[0], total_points_ * sizeof(float) * 3, 1, fp)) { + std::cout << "Failed to read CyHair colors data." << std::endl; + fclose(fp); + return false; + } + } + + // Build strand offset table. + strand_offsets_.resize(num_strands_); + strand_offsets_[0] = 0; + for (size_t i = 1; i < num_strands_; i++) { + int num_segments = segments_.empty() ? default_segments_ : segments_[i - 1]; + strand_offsets_[i] = + strand_offsets_[i - 1] + static_cast(num_segments + 1); + } + + return true; +} + +bool CyHair::ToCubicBezierCurves(std::vector *vertices, + std::vector *radiuss, + const float vertex_scale[3], + const float vertex_translate[3], + const int max_strands, const float user_thickness) { + if (points_.empty() || strand_offsets_.empty()) { + return false; + } + + vertices->clear(); + radiuss->clear(); + + int num_strands = static_cast(num_strands_); + + if ((max_strands > 0) && (max_strands < num_strands)) { + num_strands = max_strands; + } + + std::cout << "[Hair] Convert first " << num_strands << " strands from " + << max_strands << " strands in the original hair data." + << std::endl; + + // Assume input points are CatmullRom spline. + for (size_t i = 0; i < static_cast(num_strands); i++) { + if ((i % 1000) == 0) { + std::cout << i << " / " << num_strands_ << std::endl; + } + + int num_segments = segments_.empty() ? default_segments_ : segments_[i]; + if (num_segments < 2) { + continue; + } + + std::vector segment_points; + for (size_t k = 0; k < static_cast(num_segments); k++) { + // Zup -> Yup + real3 p(points_[3 * (strand_offsets_[i] + k) + 0], + points_[3 * (strand_offsets_[i] + k) + 2], + points_[3 * (strand_offsets_[i] + k) + 1]); + segment_points.push_back(p); + } + + // Skip both endpoints + for (int s = 1; s < num_segments - 1; s++) { + int seg_idx = s - 1; + real3 q[4]; + CamullRomToCubicBezier(q, segment_points.data(), num_segments, seg_idx); + + vertices->push_back(vertex_scale[0] * q[0].x + vertex_translate[0]); + vertices->push_back(vertex_scale[1] * q[0].y + vertex_translate[1]); + vertices->push_back(vertex_scale[2] * q[0].z + vertex_translate[2]); + vertices->push_back(vertex_scale[0] * q[1].x + vertex_translate[0]); + vertices->push_back(vertex_scale[1] * q[1].y + vertex_translate[1]); + vertices->push_back(vertex_scale[2] * q[1].z + vertex_translate[2]); + vertices->push_back(vertex_scale[0] * q[2].x + vertex_translate[0]); + vertices->push_back(vertex_scale[1] * q[2].y + vertex_translate[1]); + vertices->push_back(vertex_scale[2] * q[2].z + vertex_translate[2]); + vertices->push_back(vertex_scale[0] * q[3].x + vertex_translate[0]); + vertices->push_back(vertex_scale[1] * q[3].y + vertex_translate[1]); + vertices->push_back(vertex_scale[2] * q[3].z + vertex_translate[2]); + + if (user_thickness > 0) { + // Use user supplied thickness. + radiuss->push_back(user_thickness); + radiuss->push_back(user_thickness); + radiuss->push_back(user_thickness); + radiuss->push_back(user_thickness); + } else { + // TODO(syoyo) Support per point/segment thickness + radiuss->push_back(default_thickness_); + radiuss->push_back(default_thickness_); + radiuss->push_back(default_thickness_); + radiuss->push_back(default_thickness_); + } + } + } + + return true; +} + +} // namespace example diff --git a/examples/cyhair_to_gltf/cyhair_loader.h b/examples/cyhair_to_gltf/cyhair_loader.h new file mode 100644 index 0000000..3852912 --- /dev/null +++ b/examples/cyhair_to_gltf/cyhair_loader.h @@ -0,0 +1,109 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016 Light Transport Entertainment, Inc. + +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. +*/ + +// Simple Cyhair loader. + +#ifndef EXAMPLE_CYHAIR_LOADER_H_ +#define EXAMPLE_CYHAIR_LOADER_H_ + +#include +#include +#include +#include +#include + +#include + +namespace example { + +struct CyHairHeader { + char magic[4]; + unsigned int num_strands; + unsigned int total_points; + unsigned int flags; + unsigned int default_segments; + float default_thickness; + float default_transparency; + float default_color[3]; + char infomation[88]; +}; + +class CyHair { + public: + CyHair() + : flags_(0), + num_strands_(0), + total_points_(0), + default_segments_(-1), + default_thickness_(0.01f), + default_transparency_(1.0f) { + default_color_[0] = 0.5f; + default_color_[1] = 0.5f; + default_color_[2] = 0.5f; + } + + ~CyHair() {} + + /// Load CyHair data from a file. + bool Load(const char *filename); + + /// Convert to cubic bezier curves. + /// 4(cubic) * 3(xyz) * num_curves = vertices.size() + /// 4(cubic) * num_curves = radiuss.size() + /// `max_strands` limits the number of strands to convert. -1 = convert all + /// strands. + /// `thickness` overwrites strand thickness if it have positive value. + /// Apply `vertex_translate` after `vertex_scale`. + /// TODO(syoyo) return strand/segment information + bool ToCubicBezierCurves(std::vector *vertices, + std::vector *radiuss, + const float vertex_scale[3], + const float vertex_translate[3], + const int max_strands = -1, + const float thickness = -1.0f); + + CyHairHeader header_; + + // Raw CyHair values + std::vector segments_; + std::vector points_; // xyz + std::vector thicknesses_; + std::vector transparencies_; + std::vector colors_; // rgb + unsigned int flags_; + unsigned int num_strands_; + unsigned int total_points_; + int default_segments_; + float default_thickness_; + float default_transparency_; + float default_color_[3]; + int pad0; + + // Processed CyHair values + std::vector strand_offsets_; +}; + +} // namespace example + +#endif // EXAMPLE_CYHAIR_LOADER_H_