| | #pragma once |
| |
|
| | #include "diffvg.h" |
| | #include "shape.h" |
| | #include "scene.h" |
| | #include "vector.h" |
| | #include "cdf.h" |
| |
|
| | struct PathBoundaryData { |
| | int base_point_id; |
| | int point_id; |
| | float t; |
| | }; |
| |
|
| | struct BoundaryData { |
| | PathBoundaryData path; |
| | bool is_stroke; |
| | }; |
| |
|
| | DEVICE |
| | Vector2f sample_boundary(const Circle &circle, |
| | float t, |
| | Vector2f &normal, |
| | float &pdf, |
| | BoundaryData &, |
| | float stroke_perturb_direction, |
| | float stroke_radius) { |
| | |
| | |
| | |
| | auto offset = Vector2f{ |
| | circle.radius * cos(2 * float(M_PI) * t), |
| | circle.radius * sin(2 * float(M_PI) * t) |
| | }; |
| | normal = normalize(offset); |
| | pdf /= (2 * float(M_PI) * circle.radius); |
| | auto ret = circle.center + offset; |
| | if (stroke_perturb_direction != 0.f) { |
| | ret += stroke_perturb_direction * stroke_radius * normal; |
| | if (stroke_perturb_direction < 0) { |
| | |
| | normal = -normal; |
| | } |
| | } |
| | return ret; |
| | } |
| |
|
| | DEVICE |
| | Vector2f sample_boundary(const Ellipse &ellipse, |
| | float t, |
| | Vector2f &normal, |
| | float &pdf, |
| | BoundaryData &, |
| | float stroke_perturb_direction, |
| | float stroke_radius) { |
| | |
| | |
| | |
| | const auto &r = ellipse.radius; |
| | auto offset = Vector2f{ |
| | r.x * cos(2 * float(M_PI) * t), |
| | r.y * sin(2 * float(M_PI) * t) |
| | }; |
| | auto dxdt = -r.x * sin(2 * float(M_PI) * t) * 2 * float(M_PI); |
| | auto dydt = r.y * cos(2 * float(M_PI) * t) * 2 * float(M_PI); |
| | |
| | normal = normalize(Vector2f{dydt, -dxdt}); |
| | pdf /= sqrt(square(dxdt) + square(dydt)); |
| | auto ret = ellipse.center + offset; |
| | if (stroke_perturb_direction != 0.f) { |
| | ret += stroke_perturb_direction * stroke_radius * normal; |
| | if (stroke_perturb_direction < 0) { |
| | |
| | normal = -normal; |
| | } |
| | } |
| | return ret; |
| | } |
| |
|
| | DEVICE |
| | Vector2f sample_boundary(const Path &path, |
| | const float *path_length_cdf, |
| | const float *path_length_pmf, |
| | const int *point_id_map, |
| | float path_length, |
| | float t, |
| | Vector2f &normal, |
| | float &pdf, |
| | BoundaryData &data, |
| | float stroke_perturb_direction, |
| | float stroke_radius) { |
| | if (stroke_perturb_direction != 0.f && !path.is_closed) { |
| | |
| | |
| | |
| | auto cap_length = 0.f; |
| | if (path.thickness != nullptr) { |
| | auto r0 = path.thickness[0]; |
| | auto r1 = path.thickness[path.num_points - 1]; |
| | cap_length = float(M_PI) * (r0 + r1); |
| | } else { |
| | cap_length = 2 * float(M_PI) * stroke_radius; |
| | } |
| | auto cap_prob = cap_length / (cap_length + path_length); |
| | if (t < cap_prob) { |
| | t = t / cap_prob; |
| | pdf *= cap_prob; |
| | auto r0 = stroke_radius; |
| | auto r1 = stroke_radius; |
| | if (path.thickness != nullptr) { |
| | r0 = path.thickness[0]; |
| | r1 = path.thickness[path.num_points - 1]; |
| | } |
| | |
| | |
| | |
| | if (stroke_perturb_direction < 0) { |
| | |
| | auto p0 = Vector2f{path.points[0], path.points[1]}; |
| | auto offset = Vector2f{ |
| | r0 * cos(2 * float(M_PI) * t), |
| | r0 * sin(2 * float(M_PI) * t) |
| | }; |
| | normal = normalize(offset); |
| | pdf /= (2 * float(M_PI) * r0); |
| | data.path.base_point_id = 0; |
| | data.path.point_id = 0; |
| | data.path.t = 0; |
| | return p0 + offset; |
| | } else { |
| | |
| | auto p0 = Vector2f{path.points[2 * (path.num_points - 1)], |
| | path.points[2 * (path.num_points - 1) + 1]}; |
| | auto offset = Vector2f{ |
| | r1 * cos(2 * float(M_PI) * t), |
| | r1 * sin(2 * float(M_PI) * t) |
| | }; |
| | normal = normalize(offset); |
| | pdf /= (2 * float(M_PI) * r1); |
| | data.path.base_point_id = path.num_base_points - 1; |
| | data.path.point_id = path.num_points - 2 - |
| | path.num_control_points[data.path.base_point_id]; |
| | data.path.t = 1; |
| | return p0 + offset; |
| | } |
| | } else { |
| | t = (t - cap_prob) / (1 - cap_prob); |
| | pdf *= (1 - cap_prob); |
| | } |
| | } |
| | |
| | auto sample_id = sample(path_length_cdf, |
| | path.num_base_points, |
| | t, |
| | &t); |
| | assert(sample_id >= 0 && sample_id < path.num_base_points); |
| | auto point_id = point_id_map[sample_id]; |
| | if (path.num_control_points[sample_id] == 0) { |
| | |
| | auto i0 = point_id; |
| | auto i1 = (i0 + 1) % path.num_points; |
| | assert(i0 < path.num_points); |
| | auto p0 = Vector2f{path.points[2 * i0], path.points[2 * i0 + 1]}; |
| | auto p1 = Vector2f{path.points[2 * i1], path.points[2 * i1 + 1]}; |
| | data.path.base_point_id = sample_id; |
| | data.path.point_id = point_id; |
| | data.path.t = t; |
| | if (t < -1e-3f || t > 1+1e-3f) { |
| | |
| | pdf = 0; |
| | return Vector2f{0, 0}; |
| | } |
| | auto tangent = (p1 - p0); |
| | auto tan_len = length(tangent); |
| | if (tan_len == 0) { |
| | |
| | pdf = 0; |
| | return Vector2f{0, 0}; |
| | } |
| | normal = Vector2f{-tangent.y, tangent.x} / tan_len; |
| | |
| | pdf *= path_length_pmf[sample_id] / tan_len; |
| | auto ret = p0 + t * (p1 - p0); |
| | if (stroke_perturb_direction != 0.f) { |
| | auto r0 = stroke_radius; |
| | auto r1 = stroke_radius; |
| | if (path.thickness != nullptr) { |
| | r0 = path.thickness[i0]; |
| | r1 = path.thickness[i1]; |
| | } |
| | auto r = r0 + t * (r1 - r0); |
| | ret += stroke_perturb_direction * r * normal; |
| | if (stroke_perturb_direction < 0) { |
| | |
| | normal = -normal; |
| | } |
| | } |
| | return ret; |
| | } else if (path.num_control_points[sample_id] == 1) { |
| | |
| | auto i0 = point_id; |
| | auto i1 = i0 + 1; |
| | auto i2 = (i0 + 2) % path.num_points; |
| | auto p0 = Vector2f{path.points[2 * i0], path.points[2 * i0 + 1]}; |
| | auto p1 = Vector2f{path.points[2 * i1], path.points[2 * i1 + 1]}; |
| | auto p2 = Vector2f{path.points[2 * i2], path.points[2 * i2 + 1]}; |
| | auto eval = [&](float t) -> Vector2f { |
| | auto tt = 1 - t; |
| | return (tt*tt)*p0 + (2*tt*t)*p1 + (t*t)*p2; |
| | }; |
| | data.path.base_point_id = sample_id; |
| | data.path.point_id = point_id; |
| | data.path.t = t; |
| | if (t < -1e-3f || t > 1+1e-3f) { |
| | |
| | pdf = 0; |
| | return Vector2f{0, 0}; |
| | } |
| | auto tangent = 2 * (1 - t) * (p1 - p0) + 2 * t * (p2 - p1); |
| | auto tan_len = length(tangent); |
| | if (tan_len == 0) { |
| | |
| | pdf = 0; |
| | return Vector2f{0, 0}; |
| | } |
| | normal = Vector2f{-tangent.y, tangent.x} / tan_len; |
| | |
| | pdf *= path_length_pmf[sample_id] / tan_len; |
| | auto ret = eval(t); |
| | if (stroke_perturb_direction != 0.f) { |
| | auto r0 = stroke_radius; |
| | auto r1 = stroke_radius; |
| | auto r2 = stroke_radius; |
| | if (path.thickness != nullptr) { |
| | r0 = path.thickness[i0]; |
| | r1 = path.thickness[i1]; |
| | r2 = path.thickness[i2]; |
| | } |
| | auto tt = 1 - t; |
| | auto r = (tt*tt)*r0 + (2*tt*t)*r1 + (t*t)*r2; |
| | ret += stroke_perturb_direction * r * normal; |
| | if (stroke_perturb_direction < 0) { |
| | |
| | normal = -normal; |
| | } |
| | } |
| | return ret; |
| | } else if (path.num_control_points[sample_id] == 2) { |
| | |
| | auto i0 = point_id; |
| | auto i1 = point_id + 1; |
| | auto i2 = point_id + 2; |
| | auto i3 = (point_id + 3) % path.num_points; |
| | assert(i0 >= 0 && i2 < path.num_points); |
| | auto p0 = Vector2f{path.points[2 * i0], path.points[2 * i0 + 1]}; |
| | auto p1 = Vector2f{path.points[2 * i1], path.points[2 * i1 + 1]}; |
| | auto p2 = Vector2f{path.points[2 * i2], path.points[2 * i2 + 1]}; |
| | auto p3 = Vector2f{path.points[2 * i3], path.points[2 * i3 + 1]}; |
| | auto eval = [&](float t) -> Vector2f { |
| | auto tt = 1 - t; |
| | return (tt*tt*tt)*p0 + (3*tt*tt*t)*p1 + (3*tt*t*t)*p2 + (t*t*t)*p3; |
| | }; |
| | data.path.base_point_id = sample_id; |
| | data.path.point_id = point_id; |
| | data.path.t = t; |
| | if (t < -1e-3f || t > 1+1e-3f) { |
| | |
| | pdf = 0; |
| | return Vector2f{0, 0}; |
| | } |
| | auto tangent = 3 * square(1 - t) * (p1 - p0) + 6 * (1 - t) * t * (p2 - p1) + 3 * t * t * (p3 - p2); |
| | auto tan_len = length(tangent); |
| | if (tan_len == 0) { |
| | |
| | pdf = 0; |
| | return Vector2f{0, 0}; |
| | } |
| | normal = Vector2f{-tangent.y, tangent.x} / tan_len; |
| | |
| | pdf *= path_length_pmf[sample_id] / tan_len; |
| | auto ret = eval(t); |
| | if (stroke_perturb_direction != 0.f) { |
| | auto r0 = stroke_radius; |
| | auto r1 = stroke_radius; |
| | auto r2 = stroke_radius; |
| | auto r3 = stroke_radius; |
| | if (path.thickness != nullptr) { |
| | r0 = path.thickness[i0]; |
| | r1 = path.thickness[i1]; |
| | r2 = path.thickness[i2]; |
| | r3 = path.thickness[i3]; |
| | } |
| | auto tt = 1 - t; |
| | auto r = (tt*tt*tt)*r0 + (3*tt*tt*t)*r1 + (3*tt*t*t)*r2 + (t*t*t)*r3; |
| | ret += stroke_perturb_direction * r * normal; |
| | if (stroke_perturb_direction < 0) { |
| | |
| | normal = -normal; |
| | } |
| | } |
| | return ret; |
| | } else { |
| | assert(false); |
| | } |
| | assert(false); |
| | return Vector2f{0, 0}; |
| | } |
| |
|
| | DEVICE |
| | Vector2f sample_boundary(const Rect &rect, |
| | float t, Vector2f &normal, |
| | float &pdf, |
| | BoundaryData &, |
| | float stroke_perturb_direction, |
| | float stroke_radius) { |
| | |
| | auto w = rect.p_max.x - rect.p_min.x; |
| | auto h = rect.p_max.y - rect.p_min.y; |
| | pdf /= (2 * (w +h)); |
| | if (t <= w / (w + h)) { |
| | |
| | |
| | t *= (w + h) / w; |
| | |
| | if (t < 0.5f) { |
| | |
| | normal = Vector2f{0, -1}; |
| | auto ret = rect.p_min + 2 * t * Vector2f{rect.p_max.x - rect.p_min.x, 0.f}; |
| | if (stroke_perturb_direction != 0.f) { |
| | ret += stroke_perturb_direction * stroke_radius * normal; |
| | if (stroke_perturb_direction < 0) { |
| | |
| | normal = -normal; |
| | } |
| | } |
| | return ret; |
| | } else { |
| | |
| | normal = Vector2f{0, 1}; |
| | auto ret = Vector2f{rect.p_min.x, rect.p_max.y} + |
| | 2 * (t - 0.5f) * Vector2f{rect.p_max.x - rect.p_min.x, 0.f}; |
| | if (stroke_perturb_direction != 0.f) { |
| | ret += stroke_perturb_direction * stroke_radius * normal; |
| | if (stroke_perturb_direction < 0) { |
| | |
| | normal = -normal; |
| | } |
| | } |
| | return ret; |
| | } |
| | } else { |
| | |
| | |
| | assert(h > 0); |
| | t = (t - w / (w + h)) * (w + h) / h; |
| | |
| | if (t < 0.5f) { |
| | |
| | normal = Vector2f{-1, 0}; |
| | auto ret = rect.p_min + 2 * t * Vector2f{0.f, rect.p_max.y - rect.p_min.y}; |
| | if (stroke_perturb_direction != 0.f) { |
| | ret += stroke_perturb_direction * stroke_radius * normal; |
| | if (stroke_perturb_direction < 0) { |
| | |
| | normal = -normal; |
| | } |
| | } |
| | return ret; |
| | } else { |
| | |
| | normal = Vector2f{1, 0}; |
| | auto ret = Vector2f{rect.p_max.x, rect.p_min.y} + |
| | 2 * (t - 0.5f) * Vector2f{0.f, rect.p_max.y - rect.p_min.y}; |
| | if (stroke_perturb_direction != 0.f) { |
| | ret += stroke_perturb_direction * stroke_radius * normal; |
| | if (stroke_perturb_direction < 0) { |
| | |
| | normal = -normal; |
| | } |
| | } |
| | return ret; |
| | } |
| | } |
| | } |
| |
|
| | DEVICE |
| | Vector2f sample_boundary(const SceneData &scene, |
| | int shape_group_id, |
| | int shape_id, |
| | float t, |
| | Vector2f &normal, |
| | float &pdf, |
| | BoundaryData &data) { |
| | const ShapeGroup &shape_group = scene.shape_groups[shape_group_id]; |
| | const Shape &shape = scene.shapes[shape_id]; |
| | pdf = 1; |
| | |
| | |
| | |
| | auto stroke_perturb = false; |
| | if (shape_group.fill_color != nullptr && shape_group.stroke_color != nullptr) { |
| | if (t < 0.5f) { |
| | stroke_perturb = false; |
| | t = 2 * t; |
| | pdf = 0.5f; |
| | } else { |
| | stroke_perturb = true; |
| | t = 2 * (t - 0.5f); |
| | pdf = 0.5f; |
| | } |
| | } else if (shape_group.stroke_color != nullptr) { |
| | stroke_perturb = true; |
| | } |
| | data.is_stroke = stroke_perturb; |
| | auto stroke_perturb_direction = 0.f; |
| | if (stroke_perturb) { |
| | if (t < 0.5f) { |
| | stroke_perturb_direction = -1.f; |
| | t = 2 * t; |
| | pdf *= 0.5f; |
| | } else { |
| | stroke_perturb_direction = 1.f; |
| | t = 2 * (t - 0.5f); |
| | pdf *= 0.5f; |
| | } |
| | } |
| | switch (shape.type) { |
| | case ShapeType::Circle: |
| | return sample_boundary( |
| | *(const Circle *)shape.ptr, t, normal, pdf, data, stroke_perturb_direction, shape.stroke_width); |
| | case ShapeType::Ellipse: |
| | return sample_boundary( |
| | *(const Ellipse *)shape.ptr, t, normal, pdf, data, stroke_perturb_direction, shape.stroke_width); |
| | case ShapeType::Path: |
| | return sample_boundary( |
| | *(const Path *)shape.ptr, |
| | scene.path_length_cdf[shape_id], |
| | scene.path_length_pmf[shape_id], |
| | scene.path_point_id_map[shape_id], |
| | scene.shapes_length[shape_id], |
| | t, |
| | normal, |
| | pdf, |
| | data, |
| | stroke_perturb_direction, |
| | shape.stroke_width); |
| | case ShapeType::Rect: |
| | return sample_boundary( |
| | *(const Rect *)shape.ptr, t, normal, pdf, data, stroke_perturb_direction, shape.stroke_width); |
| | } |
| | assert(false); |
| | return Vector2f{}; |
| | } |
| |
|
| |
|