diff --git a/libs/yocto/yocto_math.h b/libs/yocto/yocto_math.h index 77c78bfd9..31184f64f 100644 --- a/libs/yocto/yocto_math.h +++ b/libs/yocto/yocto_math.h @@ -1188,6 +1188,76 @@ inline bbox3f capsule_bounds(vec3f p0, vec3f p1, float r0, float r1); } // namespace yocto +// ----------------------------------------------------------------------------- +// GEOMETRY UTILITIES +// ----------------------------------------------------------------------------- +namespace yocto { + +// Line properties. +inline vec3f line_tangent(vec3f p0, vec3f p1); +inline float line_length(vec3f p0, vec3f p1); + +// Triangle properties. +inline vec3f triangle_normal(vec3f p0, vec3f p1, vec3f p2); +inline float triangle_area(vec3f p0, vec3f p1, vec3f p2); + +// Quad properties. +inline vec3f quad_normal(vec3f p0, vec3f p1, vec3f p2, vec3f p3); +inline float quad_area(vec3f p0, vec3f p1, vec3f p2, vec3f p3); + +// Interpolates values over a line parameterized from a to b by u. Same as lerp. +template <typename T> +inline T interpolate_line(const T& p0, const T& p1, float u); + +// Interpolates values over a triangle parameterized by u and v along the +// (p1-p0) and (p2-p0) directions. Same as barycentric interpolation. +template <typename T> +inline T interpolate_triangle(const T& p0, const T& p1, const T& p2, vec2f uv); + +// Interpolates values over a quad parameterized by u and v along the +// (p1-p0) and (p2-p1) directions. Same as bilinear interpolation. +template <typename T> +inline T interpolate_quad( + const T& p0, const T& p1, const T& p2, const T& p3, vec2f uv); + +// Interpolates values along a cubic Bezier segment parametrized by u. +template <typename T> +inline T interpolate_bezier( + const T& p0, const T& p1, const T& p2, const T& p3, float u); + +// Computes the derivative of a cubic Bezier segment parametrized by u. +template <typename T> +inline T interpolate_bezier_derivative( + const T& p0, const T& p1, const T& p2, const T& p3, float u); + +// Interpolated line properties. +inline vec3f line_point(vec3f p0, vec3f p1, float u); +inline vec3f line_tangent(vec3f t0, vec3f t1, float u); + +// Interpolated triangle properties. +inline vec3f triangle_point(vec3f p0, vec3f p1, vec3f p2, vec2f uv); +inline vec3f triangle_normal(vec3f n0, vec3f n1, vec3f n2, vec2f uv); + +// Interpolated quad properties. +inline vec3f quad_point(vec3f p0, vec3f p1, vec3f p2, vec2f uv); +inline vec3f quad_normal(vec3f n0, vec3f n1, vec3f n2, vec3f n3, vec2f uv); + +// Interpolated sphere properties. +inline vec3f sphere_point(const vec3f p, float r, vec2f uv); +inline vec3f sphere_normal(const vec3f p, float r, vec2f uv); + +// Triangle tangent and bitangent from uv +inline pair<vec3f, vec3f> triangle_tangents_fromuv( + vec3f p0, vec3f p1, vec3f p2, vec2f uv0, vec2f uv1, vec2f uv2); + +// Quad tangent and bitangent from uv. Note that we pass a current_uv since +// internally we may want to split the quad in two and we need to known where +// to do it. If not interested in the split, just pass vec2f{0,0} here. +inline pair<vec3f, vec3f> quad_tangents_fromuv(vec3f p0, vec3f p1, vec3f p2, + vec3f p3, vec2f uv0, vec2f uv1, vec2f uv2, vec2f uv3, vec2f current_uv); + +} // namespace yocto + // ----------------------------------------------------------------------------- // USER INTERFACE UTILITIES // ----------------------------------------------------------------------------- @@ -2799,6 +2869,150 @@ inline bbox3f capsule_bounds(vec3f p0, vec3f p1, float r0, float r1) { } // namespace yocto +// ----------------------------------------------------------------------------- +// GEOMETRY UTILITIES +// ----------------------------------------------------------------------------- +namespace yocto { + +// Line properties. +inline vec3f line_tangent(vec3f p0, vec3f p1) { return normalize(p1 - p0); } +inline float line_length(vec3f p0, vec3f p1) { return length(p1 - p0); } + +// Triangle properties. +inline vec3f triangle_normal(vec3f p0, vec3f p1, vec3f p2) { + return normalize(cross(p1 - p0, p2 - p0)); +} +inline float triangle_area(vec3f p0, vec3f p1, vec3f p2) { + return length(cross(p1 - p0, p2 - p0)) / 2; +} + +// Quad propeties. +inline vec3f quad_normal(vec3f p0, vec3f p1, vec3f p2, vec3f p3) { + return normalize(triangle_normal(p0, p1, p3) + triangle_normal(p2, p3, p1)); +} +inline float quad_area(vec3f p0, vec3f p1, vec3f p2, vec3f p3) { + return triangle_area(p0, p1, p3) + triangle_area(p2, p3, p1); +} + +// Interpolates values over a line parameterized from a to b by u. Same as lerp. +template <typename T> +inline T interpolate_line(const T& p0, const T& p1, float u) { + return p0 * (1 - u) + p1 * u; +} + +// Interpolates values over a triangle parameterized by u and v along the +// (p1-p0) and (p2-p0) directions. Same as barycentric interpolation. +template <typename T> +inline T interpolate_triangle(const T& p0, const T& p1, const T& p2, vec2f uv) { + return p0 * (1 - uv.x - uv.y) + p1 * uv.x + p2 * uv.y; +} + +// Interpolates values over a quad parameterized by u and v along the +// (p1-p0) and (p2-p1) directions. Same as bilinear interpolation. +template <typename T> +inline T interpolate_quad( + const T& p0, const T& p1, const T& p2, const T& p3, vec2f uv) { + if (uv.x + uv.y <= 1) { + return interpolate_triangle(p0, p1, p3, uv); + } else { + return interpolate_triangle(p2, p3, p1, 1 - uv); + } +} + +// Interpolates values along a cubic Bezier segment parametrized by u. +template <typename T> +inline T interpolate_bezier( + const T& p0, const T& p1, const T& p2, const T& p3, float u) { + return p0 * (1 - u) * (1 - u) * (1 - u) + p1 * 3 * u * (1 - u) * (1 - u) + + p2 * 3 * u * u * (1 - u) + p3 * u * u * u; +} +// Computes the derivative of a cubic Bezier segment parametrized by u. +template <typename T> +inline T interpolate_bezier_derivative( + const T& p0, const T& p1, const T& p2, const T& p3, float u) { + return (p1 - p0) * 3 * (1 - u) * (1 - u) + (p2 - p1) * 6 * u * (1 - u) + + (p3 - p2) * 3 * u * u; +} + +// Interpolated line properties. +inline vec3f line_point(vec3f p0, vec3f p1, float u) { + return p0 * (1 - u) + p1 * u; +} +inline vec3f line_tangent(vec3f t0, vec3f t1, float u) { + return normalize(t0 * (1 - u) + t1 * u); +} + +// Interpolated triangle properties. +inline vec3f triangle_point(vec3f p0, vec3f p1, vec3f p2, vec2f uv) { + return p0 * (1 - uv.x - uv.y) + p1 * uv.x + p2 * uv.y; +} +inline vec3f triangle_normal(vec3f n0, vec3f n1, vec3f n2, vec2f uv) { + return normalize(n0 * (1 - uv.x - uv.y) + n1 * uv.x + n2 * uv.y); +} + +// Interpolated quad properties. +inline vec3f quad_point(vec3f p0, vec3f p1, vec3f p2, vec3f p3, vec2f uv) { + if (uv.x + uv.y <= 1) { + return triangle_point(p0, p1, p3, uv); + } else { + return triangle_point(p2, p3, p1, 1 - uv); + } +} +inline vec3f quad_normal(vec3f n0, vec3f n1, vec3f n2, vec3f n3, vec2f uv) { + if (uv.x + uv.y <= 1) { + return triangle_normal(n0, n1, n3, uv); + } else { + return triangle_normal(n2, n3, n1, 1 - uv); + } +} + +// Interpolated sphere properties. +inline vec3f sphere_point(const vec3f p, float r, vec2f uv) { + return p + r * vec3f{cos(uv.x * 2 * pif) * sin(uv.y * pif), + sin(uv.x * 2 * pif) * sin(uv.y * pif), cos(uv.y * pif)}; +} +inline vec3f sphere_normal(const vec3f p, float r, vec2f uv) { + return normalize(vec3f{cos(uv.x * 2 * pif) * sin(uv.y * pif), + sin(uv.x * 2 * pif) * sin(uv.y * pif), cos(uv.y * pif)}); +} + +// Triangle tangent and bitangent from uv +inline pair<vec3f, vec3f> triangle_tangents_fromuv( + vec3f p0, vec3f p1, vec3f p2, vec2f uv0, vec2f uv1, vec2f uv2) { + // Follows the definition in http://www.terathon.com/code/tangent.html and + // https://gist.github.com/aras-p/2843984 + // normal points up from texture space + auto p = p1 - p0; + auto q = p2 - p0; + auto s = vec2f{uv1.x - uv0.x, uv2.x - uv0.x}; + auto t = vec2f{uv1.y - uv0.y, uv2.y - uv0.y}; + auto div = s.x * t.y - s.y * t.x; + + if (div != 0) { + auto tu = vec3f{t.y * p.x - t.x * q.x, t.y * p.y - t.x * q.y, + t.y * p.z - t.x * q.z} / + div; + auto tv = vec3f{s.x * q.x - s.y * p.x, s.x * q.y - s.y * p.y, + s.x * q.z - s.y * p.z} / + div; + return {tu, tv}; + } else { + return {{1, 0, 0}, {0, 1, 0}}; + } +} + +// Quad tangent and bitangent from uv. +inline pair<vec3f, vec3f> quad_tangents_fromuv(vec3f p0, vec3f p1, vec3f p2, + vec3f p3, vec2f uv0, vec2f uv1, vec2f uv2, vec2f uv3, vec2f current_uv) { + if (current_uv.x + current_uv.y <= 1) { + return triangle_tangents_fromuv(p0, p1, p3, uv0, uv1, uv3); + } else { + return triangle_tangents_fromuv(p2, p3, p1, uv2, uv3, uv1); + } +} + +} // namespace yocto + // ----------------------------------------------------------------------------- // USER INTERFACE UTILITIES // ----------------------------------------------------------------------------- diff --git a/libs/yocto/yocto_modeling.h b/libs/yocto/yocto_modeling.h index 60def9244..4fcf7a90a 100644 --- a/libs/yocto/yocto_modeling.h +++ b/libs/yocto/yocto_modeling.h @@ -332,13 +332,6 @@ static pair<vector<vec4i>, vector<T>> subdivide_catmullclark_creased( const vector<vec2i>& creases, const vector<int>& corners, int subdivisions = 1, bool lock_boundary = false); -// Shape subdivision -inline shape_data subdivide_shape( - const shape_data& shape, int subdivisions, bool catmullclark); - -inline fvshape_data subdivide_fvshape( - const fvshape_data& shape, int subdivisions, bool catmullclark); - } // namespace yocto // ----------------------------------------------------------------------------- @@ -356,12 +349,6 @@ inline vector<vec3f> displace_vertices(const vector<vec3f>& positions, const image_t<vec4f>& displacement, float scale = 1.0f, float offset = 0.5f); -// Shape displacement -inline shape_data displace_shape(const shape_data& shape, - const image_t<float>& displacement, float height = 1, float offset = 0.5f); -inline shape_data displace_shape(const shape_data& shape, - const image_t<vec4f>& displacement, float height = 1, float offset = 0.5f); - } // namespace yocto // ----------------------------------------------------------------------------- @@ -2250,90 +2237,6 @@ static pair<vector<vec4i>, vector<T>> subdivide_catmullclark_illustration( return {quads, vertices}; } -// Shape subdivision -inline 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; -} - -// Subdivision -inline 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; -} - } // namespace yocto // ----------------------------------------------------------------------------- @@ -2367,30 +2270,6 @@ inline vector<vec3f> displace_vertices(const vector<vec3f>& positions, return displaced; } -// Displacement -inline shape_data displace_shape(const shape_data& shape, - const image_t<float>& displacement, float height, float offset) { - if (displacement.empty() || shape.texcoords.empty() || - shape.normals.empty() || (shape.triangles.empty() && shape.quads.empty())) - return shape; - auto displaced = shape; - displaced.positions = displace_vertices(displaced.positions, - displaced.normals, displaced.texcoords, displacement, height, offset); - displaced.normals = compute_normals(displaced); - return displaced; -} -inline shape_data displace_shape(const shape_data& shape, - const image_t<vec4f>& displacement, float height, float offset) { - if (displacement.empty() || shape.texcoords.empty() || - shape.normals.empty() || (shape.triangles.empty() && shape.quads.empty())) - return shape; - auto displaced = shape; - displaced.positions = displace_vertices(displaced.positions, - displaced.normals, displaced.texcoords, displacement, height, offset); - displaced.normals = compute_normals(displaced); - return displaced; -} - } // namespace yocto // ----------------------------------------------------------------------------- diff --git a/libs/yocto/yocto_sampling.h b/libs/yocto/yocto_sampling.h index 22d388eb1..831076739 100644 --- a/libs/yocto/yocto_sampling.h +++ b/libs/yocto/yocto_sampling.h @@ -165,13 +165,6 @@ inline pair<int, vec2f> sample_quads( inline vector<float> sample_quads_cdf( const vector<vec4i>& quads, const vector<vec3f>& positions); -// Shape sampling -vector<float> sample_shape_cdf(const shape_data& shape); -pair<int, vec2f> sample_shape( - const shape_data& shape, const vector<float>& cdf, float rn, vec2f ruv); -vector<pair<int, vec2f>> sample_shape( - const shape_data& shape, int num_samples, uint64_t seed = 98729387); - } // namespace yocto // ----------------------------------------------------------------------------- @@ -409,52 +402,6 @@ inline vector<float> sample_quads_cdf( return cdf; } -// Shape sampling -inline vector<float> 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()); - } -} - -inline pair<int, vec2f> sample_shape( - const shape_data& shape, const vector<float>& cdf, float rn, 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}}; - } -} - -inline vector<pair<int, vec2f>> sample_shape( - const shape_data& shape, int num_samples, uint64_t seed) { - auto cdf = sample_shape_cdf(shape); - auto points = vector<pair<int, vec2f>>(num_samples); - auto rng = make_rng(seed); - for (auto& point : points) { - point = sample_shape(shape, cdf, rand1f(rng), rand2f(rng)); - } - return points; -} - } // namespace yocto // ----------------------------------------------------------------------------- diff --git a/libs/yocto/yocto_scene.cpp b/libs/yocto/yocto_scene.cpp index f678e17f7..582c42aa2 100644 --- a/libs/yocto/yocto_scene.cpp +++ b/libs/yocto/yocto_scene.cpp @@ -107,6 +107,625 @@ ray3f eval_camera(const camera_data& camera, vec2f image_uv, vec2f lens_uv) { } // namespace yocto +// ----------------------------------------------------------------------------- +// SHAPE FUNCTIONS +// ----------------------------------------------------------------------------- +namespace yocto { + +// Shape creation +template <typename PFunc, typename TFunc> +inline shape_data make_lines(int steps, PFunc&& position, TFunc&& tangent) { + auto shape = shape_data{}; + shape.positions = vector<vec3f>(steps + 1); + shape.normals = vector<vec3f>(steps + 1); + shape.texcoords = vector<vec2f>(steps + 1); + for (auto idx : range(steps + 1)) { + auto u = (float)idx / (float)steps; + shape.positions[idx] = position(u); + shape.normals[idx] = tangent(u); + shape.texcoords[idx] = {u, 0}; + } + shape.lines = vector<vec2i>(steps); + for (auto idx : range(steps)) shape.lines[idx] = {idx, idx + 1}; + return shape; +} +template <typename PFunc, typename NFunc> +inline shape_data make_quads(vec2i steps, PFunc&& position, NFunc&& normal) { + auto shape = shape_data{}; + shape.positions = vector<vec3f>((steps.x + 1) * (steps.y + 1)); + shape.normals = vector<vec3f>((steps.x + 1) * (steps.y + 1)); + shape.texcoords = vector<vec2f>((steps.x + 1) * (steps.y + 1)); + for (auto j : range(steps.y + 1)) { + for (auto i : range(steps.x + 1)) { + auto uv = vec2f{i / (float)steps.x, j / (float)steps.y}; + auto idx = j * (steps.x + 1) + i; + shape.positions[idx] = position(uv); + shape.normals[idx] = normal(uv); + shape.texcoords[idx] = uv; + } + } + shape.quads = vector<vec4i>(steps.x * steps.y); + for (auto j : range(steps.y)) { + for (auto i : range(steps.x)) { + auto idx = j * steps.x + i; + shape.quads[idx] = {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 shape; +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMPLEMENTATION FO SHAPE PROPERTIES +// ----------------------------------------------------------------------------- +namespace yocto { + +// Interpolate vertex data +vec3f eval_position(const shape_data& shape, int element, 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, 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, vec2f uv) { + return eval_normal(shape, element, uv); +} + +vec2f eval_texcoord(const shape_data& shape, int element, 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, 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 {1, 1, 1, 1}; + } +} + +float eval_radius(const shape_data& shape, int element, 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<vec3f> compute_normals(const shape_data& shape) { + if (!shape.points.empty()) { + return vector<vec3f>(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<vec3f>(shape.positions.size(), {0, 0, 1}); + } +} +void compute_normals(vector<vec3f>& 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}); + } +} + +// Conversions +shape_data quads_to_triangles(const shape_data& shape) { + auto result = shape; + if (!shape.quads.empty()) { + result.triangles = quads_to_triangles(shape.quads); + result.quads = {}; + } + return result; +} +void quads_to_triangles_inplace(shape_data& shape) { + if (shape.quads.empty()) return; + shape.triangles = quads_to_triangles(shape.quads); + shape.quads = {}; +} + +// Transform shape +shape_data transform_shape( + const shape_data& shape, const frame3f& frame, bool non_rigid) { + return transform_shape(shape, frame, 1, non_rigid); +} +shape_data transform_shape(const shape_data& shape, const frame3f& frame, + float radius_scale, bool non_rigid) { + auto transformed = shape; + for (auto& position : transformed.positions) + position = transform_point(frame, position); + for (auto& normal : transformed.normals) + normal = transform_normal(frame, normal, non_rigid); + for (auto& radius : transformed.radius) radius *= radius_scale; + return transformed; +} +shape_data scale_shape(const shape_data& shape, float scale, float uvscale) { + if (scale == 1 && uvscale == 1) return shape; + auto transformed = shape; + for (auto& position : transformed.positions) position *= scale; + for (auto& texcoord : transformed.texcoords) texcoord *= uvscale; + return transformed; +} +shape_data scale_shape(shape_data&& shape, float scale, float uvscale) { + if (scale == 1 && uvscale == 1) return std::move(shape); + auto transformed = std::move(shape); + for (auto& position : transformed.positions) position *= scale; + for (auto& texcoord : transformed.texcoords) texcoord *= uvscale; + return transformed; +} + +// Manipulate vertex data +shape_data remove_normals(const shape_data& shape) { + auto transformed = shape; + transformed.normals = {}; + return transformed; +} +shape_data add_normals(const shape_data& shape) { + auto transformed = shape; + transformed.normals = compute_normals(shape); + return transformed; +} + +// Shape 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; +} + +// Displacement +shape_data displace_shape(const shape_data& shape, + const image_t<float>& displacement, float height, float offset) { + if (displacement.empty() || shape.texcoords.empty() || + shape.normals.empty() || (shape.triangles.empty() && shape.quads.empty())) + return shape; + auto displaced = shape; + displaced.positions = displace_vertices(displaced.positions, + displaced.normals, displaced.texcoords, displacement, height, offset); + displaced.normals = compute_normals(displaced); + return displaced; +} +shape_data displace_shape(const shape_data& shape, + const image_t<vec4f>& displacement, float height, float offset) { + if (displacement.empty() || shape.texcoords.empty() || + shape.normals.empty() || (shape.triangles.empty() && shape.quads.empty())) + return shape; + auto displaced = shape; + displaced.positions = displace_vertices(displaced.positions, + displaced.normals, displaced.texcoords, displacement, height, offset); + displaced.normals = compute_normals(displaced); + return displaced; +} + +// Shape sampling +vector<float> 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()); + } +} + +pair<int, vec2f> sample_shape( + const shape_data& shape, const vector<float>& cdf, float rn, 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<pair<int, vec2f>> sample_shape( + const shape_data& shape, int num_samples, uint64_t seed) { + auto cdf = sample_shape_cdf(shape); + auto points = vector<pair<int, vec2f>>(num_samples); + auto rng = make_rng(seed); + for (auto& point : points) { + point = sample_shape(shape, cdf, rand1f(rng), rand2f(rng)); + } + return points; +} + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// IMPLEMENTATION FOR FVSHAPE PROPERTIES +// ----------------------------------------------------------------------------- +namespace yocto { + +// Interpolate vertex data +vec3f eval_position(const fvshape_data& shape, int element, 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, 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, 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<vec3f> compute_normals(const fvshape_data& shape) { + if (!shape.quadspos.empty()) { + return quads_normals(shape.quadspos, shape.positions); + } else { + return vector<vec3f>(shape.positions.size(), {0, 0, 1}); + } +} +void compute_normals(vector<vec3f>& 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}); + } +} + +// Convert face varying data to single primitives. Returns the quads indices +// and filled vectors for pos, norm and texcoord. +static void split_facevarying(vector<vec4i>& split_quads, + vector<vec3f>& split_positions, vector<vec3f>& split_normals, + vector<vec2f>& split_texcoords, const vector<vec4i>& quadspos, + const vector<vec4i>& quadsnorm, const vector<vec4i>& quadstexcoord, + const vector<vec3f>& positions, const vector<vec3f>& normals, + const vector<vec2f>& texcoords) { + // make vertices + auto vertices = vector<vec3i>{}; + vertices.reserve(quadspos.size() * 4); + for (auto fid : range(quadspos.size())) { + for (auto c : range(4)) { + auto vertex = vec3i{ + (&quadspos[fid].x)[c], + (!quadsnorm.empty()) ? (&quadsnorm[fid].x)[c] : -1, + (!quadstexcoord.empty()) ? (&quadstexcoord[fid].x)[c] : -1, + }; + vertices.push_back(vertex); + } + } + + // sort vertices and remove duplicates + auto compare_vertices = [](vec3i a, vec3i b) { + return a.x < b.x || (a.x == b.x && a.y < b.y) || + (a.x == b.x && a.y == b.y && a.z < b.z); + }; + std::sort(vertices.begin(), vertices.end(), compare_vertices); + vertices.erase(std::unique(vertices.begin(), vertices.end()), vertices.end()); + + // fill vert data + if (!positions.empty()) { + split_positions.resize(vertices.size()); + for (auto&& [index, vertex] : enumerate(vertices)) { + split_positions[index] = positions[vertex.x]; + } + } else { + split_positions.clear(); + } + if (!normals.empty()) { + split_normals.resize(vertices.size()); + for (auto&& [index, vertex] : enumerate(vertices)) { + split_normals[index] = normals[vertex.y]; + } + } else { + split_normals.clear(); + } + if (!texcoords.empty()) { + split_texcoords.resize(vertices.size()); + for (auto&& [index, vertex] : enumerate(vertices)) { + split_texcoords[index] = texcoords[vertex.z]; + } + } else { + split_texcoords.clear(); + } +} + +// 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{"cannot 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<vec4i>{}; + fvshape.quadstexcoord = !shape.texcoords.empty() ? fvshape.quadspos + : vector<vec4i>{}; + return fvshape; +} + +// Transform shape +fvshape_data transform_fvshape( + const fvshape_data& shape, const frame3f& frame, bool non_rigid) { + auto transformed = shape; + for (auto& position : transformed.positions) + position = transform_point(frame, position); + for (auto& normal : transformed.normals) + normal = transform_normal(frame, normal, non_rigid); + return transformed; +} +fvshape_data scale_fvshape( + const fvshape_data& shape, float scale, float uvscale) { + if (scale == 1 && uvscale == 1) return shape; + auto transformed = shape; + for (auto& position : transformed.positions) position *= scale; + for (auto& texcoord : transformed.texcoords) texcoord *= uvscale; + return transformed; +} +fvshape_data scale_fvshape(fvshape_data&& shape, float scale, float uvscale) { + if (scale == 1 && uvscale == 1) return std::move(shape); + auto transformed = std::move(shape); + for (auto& position : transformed.positions) position *= scale; + for (auto& texcoord : transformed.texcoords) texcoord *= uvscale; + return transformed; +} + +// Vertex properties +fvshape_data remove_normals(const fvshape_data& shape) { + auto transformed = shape; + transformed.quadsnorm = {}; + transformed.normals = {}; + return transformed; +} +fvshape_data add_normals(const fvshape_data& shape) { + auto transformed = shape; + transformed.quadsnorm = transformed.quadspos; + transformed.normals = compute_normals(shape); + return transformed; +} + +// 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; +} + +} // namespace yocto + // ----------------------------------------------------------------------------- // TEXTURE PROPERTIES // ----------------------------------------------------------------------------- diff --git a/libs/yocto/yocto_scene.h b/libs/yocto/yocto_scene.h index 805bb8ec0..c2353b3b1 100644 --- a/libs/yocto/yocto_scene.h +++ b/libs/yocto/yocto_scene.h @@ -90,6 +90,38 @@ struct camera_data { float aperture = 0; }; +// Shape data represented as indexed meshes of elements. +// May contain either points, lines, triangles and quads. +struct shape_data { + // element data + vector<int> points = {}; + vector<vec2i> lines = {}; + vector<vec3i> triangles = {}; + vector<vec4i> quads = {}; + + // vertex data + vector<vec3f> positions = {}; + vector<vec3f> normals = {}; + vector<vec2f> texcoords = {}; + vector<vec4f> colors = {}; + vector<float> radius = {}; + vector<vec4f> tangents = {}; +}; + +// Shape data stored as a face-varying mesh. +// Not included in scenes but used for loading and subdividing. +struct fvshape_data { + // element data + vector<vec4i> quadspos = {}; + vector<vec4i> quadsnorm = {}; + vector<vec4i> quadstexcoord = {}; + + // vertex data + vector<vec3f> positions = {}; + vector<vec3f> normals = {}; + vector<vec2f> texcoords = {}; +}; + // Texture data as array of float or byte pixels. Textures can be stored in // linear or non linear color space. struct texture_data { @@ -236,6 +268,119 @@ ray3f eval_camera(const camera_data& camera, vec2f image_uv, vec2f lens_uv); } // namespace yocto +// ----------------------------------------------------------------------------- +// SHAPE PROPERTIES AND UTILITIES +// ----------------------------------------------------------------------------- +namespace yocto { + +// Shape creation +template <typename PFunc, typename TFunc> +inline shape_data make_lines(int steps, PFunc&& position, TFunc&& tangent); +template <typename PFunc, typename NFunc> +inline shape_data make_quads(int steps, PFunc&& position, NFunc&& normal); + +// Interpolate vertex data +vec3f eval_position(const shape_data& shape, int element, vec2f uv); +vec3f eval_normal(const shape_data& shape, int element, vec2f uv); +vec3f eval_tangent(const shape_data& shape, int element, vec2f uv); +vec2f eval_texcoord(const shape_data& shape, int element, vec2f uv); +vec4f eval_color(const shape_data& shape, int element, vec2f uv); +float eval_radius(const shape_data& shape, int element, 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<vec3f> compute_normals(const shape_data& shape); +void compute_normals(vector<vec3f>& normals, const shape_data& shape); + +// Conversions +shape_data quads_to_triangles(const shape_data& shape); +void quads_to_triangles_inplace(shape_data& shape); + +// Subdivision +shape_data subdivide_shape( + const shape_data& shape, int subdivisions, bool catmullclark); + +// Transform shape +shape_data transform_shape( + const shape_data& shape, const frame3f& frame, bool non_rigid = false); +shape_data transform_shape(const shape_data& shape, const frame3f& frame, + float radius_scale, bool non_rigid = false); +shape_data scale_shape(const shape_data& shape, float scale, float uvscale = 1); +shape_data scale_shape(shape_data&& shape, float scale, float uvscale = 1); +shape_data flipyz_shape(const shape_data& shape); + +// Manipulate vertex data +shape_data remove_normals(const shape_data& shape); +shape_data add_normals(const shape_data& shape); + +// Merge a shape into another +void merge_shape_inplace(shape_data& shape, const shape_data& merge); + +// Shape subdivision +shape_data subdivide_shape( + const shape_data& shape, int subdivisions, bool catmullclark); + +// Shape displacement +shape_data displace_shape(const shape_data& shape, + const image_t<float>& displacement, float height, float offset); +shape_data displace_shape(const shape_data& shape, + const image_t<vec4f>& displacement, float height, float offset); + +// Shape sampling +vector<float> sample_shape_cdf(const shape_data& shape); +pair<int, vec2f> sample_shape( + const shape_data& shape, const vector<float>& cdf, float rn, vec2f ruv); +vector<pair<int, vec2f>> sample_shape( + const shape_data& shape, int num_samples, uint64_t seed = 98729387); + +} // namespace yocto + +// ----------------------------------------------------------------------------- +// FACE-VARYING SHAPE PROPERTIES AND UTILITIES +// ----------------------------------------------------------------------------- +namespace yocto { + +// Interpolate vertex data +vec3f eval_position(const fvshape_data& shape, int element, vec2f uv); +vec3f eval_normal(const fvshape_data& shape, int element, vec2f uv); +vec2f eval_texcoord(const shape_data& shape, int element, 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<vec3f> compute_normals(const fvshape_data& shape); +void compute_normals(vector<vec3f>& 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); + +// Transform shape +fvshape_data transform_fvshape( + const fvshape_data& shape, const frame3f& frame, bool non_rigid = false); +fvshape_data scale_fvshape( + const fvshape_data& shape, float scale, float uvscale = 1); +fvshape_data scale_fvshape( + fvshape_data&& shape, float scale, float uvscale = 1); + +// Vertex properties +fvshape_data remove_normals(const fvshape_data& shape); +fvshape_data add_normals(const fvshape_data& shape); + +// Subdivision +fvshape_data subdivide_fvshape( + const fvshape_data& shape, int subdivisions, bool catmullclark); + +} // namespace yocto + // ----------------------------------------------------------------------------- // TEXTURE PROPERTIES // ----------------------------------------------------------------------------- diff --git a/libs/yocto/yocto_shape.h b/libs/yocto/yocto_shape.h index 1d773f585..ad8939870 100644 --- a/libs/yocto/yocto_shape.h +++ b/libs/yocto/yocto_shape.h @@ -68,76 +68,6 @@ using std::vector; #define inline inline __device__ __forceinline__ #endif -// ----------------------------------------------------------------------------- -// GEOMETRY UTILITIES -// ----------------------------------------------------------------------------- -namespace yocto { - -// Line properties. -inline vec3f line_tangent(vec3f p0, vec3f p1); -inline float line_length(vec3f p0, vec3f p1); - -// Triangle properties. -inline vec3f triangle_normal(vec3f p0, vec3f p1, vec3f p2); -inline float triangle_area(vec3f p0, vec3f p1, vec3f p2); - -// Quad properties. -inline vec3f quad_normal(vec3f p0, vec3f p1, vec3f p2, vec3f p3); -inline float quad_area(vec3f p0, vec3f p1, vec3f p2, vec3f p3); - -// Interpolates values over a line parameterized from a to b by u. Same as lerp. -template <typename T> -inline T interpolate_line(const T& p0, const T& p1, float u); - -// Interpolates values over a triangle parameterized by u and v along the -// (p1-p0) and (p2-p0) directions. Same as barycentric interpolation. -template <typename T> -inline T interpolate_triangle(const T& p0, const T& p1, const T& p2, vec2f uv); - -// Interpolates values over a quad parameterized by u and v along the -// (p1-p0) and (p2-p1) directions. Same as bilinear interpolation. -template <typename T> -inline T interpolate_quad( - const T& p0, const T& p1, const T& p2, const T& p3, vec2f uv); - -// Interpolates values along a cubic Bezier segment parametrized by u. -template <typename T> -inline T interpolate_bezier( - const T& p0, const T& p1, const T& p2, const T& p3, float u); - -// Computes the derivative of a cubic Bezier segment parametrized by u. -template <typename T> -inline T interpolate_bezier_derivative( - const T& p0, const T& p1, const T& p2, const T& p3, float u); - -// Interpolated line properties. -inline vec3f line_point(vec3f p0, vec3f p1, float u); -inline vec3f line_tangent(vec3f t0, vec3f t1, float u); - -// Interpolated triangle properties. -inline vec3f triangle_point(vec3f p0, vec3f p1, vec3f p2, vec2f uv); -inline vec3f triangle_normal(vec3f n0, vec3f n1, vec3f n2, vec2f uv); - -// Interpolated quad properties. -inline vec3f quad_point(vec3f p0, vec3f p1, vec3f p2, vec2f uv); -inline vec3f quad_normal(vec3f n0, vec3f n1, vec3f n2, vec3f n3, vec2f uv); - -// Interpolated sphere properties. -inline vec3f sphere_point(const vec3f p, float r, vec2f uv); -inline vec3f sphere_normal(const vec3f p, float r, vec2f uv); - -// Triangle tangent and bitangent from uv -inline pair<vec3f, vec3f> triangle_tangents_fromuv( - vec3f p0, vec3f p1, vec3f p2, vec2f uv0, vec2f uv1, vec2f uv2); - -// Quad tangent and bitangent from uv. Note that we pass a current_uv since -// internally we may want to split the quad in two and we need to known where -// to do it. If not interested in the split, just pass vec2f{0,0} here. -inline pair<vec3f, vec3f> quad_tangents_fromuv(vec3f p0, vec3f p1, vec3f p2, - vec3f p3, vec2f uv0, vec2f uv1, vec2f uv2, vec2f uv3, vec2f current_uv); - -} // namespace yocto - // ----------------------------------------------------------------------------- // COMPUTATION OF PER_VERTEX PROPERTIES // ----------------------------------------------------------------------------- @@ -199,129 +129,6 @@ inline vector<vec4i> bezier_to_lines(vector<vec2i>& lines); } // 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<int> points = {}; - vector<vec2i> lines = {}; - vector<vec3i> triangles = {}; - vector<vec4i> quads = {}; - - // vertex data - vector<vec3f> positions = {}; - vector<vec3f> normals = {}; - vector<vec2f> texcoords = {}; - vector<vec4f> colors = {}; - vector<float> radius = {}; - vector<vec4f> tangents = {}; -}; - -// Shape creation -template <typename PFunc, typename TFunc> -inline shape_data make_lines(int steps, PFunc&& position, TFunc&& tangent); -template <typename PFunc, typename NFunc> -inline shape_data make_quads(int steps, PFunc&& position, NFunc&& normal); - -// Interpolate vertex data -vec3f eval_position(const shape_data& shape, int element, vec2f uv); -vec3f eval_normal(const shape_data& shape, int element, vec2f uv); -vec3f eval_tangent(const shape_data& shape, int element, vec2f uv); -vec2f eval_texcoord(const shape_data& shape, int element, vec2f uv); -vec4f eval_color(const shape_data& shape, int element, vec2f uv); -float eval_radius(const shape_data& shape, int element, 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<vec3f> compute_normals(const shape_data& shape); -void compute_normals(vector<vec3f>& normals, const shape_data& shape); - -// Conversions -shape_data quads_to_triangles(const shape_data& shape); -void quads_to_triangles_inplace(shape_data& shape); - -// Subdivision -shape_data subdivide_shape( - const shape_data& shape, int subdivisions, bool catmullclark); - -// Transform shape -shape_data transform_shape( - const shape_data& shape, const frame3f& frame, bool non_rigid = false); -shape_data transform_shape(const shape_data& shape, const frame3f& frame, - float radius_scale, bool non_rigid = false); -shape_data scale_shape(const shape_data& shape, float scale, float uvscale = 1); -shape_data scale_shape(shape_data&& shape, float scale, float uvscale = 1); -shape_data flipyz_shape(const shape_data& shape); - -// Manipulate vertex data -shape_data remove_normals(const shape_data& shape); -shape_data add_normals(const shape_data& shape); - -// Merge a shape into another -void merge_shape_inplace(shape_data& shape, const shape_data& merge); - -} // namespace yocto - -// ----------------------------------------------------------------------------- -// FACE-VARYING SHAPE DATA AND UTILITIES -// ----------------------------------------------------------------------------- -namespace yocto { - -// Shape data stored as a face-varying mesh -struct fvshape_data { - // element data - vector<vec4i> quadspos = {}; - vector<vec4i> quadsnorm = {}; - vector<vec4i> quadstexcoord = {}; - - // vertex data - vector<vec3f> positions = {}; - vector<vec3f> normals = {}; - vector<vec2f> texcoords = {}; -}; - -// Interpolate vertex data -vec3f eval_position(const fvshape_data& shape, int element, vec2f uv); -vec3f eval_normal(const fvshape_data& shape, int element, vec2f uv); -vec2f eval_texcoord(const shape_data& shape, int element, 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<vec3f> compute_normals(const fvshape_data& shape); -void compute_normals(vector<vec3f>& 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); - -// Transform shape -fvshape_data transform_fvshape( - const fvshape_data& shape, const frame3f& frame, bool non_rigid = false); -fvshape_data scale_fvshape( - const fvshape_data& shape, float scale, float uvscale = 1); -fvshape_data scale_fvshape( - fvshape_data&& shape, float scale, float uvscale = 1); - -// Vertex properties -fvshape_data remove_normals(const fvshape_data& shape); -fvshape_data add_normals(const fvshape_data& shape); - -} // namespace yocto - // ----------------------------------------------------------------------------- // // @@ -330,150 +137,6 @@ fvshape_data add_normals(const fvshape_data& shape); // // ----------------------------------------------------------------------------- -// ----------------------------------------------------------------------------- -// GEOMETRY UTILITIES -// ----------------------------------------------------------------------------- -namespace yocto { - -// Line properties. -inline vec3f line_tangent(vec3f p0, vec3f p1) { return normalize(p1 - p0); } -inline float line_length(vec3f p0, vec3f p1) { return length(p1 - p0); } - -// Triangle properties. -inline vec3f triangle_normal(vec3f p0, vec3f p1, vec3f p2) { - return normalize(cross(p1 - p0, p2 - p0)); -} -inline float triangle_area(vec3f p0, vec3f p1, vec3f p2) { - return length(cross(p1 - p0, p2 - p0)) / 2; -} - -// Quad propeties. -inline vec3f quad_normal(vec3f p0, vec3f p1, vec3f p2, vec3f p3) { - return normalize(triangle_normal(p0, p1, p3) + triangle_normal(p2, p3, p1)); -} -inline float quad_area(vec3f p0, vec3f p1, vec3f p2, vec3f p3) { - return triangle_area(p0, p1, p3) + triangle_area(p2, p3, p1); -} - -// Interpolates values over a line parameterized from a to b by u. Same as lerp. -template <typename T> -inline T interpolate_line(const T& p0, const T& p1, float u) { - return p0 * (1 - u) + p1 * u; -} - -// Interpolates values over a triangle parameterized by u and v along the -// (p1-p0) and (p2-p0) directions. Same as barycentric interpolation. -template <typename T> -inline T interpolate_triangle(const T& p0, const T& p1, const T& p2, vec2f uv) { - return p0 * (1 - uv.x - uv.y) + p1 * uv.x + p2 * uv.y; -} - -// Interpolates values over a quad parameterized by u and v along the -// (p1-p0) and (p2-p1) directions. Same as bilinear interpolation. -template <typename T> -inline T interpolate_quad( - const T& p0, const T& p1, const T& p2, const T& p3, vec2f uv) { - if (uv.x + uv.y <= 1) { - return interpolate_triangle(p0, p1, p3, uv); - } else { - return interpolate_triangle(p2, p3, p1, 1 - uv); - } -} - -// Interpolates values along a cubic Bezier segment parametrized by u. -template <typename T> -inline T interpolate_bezier( - const T& p0, const T& p1, const T& p2, const T& p3, float u) { - return p0 * (1 - u) * (1 - u) * (1 - u) + p1 * 3 * u * (1 - u) * (1 - u) + - p2 * 3 * u * u * (1 - u) + p3 * u * u * u; -} -// Computes the derivative of a cubic Bezier segment parametrized by u. -template <typename T> -inline T interpolate_bezier_derivative( - const T& p0, const T& p1, const T& p2, const T& p3, float u) { - return (p1 - p0) * 3 * (1 - u) * (1 - u) + (p2 - p1) * 6 * u * (1 - u) + - (p3 - p2) * 3 * u * u; -} - -// Interpolated line properties. -inline vec3f line_point(vec3f p0, vec3f p1, float u) { - return p0 * (1 - u) + p1 * u; -} -inline vec3f line_tangent(vec3f t0, vec3f t1, float u) { - return normalize(t0 * (1 - u) + t1 * u); -} - -// Interpolated triangle properties. -inline vec3f triangle_point(vec3f p0, vec3f p1, vec3f p2, vec2f uv) { - return p0 * (1 - uv.x - uv.y) + p1 * uv.x + p2 * uv.y; -} -inline vec3f triangle_normal(vec3f n0, vec3f n1, vec3f n2, vec2f uv) { - return normalize(n0 * (1 - uv.x - uv.y) + n1 * uv.x + n2 * uv.y); -} - -// Interpolated quad properties. -inline vec3f quad_point(vec3f p0, vec3f p1, vec3f p2, vec3f p3, vec2f uv) { - if (uv.x + uv.y <= 1) { - return triangle_point(p0, p1, p3, uv); - } else { - return triangle_point(p2, p3, p1, 1 - uv); - } -} -inline vec3f quad_normal(vec3f n0, vec3f n1, vec3f n2, vec3f n3, vec2f uv) { - if (uv.x + uv.y <= 1) { - return triangle_normal(n0, n1, n3, uv); - } else { - return triangle_normal(n2, n3, n1, 1 - uv); - } -} - -// Interpolated sphere properties. -inline vec3f sphere_point(const vec3f p, float r, vec2f uv) { - return p + r * vec3f{cos(uv.x * 2 * pif) * sin(uv.y * pif), - sin(uv.x * 2 * pif) * sin(uv.y * pif), cos(uv.y * pif)}; -} -inline vec3f sphere_normal(const vec3f p, float r, vec2f uv) { - return normalize(vec3f{cos(uv.x * 2 * pif) * sin(uv.y * pif), - sin(uv.x * 2 * pif) * sin(uv.y * pif), cos(uv.y * pif)}); -} - -// Triangle tangent and bitangent from uv -inline pair<vec3f, vec3f> triangle_tangents_fromuv( - vec3f p0, vec3f p1, vec3f p2, vec2f uv0, vec2f uv1, vec2f uv2) { - // Follows the definition in http://www.terathon.com/code/tangent.html and - // https://gist.github.com/aras-p/2843984 - // normal points up from texture space - auto p = p1 - p0; - auto q = p2 - p0; - auto s = vec2f{uv1.x - uv0.x, uv2.x - uv0.x}; - auto t = vec2f{uv1.y - uv0.y, uv2.y - uv0.y}; - auto div = s.x * t.y - s.y * t.x; - - if (div != 0) { - auto tu = vec3f{t.y * p.x - t.x * q.x, t.y * p.y - t.x * q.y, - t.y * p.z - t.x * q.z} / - div; - auto tv = vec3f{s.x * q.x - s.y * p.x, s.x * q.y - s.y * p.y, - s.x * q.z - s.y * p.z} / - div; - return {tu, tv}; - } else { - return {{1, 0, 0}, {0, 1, 0}}; - } -} - -// Quad tangent and bitangent from uv. -inline pair<vec3f, vec3f> quad_tangents_fromuv(vec3f p0, vec3f p1, vec3f p2, - vec3f p3, vec2f uv0, vec2f uv1, vec2f uv2, vec2f uv3, vec2f current_uv) { - if (current_uv.x + current_uv.y <= 1) { - return triangle_tangents_fromuv(p0, p1, p3, uv0, uv1, uv3); - } else { - return triangle_tangents_fromuv(p2, p3, p1, uv2, uv3, uv1); - } -} - -} // namespace yocto - // ----------------------------------------------------------------------------- // IMPLEMENTATION OF COMPUTATION OF PER-VERTEX PROPERTIES // ----------------------------------------------------------------------------- @@ -685,474 +348,6 @@ inline vector<vec2i> bezier_to_lines(const vector<vec4i>& beziers) { } // namespace yocto -// ----------------------------------------------------------------------------- -// SHAPE FUNCTIONS -// ----------------------------------------------------------------------------- -namespace yocto { - -// Shape creation -template <typename PFunc, typename TFunc> -inline shape_data make_lines(int steps, PFunc&& position, TFunc&& tangent) { - auto shape = shape_data{}; - shape.positions = vector<vec3f>(steps + 1); - shape.normals = vector<vec3f>(steps + 1); - shape.texcoords = vector<vec2f>(steps + 1); - for (auto idx : range(steps + 1)) { - auto u = (float)idx / (float)steps; - shape.positions[idx] = position(u); - shape.normals[idx] = tangent(u); - shape.texcoords[idx] = {u, 0}; - } - shape.lines = vector<vec2i>(steps); - for (auto idx : range(steps)) shape.lines[idx] = {idx, idx + 1}; - return shape; -} -template <typename PFunc, typename NFunc> -inline shape_data make_quads(vec2i steps, PFunc&& position, NFunc&& normal) { - auto shape = shape_data{}; - shape.positions = vector<vec3f>((steps.x + 1) * (steps.y + 1)); - shape.normals = vector<vec3f>((steps.x + 1) * (steps.y + 1)); - shape.texcoords = vector<vec2f>((steps.x + 1) * (steps.y + 1)); - for (auto j : range(steps.y + 1)) { - for (auto i : range(steps.x + 1)) { - auto uv = vec2f{i / (float)steps.x, j / (float)steps.y}; - auto idx = j * (steps.x + 1) + i; - shape.positions[idx] = position(uv); - shape.normals[idx] = normal(uv); - shape.texcoords[idx] = uv; - } - } - shape.quads = vector<vec4i>(steps.x * steps.y); - for (auto j : range(steps.y)) { - for (auto i : range(steps.x)) { - auto idx = j * steps.x + i; - shape.quads[idx] = {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 shape; -} - -} // namespace yocto - -// ----------------------------------------------------------------------------- -// IMPLEMENTATION FO SHAPE PROPERTIES -// ----------------------------------------------------------------------------- -namespace yocto { - -// Interpolate vertex data -inline vec3f eval_position(const shape_data& shape, int element, 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}; - } -} - -inline vec3f eval_normal(const shape_data& shape, int element, 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}; - } -} - -inline vec3f eval_tangent(const shape_data& shape, int element, vec2f uv) { - return eval_normal(shape, element, uv); -} - -inline vec2f eval_texcoord(const shape_data& shape, int element, 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; - } -} - -inline vec4f eval_color(const shape_data& shape, int element, 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 {1, 1, 1, 1}; - } -} - -inline float eval_radius(const shape_data& shape, int element, 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 -inline 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. -inline vector<vec3f> compute_normals(const shape_data& shape) { - if (!shape.points.empty()) { - return vector<vec3f>(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<vec3f>(shape.positions.size(), {0, 0, 1}); - } -} -inline void compute_normals(vector<vec3f>& 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}); - } -} - -// Conversions -inline shape_data quads_to_triangles(const shape_data& shape) { - auto result = shape; - if (!shape.quads.empty()) { - result.triangles = quads_to_triangles(shape.quads); - result.quads = {}; - } - return result; -} -inline void quads_to_triangles_inplace(shape_data& shape) { - if (shape.quads.empty()) return; - shape.triangles = quads_to_triangles(shape.quads); - shape.quads = {}; -} - -// Transform shape -inline shape_data transform_shape( - const shape_data& shape, const frame3f& frame, bool non_rigid) { - return transform_shape(shape, frame, 1, non_rigid); -} -inline shape_data transform_shape(const shape_data& shape, const frame3f& frame, - float radius_scale, bool non_rigid) { - auto transformed = shape; - for (auto& position : transformed.positions) - position = transform_point(frame, position); - for (auto& normal : transformed.normals) - normal = transform_normal(frame, normal, non_rigid); - for (auto& radius : transformed.radius) radius *= radius_scale; - return transformed; -} -inline shape_data scale_shape( - const shape_data& shape, float scale, float uvscale) { - if (scale == 1 && uvscale == 1) return shape; - auto transformed = shape; - for (auto& position : transformed.positions) position *= scale; - for (auto& texcoord : transformed.texcoords) texcoord *= uvscale; - return transformed; -} -inline shape_data scale_shape(shape_data&& shape, float scale, float uvscale) { - if (scale == 1 && uvscale == 1) return std::move(shape); - auto transformed = std::move(shape); - for (auto& position : transformed.positions) position *= scale; - for (auto& texcoord : transformed.texcoords) texcoord *= uvscale; - return transformed; -} - -// Manipulate vertex data -inline shape_data remove_normals(const shape_data& shape) { - auto transformed = shape; - transformed.normals = {}; - return transformed; -} -inline shape_data add_normals(const shape_data& shape) { - auto transformed = shape; - transformed.normals = compute_normals(shape); - return transformed; -} - -} // namespace yocto - -// ----------------------------------------------------------------------------- -// IMPLEMENTATION FOR FVSHAPE PROPERTIES -// ----------------------------------------------------------------------------- -namespace yocto { - -// Interpolate vertex data -inline vec3f eval_position(const fvshape_data& shape, int element, 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}; - } -} - -inline vec3f eval_normal(const fvshape_data& shape, int element, 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}; - } -} - -inline vec2f eval_texcoord(const fvshape_data& shape, int element, 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 -inline 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. -inline vector<vec3f> compute_normals(const fvshape_data& shape) { - if (!shape.quadspos.empty()) { - return quads_normals(shape.quadspos, shape.positions); - } else { - return vector<vec3f>(shape.positions.size(), {0, 0, 1}); - } -} -inline void compute_normals(vector<vec3f>& 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}); - } -} - -// Convert face varying data to single primitives. Returns the quads indices -// and filled vectors for pos, norm and texcoord. -inline void split_facevarying(vector<vec4i>& split_quads, - vector<vec3f>& split_positions, vector<vec3f>& split_normals, - vector<vec2f>& split_texcoords, const vector<vec4i>& quadspos, - const vector<vec4i>& quadsnorm, const vector<vec4i>& quadstexcoord, - const vector<vec3f>& positions, const vector<vec3f>& normals, - const vector<vec2f>& texcoords) { - // make vertices - auto vertices = vector<vec3i>{}; - vertices.reserve(quadspos.size() * 4); - for (auto fid : range(quadspos.size())) { - for (auto c : range(4)) { - auto vertex = vec3i{ - (&quadspos[fid].x)[c], - (!quadsnorm.empty()) ? (&quadsnorm[fid].x)[c] : -1, - (!quadstexcoord.empty()) ? (&quadstexcoord[fid].x)[c] : -1, - }; - vertices.push_back(vertex); - } - } - - // sort vertices and remove duplicates - auto compare_vertices = [](vec3i a, vec3i b) { - return a.x < b.x || (a.x == b.x && a.y < b.y) || - (a.x == b.x && a.y == b.y && a.z < b.z); - }; - std::sort(vertices.begin(), vertices.end(), compare_vertices); - vertices.erase(std::unique(vertices.begin(), vertices.end()), vertices.end()); - - // fill vert data - if (!positions.empty()) { - split_positions.resize(vertices.size()); - for (auto&& [index, vertex] : enumerate(vertices)) { - split_positions[index] = positions[vertex.x]; - } - } else { - split_positions.clear(); - } - if (!normals.empty()) { - split_normals.resize(vertices.size()); - for (auto&& [index, vertex] : enumerate(vertices)) { - split_normals[index] = normals[vertex.y]; - } - } else { - split_normals.clear(); - } - if (!texcoords.empty()) { - split_texcoords.resize(vertices.size()); - for (auto&& [index, vertex] : enumerate(vertices)) { - split_texcoords[index] = texcoords[vertex.z]; - } - } else { - split_texcoords.clear(); - } -} - -// Conversions -inline 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; -} -inline fvshape_data shape_to_fvshape(const shape_data& shape) { - if (!shape.points.empty() || !shape.lines.empty()) - throw std::invalid_argument{"cannot 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<vec4i>{}; - fvshape.quadstexcoord = !shape.texcoords.empty() ? fvshape.quadspos - : vector<vec4i>{}; - return fvshape; -} - -// Transform shape -inline fvshape_data transform_fvshape( - const fvshape_data& shape, const frame3f& frame, bool non_rigid) { - auto transformed = shape; - for (auto& position : transformed.positions) - position = transform_point(frame, position); - for (auto& normal : transformed.normals) - normal = transform_normal(frame, normal, non_rigid); - return transformed; -} -inline fvshape_data scale_fvshape( - const fvshape_data& shape, float scale, float uvscale) { - if (scale == 1 && uvscale == 1) return shape; - auto transformed = shape; - for (auto& position : transformed.positions) position *= scale; - for (auto& texcoord : transformed.texcoords) texcoord *= uvscale; - return transformed; -} -inline fvshape_data scale_fvshape( - fvshape_data&& shape, float scale, float uvscale) { - if (scale == 1 && uvscale == 1) return std::move(shape); - auto transformed = std::move(shape); - for (auto& position : transformed.positions) position *= scale; - for (auto& texcoord : transformed.texcoords) texcoord *= uvscale; - return transformed; -} - -// Vertex properties -inline fvshape_data remove_normals(const fvshape_data& shape) { - auto transformed = shape; - transformed.quadsnorm = {}; - transformed.normals = {}; - return transformed; -} -inline fvshape_data add_normals(const fvshape_data& shape) { - auto transformed = shape; - transformed.quadsnorm = transformed.quadspos; - transformed.normals = compute_normals(shape); - return transformed; -} - -} // namespace yocto - // ----------------------------------------------------------------------------- // CUDA SUPPORT // -----------------------------------------------------------------------------