517 lines
15 KiB
C++
517 lines
15 KiB
C++
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#include "scene/camera.h"
|
|
#include "scene/mesh.h"
|
|
|
|
#include "subd/dice.h"
|
|
#include "subd/patch.h"
|
|
#include "subd/split.h"
|
|
|
|
#include "subd/subpatch.h"
|
|
#include "util/algorithm.h"
|
|
|
|
#include "util/math.h"
|
|
#include "util/types.h"
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
/* DiagSplit */
|
|
|
|
enum {
|
|
DSPLIT_NON_UNIFORM = -1,
|
|
DSPLIT_MAX_DEPTH = 32,
|
|
DSPLIT_MAX_SEGMENTS = 8,
|
|
};
|
|
|
|
DiagSplit::DiagSplit(const SubdParams ¶ms_) : params(params_) {}
|
|
|
|
int DiagSplit::alloc_verts(int num)
|
|
{
|
|
const int index = num_verts;
|
|
num_verts += num;
|
|
return index;
|
|
}
|
|
|
|
SubEdge *DiagSplit::alloc_edge(const int v0, const int v1)
|
|
{
|
|
const SubEdge edge(v0, v1);
|
|
const auto it = edges.find(edge);
|
|
return const_cast<SubEdge *>((it == edges.end()) ? &*(edges.emplace(edge).first) : &*it);
|
|
}
|
|
|
|
void DiagSplit::alloc_edge(SubPatch::Edge *sub_edge, int v0, int v1)
|
|
{
|
|
sub_edge->edge = (v0 < v1) ? alloc_edge(v0, v1) : alloc_edge(v1, v0);
|
|
sub_edge->reversed = sub_edge->edge->start_vert_index != v0;
|
|
}
|
|
|
|
void DiagSplit::alloc_subpatch(SubPatch &&sub)
|
|
{
|
|
assert(sub.edge_u0.edge->T >= 1);
|
|
assert(sub.edge_v1.edge->T >= 1);
|
|
assert(sub.edge_u1.edge->T >= 1);
|
|
assert(sub.edge_v0.edge->T >= 1);
|
|
|
|
sub.inner_grid_vert_offset = alloc_verts(sub.calc_num_inner_verts());
|
|
num_triangles += sub.calc_num_triangles();
|
|
|
|
subpatches.push_back(std::move(sub));
|
|
}
|
|
|
|
float3 DiagSplit::to_world(const Patch *patch, const float2 uv)
|
|
{
|
|
float3 P;
|
|
|
|
patch->eval(&P, nullptr, nullptr, nullptr, uv.x, uv.y);
|
|
if (params.camera) {
|
|
P = transform_point(¶ms.objecttoworld, P);
|
|
}
|
|
|
|
return P;
|
|
}
|
|
|
|
int DiagSplit::T(
|
|
const Patch *patch, float2 Pstart, float2 Pend, const int depth, const bool recursive_resolve)
|
|
{
|
|
/* May not be necessary, but better to be safe. */
|
|
if (Pend.x < Pstart.x || Pend.y < Pstart.y) {
|
|
swap(Pstart, Pend);
|
|
}
|
|
|
|
float Lsum = 0.0f;
|
|
float Lmax = 0.0f;
|
|
|
|
float3 Plast = to_world(patch, Pstart);
|
|
|
|
for (int i = 1; i < params.test_steps; i++) {
|
|
const float t = i / (float)(params.test_steps - 1);
|
|
|
|
const float3 P = to_world(patch, Pstart + t * (Pend - Pstart));
|
|
|
|
float L;
|
|
|
|
if (!params.camera) {
|
|
L = len(P - Plast);
|
|
}
|
|
else {
|
|
Camera *cam = params.camera;
|
|
|
|
const float pixel_width = cam->world_to_raster_size((P + Plast) * 0.5f);
|
|
L = len(P - Plast) / pixel_width;
|
|
}
|
|
|
|
Lsum += L;
|
|
Lmax = max(L, Lmax);
|
|
|
|
Plast = P;
|
|
}
|
|
|
|
const int tmin = (int)ceilf(Lsum / params.dicing_rate);
|
|
const int tmax = (int)ceilf(
|
|
(params.test_steps - 1) * Lmax /
|
|
params.dicing_rate); // XXX paper says N instead of N-1, seems wrong?
|
|
int res = max(tmax, 1);
|
|
|
|
if (tmax - tmin > params.split_threshold) {
|
|
if (!recursive_resolve) {
|
|
res = DSPLIT_NON_UNIFORM;
|
|
}
|
|
else {
|
|
const float2 P = (Pstart + Pend) * 0.5f;
|
|
res = T(patch, Pstart, P, depth, true) + T(patch, P, Pend, depth, true);
|
|
}
|
|
}
|
|
|
|
res = limit_edge_factor(patch, Pstart, Pend, res);
|
|
|
|
/* Limit edge factor so we don't go beyond max depth. */
|
|
if (depth >= DSPLIT_MAX_DEPTH - 2) {
|
|
if (res == DSPLIT_NON_UNIFORM || res > DSPLIT_MAX_SEGMENTS) {
|
|
res = DSPLIT_MAX_SEGMENTS;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
int DiagSplit::limit_edge_factor(const Patch *patch,
|
|
const float2 Pstart,
|
|
const float2 Pend,
|
|
const int T)
|
|
{
|
|
const int max_t = 1 << params.max_level;
|
|
int max_t_for_edge = int(max_t * len(Pstart - Pend));
|
|
|
|
if (patch->from_ngon) {
|
|
max_t_for_edge >>= 1; /* Initial split of ngon causes edges to extend half the distance. */
|
|
}
|
|
|
|
const int limit_T = (max_t_for_edge <= 1) ? 1 : min(T, max_t_for_edge);
|
|
|
|
assert(limit_T >= 1 || limit_T == DSPLIT_NON_UNIFORM);
|
|
return limit_T;
|
|
}
|
|
|
|
void DiagSplit::assign_edge_factor(SubEdge *edge, const int T)
|
|
{
|
|
assert(edge->T == 0 || edge->T == DSPLIT_NON_UNIFORM);
|
|
edge->T = T;
|
|
|
|
if (edge->T != DSPLIT_NON_UNIFORM) {
|
|
edge->second_vert_index = alloc_verts(edge->T - 1);
|
|
}
|
|
}
|
|
|
|
void DiagSplit::resolve_edge_factors(const SubPatch &sub, const int depth)
|
|
{
|
|
/* Compute edge factor if not already set. Or if DSPLIT_NON_UNIFORM and splitting is
|
|
* no longer possible because the opposite side can't be split. */
|
|
if (sub.edge_u0.edge->T == 0 ||
|
|
(sub.edge_u0.edge->T == DSPLIT_NON_UNIFORM && sub.edge_u1.edge->T == 1))
|
|
{
|
|
assign_edge_factor(sub.edge_u0.edge, T(sub.patch, sub.uv00, sub.uv10, depth, true));
|
|
}
|
|
if (sub.edge_v1.edge->T == 0 ||
|
|
(sub.edge_v1.edge->T == DSPLIT_NON_UNIFORM && sub.edge_v0.edge->T == 1))
|
|
{
|
|
assign_edge_factor(sub.edge_v1.edge, T(sub.patch, sub.uv10, sub.uv11, depth, true));
|
|
}
|
|
if (sub.edge_u1.edge->T == 0 ||
|
|
(sub.edge_u1.edge->T == DSPLIT_NON_UNIFORM && sub.edge_u0.edge->T == 1))
|
|
{
|
|
assign_edge_factor(sub.edge_u1.edge, T(sub.patch, sub.uv11, sub.uv01, depth, true));
|
|
}
|
|
if (sub.edge_v0.edge->T == 0 ||
|
|
(sub.edge_v0.edge->T == DSPLIT_NON_UNIFORM && sub.edge_v1.edge->T == 1))
|
|
{
|
|
assign_edge_factor(sub.edge_v0.edge, T(sub.patch, sub.uv01, sub.uv00, depth, true));
|
|
}
|
|
}
|
|
|
|
float2 DiagSplit::split_edge(
|
|
const Patch *patch, SubPatch::Edge *subedge, float2 Pstart, float2 Pend, const int depth)
|
|
{
|
|
/* This splits following the direction of the edge itself, not subpatch edge direction. */
|
|
if (subedge->reversed) {
|
|
swap(Pstart, Pend);
|
|
}
|
|
|
|
SubEdge *edge = subedge->edge;
|
|
|
|
if (edge->T == DSPLIT_NON_UNIFORM) {
|
|
/* Split down the middle. */
|
|
const float2 P = 0.5f * (Pstart + Pend);
|
|
if (edge->mid_vert_index == -1) {
|
|
/* Allocate mid vertex and edges. */
|
|
edge->mid_vert_index = alloc_verts(1);
|
|
|
|
SubEdge *edge_a = alloc_edge(edge->start_vert_index, edge->mid_vert_index);
|
|
SubEdge *edge_b = alloc_edge(edge->mid_vert_index, edge->end_vert_index);
|
|
assign_edge_factor(edge_a, T(patch, Pstart, P, depth));
|
|
assign_edge_factor(edge_b, T(patch, P, Pend, depth));
|
|
}
|
|
assert(P.x >= 0 && P.x <= 1.0f && P.y >= 0.0f && P.y <= 1.0f);
|
|
return P;
|
|
}
|
|
|
|
assert(edge->T >= 2);
|
|
const int mid = edge->T / 2;
|
|
|
|
/* T is final and edge vertices are already allocated. An adjacent subpatch may not
|
|
* split this edge. So we ensure T and vertex indices match up with the non-split edge. */
|
|
if (edge->mid_vert_index == -1) {
|
|
/* Allocate mid vertex and edges. */
|
|
edge->mid_vert_index = edge->second_vert_index - 1 + mid;
|
|
|
|
SubEdge *edge_a = alloc_edge(edge->start_vert_index, edge->mid_vert_index);
|
|
SubEdge *edge_b = alloc_edge(edge->mid_vert_index, edge->end_vert_index);
|
|
edge_a->T = mid;
|
|
edge_b->T = edge->T - mid;
|
|
edge_a->second_vert_index = edge->second_vert_index;
|
|
edge_b->second_vert_index = edge->second_vert_index + edge_a->T;
|
|
}
|
|
|
|
const float2 P = interp(Pstart, Pend, mid / (float)edge->T);
|
|
assert(P.x >= 0 && P.x <= 1.0f && P.y >= 0.0f && P.y <= 1.0f);
|
|
|
|
return P;
|
|
}
|
|
|
|
void DiagSplit::split(SubPatch &&sub, const int depth)
|
|
{
|
|
/* Edge factors are limited so that this should never happen. */
|
|
assert(depth <= DSPLIT_MAX_DEPTH);
|
|
|
|
/* Set edge factors if we haven't already. */
|
|
resolve_edge_factors(sub, depth);
|
|
|
|
/* Split subpatch if edges are marked as DSPLIT_NON_UNIFORM,
|
|
* or if the following conditions are met:
|
|
* - Both edges have at least 2 segments.
|
|
* - Either edge has more than DSPLIT_MAX_SEGMENTS segments.
|
|
* - The ratio of segments for opposite edges doesn't exceed 1.5.
|
|
* This reduces over tessellation for some patches. */
|
|
const int min_T_u = min(sub.edge_u0.edge->T, sub.edge_u1.edge->T);
|
|
const int max_T_u = max(sub.edge_u0.edge->T, sub.edge_u1.edge->T);
|
|
const int min_T_v = min(sub.edge_v0.edge->T, sub.edge_v1.edge->T);
|
|
const int max_T_v = max(sub.edge_v0.edge->T, sub.edge_v1.edge->T);
|
|
|
|
bool split_u = sub.edge_u0.edge->T == DSPLIT_NON_UNIFORM ||
|
|
sub.edge_u1.edge->T == DSPLIT_NON_UNIFORM ||
|
|
(min_T_u >= 2 && min_T_v > DSPLIT_MAX_SEGMENTS && max_T_v / min_T_v > 1.5f);
|
|
bool split_v = sub.edge_v0.edge->T == DSPLIT_NON_UNIFORM ||
|
|
sub.edge_v1.edge->T == DSPLIT_NON_UNIFORM ||
|
|
(min_T_v >= 2 && min_T_u > DSPLIT_MAX_SEGMENTS && max_T_u / min_T_u > 1.5f);
|
|
|
|
/* Alternate axis. */
|
|
if (split_u && split_v) {
|
|
split_u = depth % 2;
|
|
}
|
|
|
|
if (!split_u && !split_v) {
|
|
/* Add the unsplit subpatch. */
|
|
alloc_subpatch(std::move(sub));
|
|
return;
|
|
}
|
|
|
|
/* Copy into new subpatches. */
|
|
SubPatch sub_a = sub;
|
|
SubPatch sub_b = sub;
|
|
|
|
/* Pointers to various subpatch elements. */
|
|
SubPatch::Edge *sub_across_0;
|
|
SubPatch::Edge *sub_across_1;
|
|
SubPatch::Edge *sub_a_across_0;
|
|
SubPatch::Edge *sub_a_across_1;
|
|
SubPatch::Edge *sub_b_across_0;
|
|
SubPatch::Edge *sub_b_across_1;
|
|
|
|
SubPatch::Edge *sub_a_split;
|
|
SubPatch::Edge *sub_b_split;
|
|
|
|
float2 *Pa;
|
|
float2 *Pb;
|
|
float2 *Pc;
|
|
float2 *Pd;
|
|
|
|
/* Set pointers based on split axis. */
|
|
if (split_u) {
|
|
/*
|
|
* sub_across_1
|
|
* -------Pa Pc-------
|
|
* | | | |
|
|
* | A | | B |
|
|
* | | | |
|
|
* -------Pb Pd-------
|
|
* sub_across_0
|
|
*/
|
|
sub_across_0 = &sub.edge_u0;
|
|
sub_across_1 = &sub.edge_u1;
|
|
sub_a_across_0 = &sub_a.edge_u0;
|
|
sub_a_across_1 = &sub_a.edge_u1;
|
|
sub_b_across_0 = &sub_b.edge_u0;
|
|
sub_b_across_1 = &sub_b.edge_u1;
|
|
|
|
sub_a_split = &sub_a.edge_v1;
|
|
sub_b_split = &sub_b.edge_v0;
|
|
|
|
Pa = &sub_a.uv11;
|
|
Pb = &sub_a.uv10;
|
|
Pc = &sub_b.uv01;
|
|
Pd = &sub_b.uv00;
|
|
}
|
|
else {
|
|
/*
|
|
* --------------------
|
|
* | A |
|
|
* Pb----------------Pa
|
|
* sub_across_0 sub_across_1
|
|
* Pd----------------Pc
|
|
* | B |
|
|
* --------------------
|
|
*/
|
|
sub_across_0 = &sub.edge_v0;
|
|
sub_across_1 = &sub.edge_v1;
|
|
sub_a_across_0 = &sub_a.edge_v0;
|
|
sub_a_across_1 = &sub_a.edge_v1;
|
|
sub_b_across_0 = &sub_b.edge_v0;
|
|
sub_b_across_1 = &sub_b.edge_v1;
|
|
|
|
sub_a_split = &sub_a.edge_u0;
|
|
sub_b_split = &sub_b.edge_u1;
|
|
|
|
Pa = &sub_a.uv10;
|
|
Pb = &sub_a.uv00;
|
|
Pc = &sub_b.uv11;
|
|
Pd = &sub_b.uv01;
|
|
}
|
|
|
|
/* Allocate new edges and vertices. */
|
|
const float2 P0 = split_edge(sub.patch, sub_across_0, *Pd, *Pb, depth);
|
|
const float2 P1 = split_edge(sub.patch, sub_across_1, *Pa, *Pc, depth);
|
|
|
|
alloc_edge(sub_a_across_0, sub_across_0->start_vert_index(), sub_across_0->mid_vert_index());
|
|
alloc_edge(sub_b_across_0, sub_across_0->mid_vert_index(), sub_across_0->end_vert_index());
|
|
alloc_edge(sub_b_across_1, sub_across_1->start_vert_index(), sub_across_1->mid_vert_index());
|
|
alloc_edge(sub_a_across_1, sub_across_1->mid_vert_index(), sub_across_1->end_vert_index());
|
|
|
|
assert(sub_a_across_0->edge->T != 0);
|
|
assert(sub_b_across_0->edge->T != 0);
|
|
assert(sub_a_across_1->edge->T != 0);
|
|
assert(sub_b_across_1->edge->T != 0);
|
|
|
|
/* Split */
|
|
*Pa = P1;
|
|
*Pb = P0;
|
|
|
|
*Pc = P1;
|
|
*Pd = P0;
|
|
|
|
/* Create new edge */
|
|
alloc_edge(sub_a_split, sub_across_0->mid_vert_index(), sub_across_1->mid_vert_index());
|
|
alloc_edge(sub_b_split, sub_across_1->mid_vert_index(), sub_across_0->mid_vert_index());
|
|
|
|
/* Set T for split edge. */
|
|
int tsplit = T(sub.patch, P0, P1, depth);
|
|
if (depth == -2 && tsplit == 1) {
|
|
tsplit = 2; /* Ensure we can always split at depth -1. */
|
|
}
|
|
assign_edge_factor(sub_a_split->edge, tsplit);
|
|
|
|
/* Recurse */
|
|
split(std::move(sub_a), depth + 1);
|
|
split(std::move(sub_b), depth + 1);
|
|
}
|
|
|
|
void DiagSplit::split_quad(const Mesh::SubdFace &face, const Patch *patch)
|
|
{
|
|
/*
|
|
* edge_u1
|
|
* uv01 ←-------- uv11
|
|
* | ↑
|
|
* edge_v0 | | edge_v1
|
|
* ↓ |
|
|
* uv00 --------→ uv10
|
|
* edge_u0
|
|
*/
|
|
|
|
const int *subd_face_corners = params.mesh->get_subd_face_corners().data();
|
|
const int v00 = subd_face_corners[face.start_corner + 0];
|
|
const int v10 = subd_face_corners[face.start_corner + 1];
|
|
const int v11 = subd_face_corners[face.start_corner + 2];
|
|
const int v01 = subd_face_corners[face.start_corner + 3];
|
|
|
|
SubPatch subpatch(patch);
|
|
alloc_edge(&subpatch.edge_u0, v00, v10);
|
|
alloc_edge(&subpatch.edge_v1, v10, v11);
|
|
alloc_edge(&subpatch.edge_u1, v11, v01);
|
|
alloc_edge(&subpatch.edge_v0, v01, v00);
|
|
|
|
/* Forces a split in both axis for quads, needed to match split of ngons into quads. */
|
|
subpatch.edge_u0.edge->T = DSPLIT_NON_UNIFORM;
|
|
subpatch.edge_v0.edge->T = DSPLIT_NON_UNIFORM;
|
|
subpatch.edge_u1.edge->T = DSPLIT_NON_UNIFORM;
|
|
subpatch.edge_v1.edge->T = DSPLIT_NON_UNIFORM;
|
|
|
|
split(std::move(subpatch), -2);
|
|
}
|
|
|
|
void DiagSplit::split_ngon(const Mesh::SubdFace &face,
|
|
const Patch *patches,
|
|
const size_t patches_byte_stride)
|
|
{
|
|
const int *subd_face_corners = params.mesh->get_subd_face_corners().data();
|
|
const int v11 = alloc_verts(1);
|
|
|
|
for (int corner = 0; corner < face.num_corners; corner++) {
|
|
const Patch *patch = (const Patch *)(((char *)patches) + (corner * patches_byte_stride));
|
|
|
|
/* vprev .
|
|
* . .
|
|
* . edge_u1 .
|
|
* v01 ←------ v11 . . .
|
|
* | ↑
|
|
* edge_v0 | | edge_v1
|
|
* ↓ |
|
|
* v00 ------→ v10 . . vnext
|
|
* edge_u0
|
|
*/
|
|
|
|
/* Setup edges. */
|
|
const int v00 = subd_face_corners[face.start_corner + corner];
|
|
const int vprev = subd_face_corners[face.start_corner +
|
|
mod(corner + face.num_corners - 1, face.num_corners)];
|
|
const int vnext = subd_face_corners[face.start_corner + mod(corner + 1, face.num_corners)];
|
|
|
|
SubPatch::Edge edge_u0;
|
|
SubPatch::Edge edge_v0;
|
|
|
|
alloc_edge(&edge_u0, v00, vnext);
|
|
alloc_edge(&edge_v0, vprev, v00);
|
|
|
|
if (edge_u0.edge->mid_vert_index == -1) {
|
|
edge_u0.edge->mid_vert_index = alloc_verts(1);
|
|
}
|
|
if (edge_v0.edge->mid_vert_index == -1) {
|
|
edge_v0.edge->mid_vert_index = alloc_verts(1);
|
|
}
|
|
|
|
const int v10 = edge_u0.edge->mid_vert_index;
|
|
const int v01 = edge_v0.edge->mid_vert_index;
|
|
|
|
SubPatch subpatch(patch);
|
|
alloc_edge(&subpatch.edge_u0, v00, v10);
|
|
alloc_edge(&subpatch.edge_v1, v10, v11);
|
|
alloc_edge(&subpatch.edge_u1, v11, v01);
|
|
alloc_edge(&subpatch.edge_v0, v01, v00);
|
|
|
|
/* Perform split. */
|
|
split(std::move(subpatch), 0);
|
|
}
|
|
}
|
|
|
|
void DiagSplit::split_patches(const Patch *patches, const size_t patches_byte_stride)
|
|
{
|
|
/* Keep base mesh vertices, create new triangles. */
|
|
num_verts = params.mesh->get_num_subd_base_verts();
|
|
num_triangles = 0;
|
|
|
|
/* Split all faces in the mesh. */
|
|
int patch_index = 0;
|
|
for (int f = 0; f < params.mesh->get_num_subd_faces(); f++) {
|
|
Mesh::SubdFace face = params.mesh->get_subd_face(f);
|
|
const Patch *patch = (const Patch *)(((char *)patches) + (patch_index * patches_byte_stride));
|
|
|
|
if (face.is_quad()) {
|
|
patch_index++;
|
|
split_quad(face, patch);
|
|
}
|
|
else {
|
|
patch_index += face.num_corners;
|
|
split_ngon(face, patch, patches_byte_stride);
|
|
}
|
|
}
|
|
|
|
// TODO: avoid multiple write for shared vertices
|
|
// TODO: avoid multiple write for linear vert attributes
|
|
// TODO: avoid multiple write for smooth vert attributes
|
|
// TODO: support not splitting n-gons if not needed
|
|
// TODO: multithreading
|
|
|
|
/* Dice all patches. */
|
|
QuadDice dice(params);
|
|
dice.reserve(num_verts, num_triangles);
|
|
|
|
for (SubPatch &sub : subpatches) {
|
|
dice.dice(sub);
|
|
}
|
|
|
|
/* Cleanup */
|
|
subpatches.clear();
|
|
edges.clear();
|
|
}
|
|
|
|
CCL_NAMESPACE_END
|