Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: Apache-2.0
set(INC
..
)
set(INC_SYS
)
set(SRC
dice.cpp
interpolation.cpp
osd.cpp
patch.cpp
split.cpp
)
set(SRC_HEADERS
dice.h
interpolation.h
osd.h
patch.h
split.h
subpatch.h
)
set(LIB
PUBLIC cycles_scene
PRIVATE bf::dependencies::openimageio
PUBLIC bf::dependencies::optional::opensubdiv
)
include_directories(${INC})
include_directories(SYSTEM ${INC_SYS})
cycles_add_library(cycles_subd "${LIB}" ${SRC} ${SRC_HEADERS})

View File

@@ -0,0 +1,698 @@
/* 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/interpolation.h"
#include "subd/patch.h"
#include "subd/split.h"
#include "util/tbb.h"
CCL_NAMESPACE_BEGIN
EdgeDice::EdgeDice(const SubdParams &params_,
const int num_verts,
const int num_triangles,
SubdAttributeInterpolation &interpolation)
: params(params_), interpolation(interpolation)
{
Mesh *mesh = params.mesh;
mesh->num_subd_added_verts = num_verts - mesh->num_verts();
mesh->resize_mesh(num_verts, num_triangles);
mesh->attributes.add(ATTR_STD_VERTEX_NORMAL);
interpolation.setup();
/* Get pointers after interpolation.setup() since it may reallocate
* attribute buffers when setting motion steps. */
Attribute *attr_vN = mesh->attributes.find(ATTR_STD_VERTEX_NORMAL);
mesh_triangles = mesh->triangles.data();
mesh_shader = mesh->shader.data();
mesh_smooth = mesh->smooth.data();
mesh_P = mesh->get_position_for_write();
mesh_N = attr_vN->data_for_write<packed_normal>();
if (params.ptex) {
Attribute *attr_ptex_face_id = params.mesh->attributes.add(ATTR_STD_PTEX_FACE_ID);
Attribute *attr_ptex_uv = params.mesh->attributes.add(ATTR_STD_PTEX_UV);
mesh_ptex_face_id = attr_ptex_face_id->data_for_write<float>();
mesh_ptex_uv = attr_ptex_uv->data_for_write<float2>();
}
}
float3 EdgeDice::eval_projected(const SubPatch &sub, const float2 uv)
{
float3 P;
sub.patch->eval(&P, nullptr, nullptr, nullptr, uv.x, uv.y);
if (params.camera) {
P = transform_perspective(&params.camera->worldtoraster, P);
}
return P;
}
float EdgeDice::quad_area(const float3 &a, const float3 &b, const float3 &c, const float3 &d)
{
return triangle_area(a, b, d) + triangle_area(a, d, c);
}
float EdgeDice::scale_factor(const SubPatch &sub, const int Mu, const int Mv)
{
/* estimate area as 4x largest of 4 quads */
float3 P[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
P[i][j] = eval_projected(sub, sub.map_uv(make_float2(i * 0.5f, j * 0.5f)));
}
}
const float A1 = quad_area(P[0][0], P[1][0], P[0][1], P[1][1]);
const float A2 = quad_area(P[1][0], P[2][0], P[1][1], P[2][1]);
const float A3 = quad_area(P[0][1], P[1][1], P[0][2], P[1][2]);
const float A4 = quad_area(P[1][1], P[2][1], P[1][2], P[2][2]);
const float Apatch = max(A1, max(A2, max(A3, A4))) * 4.0f;
/* solve for scaling factor */
const float Atri = params.dicing_rate * params.dicing_rate * 0.5f;
const float Ntris = Apatch / Atri;
// XXX does the -sqrt solution matter
// XXX max(D, 0.0) is highly suspicious, need to test cases
// where D goes negative
const float N = 0.5f * (Ntris - (sub.edges[0].edge->T + sub.edges[2].edge->T +
sub.edges[3].edge->T + sub.edges[1].edge->T));
const float D = (4.0f * N * Mu * Mv) + ((Mu + Mv) * (Mu + Mv));
const float S = (Mu + Mv + sqrtf(max(D, 0.0f))) / (2 * Mu * Mv);
return S;
}
void EdgeDice::set_vertex(const SubPatch &sub, const int index, const float2 uv)
{
assert(index < params.mesh->num_verts());
float3 P;
float3 N;
sub.patch->eval(&P, nullptr, nullptr, &N, uv.x, uv.y);
mesh_P[index] = P;
mesh_N[index] = packed_normal(N);
for (const SubdAttribute &attr : interpolation.vertex_attributes) {
attr.interp(sub.patch->patch_index, sub.face_index, sub.corner, &index, &uv, 1);
}
}
void EdgeDice::set_triangle(const SubPatch &sub,
const int triangle_index,
const int v0,
const int v1,
const int v2,
const float2 uv0,
const float2 uv1,
const float2 uv2)
{
assert(triangle_index * 3 < params.mesh->triangles.size());
const Patch *patch = sub.patch;
mesh_triangles[triangle_index * 3 + 0] = v0;
mesh_triangles[triangle_index * 3 + 1] = v1;
mesh_triangles[triangle_index * 3 + 2] = v2;
mesh_shader[triangle_index] = patch->shader;
mesh_smooth[triangle_index] = patch->smooth;
if (mesh_ptex_face_id) {
mesh_ptex_face_id[triangle_index] = patch->patch_index;
}
if (mesh_ptex_uv) {
mesh_ptex_uv[triangle_index * 3 + 0] = uv0;
mesh_ptex_uv[triangle_index * 3 + 0] = uv1;
mesh_ptex_uv[triangle_index * 3 + 0] = uv2;
}
/* TODO: batch together multiple triangles. */
float2 uv[3] = {uv0, uv1, uv2};
for (const SubdAttribute &attr : interpolation.triangle_attributes) {
attr.interp(sub.patch->patch_index, sub.face_index, sub.corner, &triangle_index, uv, 1);
}
}
void EdgeDice::add_grid_triangles_and_stitch(const SubPatch &sub, const int Mu, const int Mv)
{
const float du = 1.0f / (float)Mu;
const float dv = 1.0f / (float)Mv;
const int grid_vertex_offset = sub.inner_grid_vert_offset;
int triangle_index = sub.triangles_offset;
/* Create inner grid. */
for (int j = 1; j < Mv; j++) {
for (int i = 1; i < Mu; i++) {
const float u = i * du;
const float v = j * dv;
const int center_i = grid_vertex_offset + (i - 1) + (j - 1) * (Mu - 1);
set_vertex(sub, center_i, sub.map_uv(make_float2(u, v)));
if (i < Mu - 1 && j < Mv - 1) {
const int i1 = grid_vertex_offset + (i - 1) + (j - 1) * (Mu - 1);
const int i2 = grid_vertex_offset + i + (j - 1) * (Mu - 1);
const int i3 = grid_vertex_offset + i + j * (Mu - 1);
const int i4 = grid_vertex_offset + (i - 1) + j * (Mu - 1);
const float2 uv1 = sub.map_uv(make_float2(u, v));
const float2 uv2 = sub.map_uv(make_float2(u + du, v));
const float2 uv3 = sub.map_uv(make_float2(u + du, v + dv));
const float2 uv4 = sub.map_uv(make_float2(u, v + dv));
set_triangle(sub, triangle_index++, i1, i2, i3, uv1, uv2, uv3);
set_triangle(sub, triangle_index++, i1, i3, i4, uv1, uv3, uv4);
}
}
}
/* Stitch inner grid to edges. */
for (int edge = 0; edge < 4; edge++) {
const int outer_T = sub.edges[edge].edge->T;
const int inner_T = ((edge % 2) == 0) ? Mu - 2 : Mv - 2;
float2 inner_uv, outer_uv, inner_uv_step, outer_uv_step;
switch (edge) {
case 0:
inner_uv = make_float2(du, dv);
outer_uv = make_float2(0.0f, 0.0f);
inner_uv_step = make_float2(du, 0.0f);
outer_uv_step = make_float2(1.0f / (float)outer_T, 0.0f);
break;
case 1:
inner_uv = make_float2(1.0f - du, dv);
outer_uv = make_float2(1.0f, 0.0f);
inner_uv_step = make_float2(0.0f, dv);
outer_uv_step = make_float2(0.0f, 1.0f / (float)outer_T);
break;
case 2:
inner_uv = make_float2(1.0f - du, 1.0f - dv);
outer_uv = make_float2(1.0f, 1.0f);
inner_uv_step = make_float2(-du, 0.0f);
outer_uv_step = make_float2(-1.0f / (float)outer_T, 0.0f);
break;
case 3:
default:
inner_uv = make_float2(du, 1.0f - dv);
outer_uv = make_float2(0.0f, 1.0f);
inner_uv_step = make_float2(0.0f, -dv);
outer_uv_step = make_float2(0.0f, -1.0f / (float)outer_T);
break;
}
/* Stitch together two arrays of verts with triangles. At each step, we compare using
* the next verts on both sides, to find the split direction with the smallest
* diagonal, and use that in order to keep the triangle shape reasonable. */
for (size_t i = 0, j = 0; i < inner_T || j < outer_T;) {
const int v0 = sub.get_vert_along_grid_edge(edge, i);
const int v1 = sub.get_vert_along_edge(edge, j);
int v2;
const float2 uv0 = sub.map_uv(inner_uv);
const float2 uv1 = sub.map_uv(outer_uv);
float2 uv2;
if (j == outer_T) {
v2 = sub.get_vert_along_grid_edge(edge, ++i);
inner_uv += inner_uv_step;
uv2 = sub.map_uv(inner_uv);
}
else if (i == inner_T) {
v2 = sub.get_vert_along_edge(edge, ++j);
outer_uv += outer_uv_step;
uv2 = sub.map_uv(outer_uv);
}
else {
/* Length of diagonals. */
const int v2_a = sub.get_vert_along_edge(edge, j + 1);
const int v2_b = sub.get_vert_along_grid_edge(edge, i + 1);
const float len_a = len_squared(mesh_P[v0] - mesh_P[v2_a]);
const float len_b = len_squared(mesh_P[v1] - mesh_P[v2_b]);
/* Use smallest diagonal. */
if (len_a < len_b) {
v2 = v2_a;
outer_uv += outer_uv_step;
uv2 = sub.map_uv(outer_uv);
j++;
}
else {
v2 = v2_b;
inner_uv += inner_uv_step;
uv2 = sub.map_uv(inner_uv);
i++;
}
}
set_triangle(sub, triangle_index++, v0, v1, v2, uv0, uv1, uv2);
}
}
}
void EdgeDice::add_triangle_strip(const SubPatch &sub, const int left_edge, const int right_edge)
{
/* Stitch triangles from side to side, edge in the other direction has T = 1. */
const int left_T = sub.edges[left_edge].edge->T;
const int right_T = sub.edges[right_edge].edge->T;
float2 left_uv, right_uv, left_uv_step, right_uv_step;
if (right_edge == 0) {
left_uv = make_float2(0.0f, 1.0f);
right_uv = make_float2(0.0f, 0.0f);
left_uv_step = make_float2(1.0f / (float)left_T, 0.0f);
right_uv_step = make_float2(1.0f / (float)right_T, 0.0f);
}
else {
left_uv = make_float2(0.0f, 0.0f);
right_uv = make_float2(1.0f, 0.0f);
left_uv_step = make_float2(0.0f, 1.0f / (float)left_T);
right_uv_step = make_float2(0.0f, 1.0f / (float)right_T);
}
/* Stitch together two arrays of verts with triangles. at each step, we compare using the next
* verts on both sides, to find the split direction with the smallest diagonal, and use that
* in order to keep the triangle shape reasonable. */
int triangle_index = sub.triangles_offset;
for (size_t i = 0, j = 0; i < left_T || j < right_T;) {
const int v0 = sub.get_vert_along_edge_reverse(left_edge, i);
const int v1 = sub.get_vert_along_edge(right_edge, j);
int v2;
const float2 uv0 = sub.map_uv(left_uv);
const float2 uv1 = sub.map_uv(right_uv);
float2 uv2;
if (j == right_T) {
v2 = sub.get_vert_along_edge_reverse(left_edge, ++i);
left_uv += left_uv_step;
uv2 = sub.map_uv(left_uv);
}
else if (i == left_T) {
v2 = sub.get_vert_along_edge(right_edge, ++j);
right_uv += right_uv_step;
uv2 = sub.map_uv(right_uv);
}
else {
/* Length of diagonals. */
const int v2_a = sub.get_vert_along_edge(right_edge, j + 1);
const int v2_b = sub.get_vert_along_edge_reverse(left_edge, i + 1);
const float len_a = len_squared(mesh_P[v0] - mesh_P[v2_a]);
const float len_b = len_squared(mesh_P[v1] - mesh_P[v2_b]);
/* Use smallest diagonal. */
if (len_a < len_b) {
v2 = v2_a;
right_uv += right_uv_step;
uv2 = sub.map_uv(right_uv);
j++;
}
else {
v2 = v2_b;
left_uv += left_uv_step;
uv2 = sub.map_uv(left_uv);
i++;
}
}
set_triangle(sub, triangle_index++, v0, v1, v2, uv0, uv1, uv2);
}
}
void EdgeDice::quad_set_sides(const SubPatch &sub)
{
for (int edge = 0; edge < 4; edge++) {
const int t = sub.edges[edge].edge->T;
const int i_start = (sub.edges[edge].own_vertex) ? 0 : 1;
const int i_end = (sub.edges[edge].own_edge) ? t : 1;
/* set verts on the edge of the patch */
for (int i = i_start; i < i_end; i++) {
const float f = i / (float)t;
float2 uv;
switch (edge) {
case 0:
uv = make_float2(f, 0.0f);
break;
case 1:
uv = make_float2(1.0f, f);
break;
case 2:
uv = make_float2(1.0f - f, 1.0f);
break;
case 3:
default:
uv = make_float2(0.0f, 1.0f - f);
break;
}
const int vert_index = sub.get_vert_along_edge(edge, i);
set_vertex(sub, vert_index, sub.map_uv(uv));
}
}
}
void EdgeDice::quad_dice(const SubPatch &sub)
{
/* Compute inner grid size with scale factor. */
const int Mu = max(sub.edges[0].edge->T, sub.edges[2].edge->T);
const int Mv = max(sub.edges[3].edge->T, sub.edges[1].edge->T);
if (Mv == 1) {
/* No inner grid, stitch triangles from side to side. */
add_triangle_strip(sub, 2, 0);
}
else if (Mu == 1) {
/* No inner grid, stitch triangles from side to side. */
add_triangle_strip(sub, 3, 1);
}
else {
#if 0
/* Doesn't work very well, especially at grazing angles. */
const float S = scale_factor(sub, ef, Mu, Mv);
const int grid_Mu = max((int)ceilf(S * Mu), 1); // XXX handle 0 & 1?
const int grid_Mv = max((int)ceilf(S * Mv), 1); // XXX handle 0 & 1?
add_grid_triangles_and_stitch(sub, grid_Mu, grid_Mv);
#else
add_grid_triangles_and_stitch(sub, Mu, Mv);
#endif
}
}
void EdgeDice::tri_set_sides(const SubPatch &sub)
{
for (int edge = 0; edge < 3; edge++) {
const int t = sub.edges[edge].edge->T;
const int i_start = (sub.edges[edge].own_vertex) ? 0 : 1;
const int i_end = (sub.edges[edge].own_edge) ? t : 1;
/* set verts on the edge of the patch */
for (int i = i_start; i < i_end; i++) {
const float f = i / (float)t;
float2 uv;
switch (edge) {
case 0:
uv = make_float2(f, 0.0f);
break;
case 1:
uv = make_float2(1.0f - f, f);
break;
case 2:
default:
uv = make_float2(0.0f, 1.0f - f);
break;
}
const int vert_index = sub.get_vert_along_edge(edge, i);
set_vertex(sub, vert_index, sub.map_uv(uv));
}
}
}
void EdgeDice::tri_dice(const SubPatch &sub)
{
const int M = max(max(sub.edges[0].edge->T, sub.edges[1].edge->T), sub.edges[2].edge->T);
const float d = 1.0f / (float)(M + 1);
int triangle_index = sub.triangles_offset;
if (M == 1) {
/* Single triangle. */
set_triangle(sub,
triangle_index++,
sub.edges[0].start_vert_index(),
sub.edges[1].start_vert_index(),
sub.edges[2].start_vert_index(),
sub.map_uv(make_float2(0.0f, 0.0f)),
sub.map_uv(make_float2(1.0f, 0.0f)),
sub.map_uv(make_float2(0.0f, 1.0f)));
assert(triangle_index == sub.triangles_offset + sub.calc_num_triangles());
return;
}
if (M == 2) {
/* Edges have 2 segments or less. */
int num_split = 0;
int split_0 = -1;
for (int i = 0; i < 3; i++) {
if (sub.edges[i].edge->T == 2) {
num_split++;
if (split_0 == -1) {
split_0 = i;
}
}
}
/* When two edges have 2 segments, we assume split_0 is the first of two consecutive edges. */
if (split_0 == 0 && sub.edges[2].edge->T == 2) {
split_0 = 2;
}
const int split_1 = (split_0 + 1) % 3;
const int split_2 = (split_0 + 2) % 3;
const int v[3] = {sub.edges[0].start_vert_index(),
sub.edges[1].start_vert_index(),
sub.edges[2].start_vert_index()};
const int mid_v[3] = {sub.get_vert_along_edge(0, 1),
sub.get_vert_along_edge(1, 1),
sub.get_vert_along_edge(2, 1)};
const float2 uv[3] = {sub.map_uv(make_float2(0.0f, 0.0f)),
sub.map_uv(make_float2(1.0f, 0.0f)),
sub.map_uv(make_float2(0.0f, 1.0f))};
const float2 mid_uv[3] = {sub.map_uv(make_float2(0.5f, 0.0f)),
sub.map_uv(make_float2(0.5f, 0.5f)),
sub.map_uv(make_float2(0.0f, 0.5f))};
if (num_split == 3) {
/* All edges have two segments
* /\
* /--\
* / \/ \
* ------- */
set_triangle(sub, triangle_index++, v[0], mid_v[0], mid_v[2], uv[0], mid_uv[0], mid_uv[2]);
set_triangle(sub, triangle_index++, v[1], mid_v[1], mid_v[0], uv[1], mid_uv[1], mid_uv[0]);
set_triangle(sub, triangle_index++, v[2], mid_v[2], mid_v[1], uv[2], mid_uv[2], mid_uv[1]);
set_triangle(
sub, triangle_index++, mid_v[0], mid_v[1], mid_v[2], mid_uv[0], mid_uv[1], mid_uv[2]);
}
else {
/* One edge has two segments.
* / \
* / | \
* / | \
* ------- */
set_triangle(sub,
triangle_index++,
v[split_0],
mid_v[split_0],
v[split_2],
uv[split_0],
mid_uv[split_0],
uv[split_2]);
if (num_split == 1) {
set_triangle(sub,
triangle_index++,
mid_v[split_0],
v[split_1],
v[split_2],
mid_uv[split_0],
uv[split_1],
uv[split_2]);
}
else {
/* Two edges have two segments.
* /|\
* / | \
* / |/ \
* ------- */
set_triangle(sub,
triangle_index++,
mid_v[split_0],
v[split_1],
mid_v[split_1],
mid_uv[split_0],
uv[split_1],
mid_uv[split_1]);
set_triangle(sub,
triangle_index++,
mid_v[split_0],
mid_v[split_1],
v[split_2],
mid_uv[split_0],
mid_uv[split_1],
uv[split_2]);
}
}
assert(triangle_index == sub.triangles_offset + sub.calc_num_triangles());
return;
}
const int inner_M = M - 2;
for (int j = 0; j < inner_M; j++) {
for (int i = 0; i < j + 1; i++) {
const int i_next = i + 1;
const int j_next = j + 1;
const float2 inner_uv = make_float2(d, d);
const int v0 = sub.get_inner_grid_vert_triangle(i, j);
const int v1 = sub.get_inner_grid_vert_triangle(i_next, j_next);
const int v2 = sub.get_inner_grid_vert_triangle(i, j_next);
const float2 uv0 = sub.map_uv(inner_uv + make_float2(i, j - i) * d);
const float2 uv1 = sub.map_uv(inner_uv + make_float2(i_next, j - i) * d);
const float2 uv2 = sub.map_uv(inner_uv + make_float2(i, j_next - i) * d);
set_vertex(sub, v0, uv0);
if (j == inner_M - 1) {
set_vertex(sub, v1, uv1);
set_vertex(sub, v2, uv2);
}
set_triangle(sub, triangle_index++, v0, v1, v2, uv0, uv1, uv2);
if (i < j) {
const int v3 = sub.get_inner_grid_vert_triangle(i_next, j);
const float2 uv3 = sub.map_uv(inner_uv + make_float2(i_next, j - i_next) * d);
set_vertex(sub, v3, uv3);
set_triangle(sub, triangle_index++, v0, v3, v1, uv0, uv3, uv1);
}
}
}
assert(triangle_index == sub.triangles_offset + inner_M * inner_M);
/* Stitch inner grid to edges. */
for (int edge = 0; edge < 3; edge++) {
const int outer_T = sub.edges[edge].edge->T;
const int inner_T = inner_M;
float2 inner_uv, outer_uv, inner_uv_step, outer_uv_step;
switch (edge) {
case 0:
inner_uv = make_float2(d, d);
outer_uv = make_float2(0.0f, 0.0f);
inner_uv_step = make_float2(d, 0.0f);
outer_uv_step = make_float2(1.0f / (float)outer_T, 0.0f);
break;
case 1:
inner_uv = make_float2(1.0f - 2.0f * d, d);
outer_uv = make_float2(1.0f, 0.0f);
inner_uv_step = make_float2(-d, d);
outer_uv_step = make_float2(-1.0f / (float)outer_T, 1.0f / (float)outer_T);
break;
case 2:
default:
inner_uv = make_float2(d, 1.0f - 2.0f * d);
outer_uv = make_float2(0.0f, 1.0f);
inner_uv_step = make_float2(0.0f, -d);
outer_uv_step = make_float2(0.0f, -1.0f / (float)outer_T);
break;
}
/* Stitch together two arrays of verts with triangles. At each step, we compare using
* the next verts on both sides, to find the split direction with the smallest
* diagonal, and use that in order to keep the triangle shape reasonable. */
for (size_t i = 0, j = 0; i < inner_T || j < outer_T;) {
const int v0 = sub.get_vert_along_grid_edge(edge, i);
const int v1 = sub.get_vert_along_edge(edge, j);
int v2;
const float2 uv0 = sub.map_uv(inner_uv);
const float2 uv1 = sub.map_uv(outer_uv);
float2 uv2;
if (j == outer_T) {
v2 = sub.get_vert_along_grid_edge(edge, ++i);
inner_uv += inner_uv_step;
uv2 = sub.map_uv(inner_uv);
}
else if (i == inner_T) {
v2 = sub.get_vert_along_edge(edge, ++j);
outer_uv += outer_uv_step;
uv2 = sub.map_uv(outer_uv);
}
else {
/* Length of diagonals. */
const int v2_a = sub.get_vert_along_edge(edge, j + 1);
const int v2_b = sub.get_vert_along_grid_edge(edge, i + 1);
const float len_a = len_squared(mesh_P[v0] - mesh_P[v2_a]);
const float len_b = len_squared(mesh_P[v1] - mesh_P[v2_b]);
/* Use smallest diagonal. */
if (len_a < len_b) {
v2 = v2_a;
outer_uv += outer_uv_step;
uv2 = sub.map_uv(outer_uv);
j++;
}
else {
v2 = v2_b;
inner_uv += inner_uv_step;
uv2 = sub.map_uv(inner_uv);
i++;
}
}
set_triangle(sub, triangle_index++, v0, v1, v2, uv0, uv1, uv2);
}
}
assert(triangle_index == sub.triangles_offset + sub.calc_num_triangles());
}
void EdgeDice::dice(const DiagSplit &split)
{
const size_t num_subpatches = split.get_num_subpatches();
/* Vertex coordinates for sides. Needs to be done first because tessellation depends
* on these coordinates and they are unique assigned to a subpatch for determinism. */
parallel_for(blocked_range<size_t>(0, num_subpatches, 8), [&](const blocked_range<size_t> &r) {
for (size_t i = r.begin(); i != r.end(); i++) {
const SubPatch &subpatch = split.get_subpatch(i);
if (subpatch.shape == SubPatch::TRIANGLE) {
tri_set_sides(subpatch);
}
else {
quad_set_sides(subpatch);
}
}
});
/* Inner vertex coordinates and triangles. */
parallel_for(blocked_range<size_t>(0, num_subpatches, 8), [&](const blocked_range<size_t> &r) {
for (size_t i = r.begin(); i != r.end(); i++) {
const SubPatch &subpatch = split.get_subpatch(i);
if (subpatch.shape == SubPatch::TRIANGLE) {
tri_dice(subpatch);
}
else {
quad_dice(subpatch);
}
}
});
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,84 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
/* DX11 like EdgeDice implementation, with different tessellation factors for
* each edge for watertight tessellation, with subpatch remapping to work with
* DiagSplit. For more algorithm details, see the DiagSplit paper or the
* ARB_tessellation_shader OpenGL extension, Section 2.X.2. */
#include "util/transform.h"
#include "util/types.h"
#include "subd/subpatch.h"
CCL_NAMESPACE_BEGIN
class Camera;
class Mesh;
class Patch;
class SubdAttributeInterpolation;
class DiagSplit;
struct SubdParams {
Mesh *mesh = nullptr;
bool ptex = false;
int test_steps = 3;
int split_threshold = 1;
float dicing_rate = 1.0f;
int max_level = 12;
Camera *camera = nullptr;
Transform objecttoworld = transform_identity();
SubdParams(Mesh *mesh_, bool ptex_ = false) : mesh(mesh_), ptex(ptex_) {}
};
class EdgeDice {
public:
SubdParams params;
SubdAttributeInterpolation &interpolation;
int *mesh_triangles = nullptr;
int *mesh_shader = nullptr;
bool *mesh_smooth = nullptr;
packed_float3 *mesh_P = nullptr;
packed_normal *mesh_N = nullptr;
float *mesh_ptex_face_id = nullptr;
float2 *mesh_ptex_uv = nullptr;
explicit EdgeDice(const SubdParams &params,
const int num_verts,
const int num_triangles,
SubdAttributeInterpolation &interpolation);
void dice(const DiagSplit &split);
protected:
void tri_dice(const SubPatch &sub);
void quad_dice(const SubPatch &sub);
void set_vertex(const SubPatch &sub, const int index, const float2 uv);
void set_triangle(const SubPatch &sub,
const int triangle_index,
const int v0,
const int v1,
const int v2,
const float2 uv0,
const float2 uv1,
const float2 uv2);
void add_grid_triangles_and_stitch(const SubPatch &sub, const int Mu, const int Mv);
void add_triangle_strip(const SubPatch &sub, const int left_edge, const int right_edge);
float3 eval_projected(const SubPatch &sub, const float2 uv);
void tri_set_sides(const SubPatch &sub);
void quad_set_sides(const SubPatch &sub);
float quad_area(const float3 &a, const float3 &b, const float3 &c, const float3 &d);
float scale_factor(const SubPatch &sub, const int Mu, const int Mv);
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,637 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "subd/interpolation.h"
#include "kernel/types.h"
#include "scene/attribute.h"
#include "scene/mesh.h"
#include "util/color.h"
CCL_NAMESPACE_BEGIN
/* Classes for interpolation to use float math for byte value, for precision. */
template<typename T> struct SubdFloat {
using Type = T;
using AccumType = T;
static Type read(const Type &value)
{
return value;
}
static Type output(const Type &value)
{
return value;
}
};
struct SubdByte {
using Type = uchar4;
using AccumType = float4;
static AccumType read(const Type &value)
{
return color_uchar4_to_float4(value);
}
static Type output(const AccumType &value)
{
return color_float4_to_uchar4(value);
}
};
struct SubdNormal {
using Type = packed_normal;
using AccumType = float3;
static AccumType read(const Type &value)
{
return value.decode();
}
static Type output(const AccumType &value)
{
return packed_normal(value);
}
};
struct SubdPackedFloat3 {
using Type = packed_float3;
using AccumType = float3;
static AccumType read(const Type &value)
{
return float3(value);
}
static Type output(const AccumType &value)
{
return packed_float3(value);
}
};
#ifdef WITH_OPENSUBDIV
SubdAttributeInterpolation::SubdAttributeInterpolation(Mesh &mesh,
OsdMesh &osd_mesh,
OsdData &osd_data)
#else
SubdAttributeInterpolation::SubdAttributeInterpolation(Mesh &mesh)
#endif
: mesh(mesh)
#ifdef WITH_OPENSUBDIV
,
osd_mesh(osd_mesh),
osd_data(osd_data)
#endif
{
}
void SubdAttributeInterpolation::setup()
{
if (mesh.get_num_subd_faces() == 0) {
return;
}
for (const Attribute &subd_attr : mesh.subd_attributes.attributes) {
if (!support_interp_attribute(subd_attr)) {
continue;
}
Attribute &mesh_attr = mesh.attributes.copy(subd_attr);
setup_attribute(subd_attr, mesh_attr);
}
}
bool SubdAttributeInterpolation::support_interp_attribute(const Attribute &attr) const
{
switch (attr.std) {
/* Smooth normals are computed from derivatives, for linear interpolate. */
case ATTR_STD_VERTEX_NORMAL:
case ATTR_STD_CORNER_NORMAL:
if (mesh.get_subdivision_type() == Mesh::SUBDIVISION_CATMULL_CLARK) {
return false;
}
break;
/* Center step position is computed by patch evaluation during dicing.
* Only interpolate position when there are motion steps. */
case ATTR_STD_POSITION:
if (!attr.has_motion()) {
return false;
}
break;
/* PTex coordinates will be computed by subdivision. */
case ATTR_STD_PTEX_FACE_ID:
case ATTR_STD_PTEX_UV:
return false;
default:
break;
}
/* Skip element types that should not exist for subd attributes anyway. */
switch (attr.element) {
case ATTR_ELEMENT_OBJECT:
case ATTR_ELEMENT_MESH:
case ATTR_ELEMENT_VERTEX:
case ATTR_ELEMENT_VERTEX_NORMAL:
case ATTR_ELEMENT_CORNER:
case ATTR_ELEMENT_CORNER_BYTE:
case ATTR_ELEMENT_CORNER_NORMAL:
case ATTR_ELEMENT_FACE:
break;
default:
return false;
}
return true;
}
void SubdAttributeInterpolation::setup_attribute(const Attribute &subd_attr, Attribute &mesh_attr)
{
if (subd_attr.element & ATTR_ELEMENT_IS_BYTE) {
setup_attribute_type<SubdByte>(subd_attr, mesh_attr);
}
else if (subd_attr.element & ATTR_ELEMENT_IS_NORMAL) {
setup_attribute_type<SubdNormal>(subd_attr, mesh_attr);
}
else if (Attribute::same_storage(subd_attr.type, TypeFloat)) {
setup_attribute_type<SubdFloat<float>>(subd_attr, mesh_attr);
}
else if (Attribute::same_storage(subd_attr.type, TypeFloat2)) {
setup_attribute_type<SubdFloat<float2>>(subd_attr, mesh_attr);
}
else if (Attribute::same_storage(subd_attr.type, TypeVector)) {
setup_attribute_type<SubdPackedFloat3>(subd_attr, mesh_attr);
}
else if (Attribute::same_storage(subd_attr.type, TypeFloat4) ||
Attribute::same_storage(subd_attr.type, TypeRGBA))
{
setup_attribute_type<SubdFloat<float4>>(subd_attr, mesh_attr);
}
}
template<typename T>
void SubdAttributeInterpolation::setup_attribute_vertex_linear(const Attribute &subd_attr,
Attribute &mesh_attr,
const int motion_step)
{
SubdAttribute attr;
/* motion_step -1 selects the center step, other select motion steps. */
const int attr_step = motion_step + 1;
assert(attr_step == 0 || subd_attr.has_motion());
const typename T::Type *subd_data = subd_attr.data<typename T::Type>(attr_step);
typename T::Type *mesh_data = mesh_attr.data_for_write<typename T::Type>(attr_step);
assert(mesh_data != nullptr);
attr.interp = [this, subd_data, mesh_data](const int /*patch_index*/,
const int face_index,
const int corner,
const int *vert_index,
const float2 *vert_uv,
const int vert_num) {
/* Interpolate values at vertices. */
const int *subd_face_corners = mesh.get_subd_face_corners().data();
Mesh::SubdFace face = mesh.get_subd_face(face_index);
if (face.is_quad()) {
/* Simple case for quads. */
const typename T::AccumType value0 = T::read(
subd_data[subd_face_corners[face.start_corner + 0]]);
const typename T::AccumType value1 = T::read(
subd_data[subd_face_corners[face.start_corner + 1]]);
const typename T::AccumType value2 = T::read(
subd_data[subd_face_corners[face.start_corner + 2]]);
const typename T::AccumType value3 = T::read(
subd_data[subd_face_corners[face.start_corner + 3]]);
for (int i = 0; i < vert_num; i++) {
const float2 uv = vert_uv[i];
const typename T::AccumType value = interp(
interp(value0, value1, uv.x), interp(value3, value2, uv.x), uv.y);
mesh_data[vert_index[i]] = T::output(value);
}
}
else {
/* Other n-gons are split into n quads. */
/* Compute value at center of polygon. */
typename T::AccumType value_center = T::read(
subd_data[subd_face_corners[face.start_corner]]);
for (int j = 1; j < face.num_corners; j++) {
value_center += T::read(subd_data[subd_face_corners[face.start_corner + j]]);
}
value_center /= (float)face.num_corners;
/* Compute value at corner at adjacent vertices. */
const typename T::AccumType value_corner = T::read(
subd_data[subd_face_corners[face.start_corner + corner]]);
const typename T::AccumType value_prev =
0.5f * (value_corner +
T::read(subd_data[subd_face_corners[face.start_corner +
mod(corner - 1, face.num_corners)]]));
const typename T::AccumType value_next =
0.5f * (value_corner +
T::read(subd_data[subd_face_corners[face.start_corner +
mod(corner + 1, face.num_corners)]]));
for (int i = 0; i < vert_num; i++) {
const float2 uv = vert_uv[i];
/* Interpolate. */
const typename T::AccumType value = interp(
interp(value_corner, value_next, uv.x), interp(value_prev, value_center, uv.x), uv.y);
mesh_data[vert_index[i]] = T::output(value);
}
}
};
vertex_attributes.push_back(std::move(attr));
}
#ifdef WITH_OPENSUBDIV
template<typename T>
void SubdAttributeInterpolation::setup_attribute_vertex_smooth(const Attribute &subd_attr,
Attribute &mesh_attr,
const int motion_step)
{
SubdAttribute attr;
// TODO: Avoid computing derivative weights when not needed
// TODO: overhead of FindPatch and EvaluateBasis with vertex position
const int num_refiner_verts = osd_data.refiner->GetNumVerticesTotal();
const int num_local_points = osd_data.patch_table->GetNumLocalPoints();
const int num_base_verts = mesh.get_num_subd_base_verts();
/* Refine attribute data to get patch coordinates. */
attr.refined_data.resize((num_refiner_verts + num_local_points) * sizeof(typename T::AccumType));
typename T::AccumType *subd_data = reinterpret_cast<typename T::AccumType *>(
attr.refined_data.data());
const int attr_step = motion_step + 1;
assert(attr_step == 0 || subd_attr.has_motion());
const typename T::Type *base_src = subd_attr.data<typename T::Type>(attr_step);
typename T::AccumType *base_dst = subd_data;
for (int i = 0; i < num_base_verts; i++) {
base_dst[i] = T::read(base_src[i]);
}
Far::PrimvarRefiner primvar_refiner(*osd_data.refiner);
typename T::AccumType *src = subd_data;
for (int i = 0; i < osd_data.refiner->GetMaxLevel(); i++) {
typename T::AccumType *dest = src + osd_data.refiner->GetLevel(i).GetNumVertices();
primvar_refiner.Interpolate(
i + 1, (OsdValue<typename T::AccumType> *)src, (OsdValue<typename T::AccumType> *&)dest);
src = dest;
}
if (num_local_points) {
osd_data.patch_table->ComputeLocalPointValues(
(OsdValue<typename T::AccumType> *)subd_data,
(OsdValue<typename T::AccumType> *)(subd_data + num_refiner_verts));
}
/* Evaluate patches at limit. */
assert(attr_step == 0 || mesh_attr.has_motion());
typename T::Type *mesh_data = mesh_attr.data_for_write<typename T::Type>(attr_step);
assert(mesh_data != nullptr);
/* Compute motion normals alongside positions. */
packed_normal *mesh_normal_data = nullptr;
if constexpr (std::is_same_v<typename T::AccumType, float3>) {
if (motion_step >= 0 && mesh_attr.std == ATTR_STD_POSITION && mesh_attr.has_motion()) {
Attribute *attr_normal = mesh.attributes.find(ATTR_STD_VERTEX_NORMAL);
if (attr_normal) {
attr_normal->add_motion(&mesh);
mesh_normal_data = attr_normal->data_for_write<packed_normal>(attr_step);
}
}
}
attr.interp = [this, subd_data, mesh_data, mesh_normal_data](const int patch_index,
const int /*face_index*/,
const int /*corner*/,
const int *vert_index,
const float2 *vert_uv,
const int vert_num) {
for (int i = 0; i < vert_num; i++) {
/* Compute patch weights. */
const float2 uv = vert_uv[i];
const Far::PatchTable::PatchHandle &handle = *osd_data.patch_map->FindPatch(
patch_index, (double)uv.x, (double)uv.y);
float p_weights[20], du_weights[20], dv_weights[20];
osd_data.patch_table->EvaluateBasis(handle, uv.x, uv.y, p_weights, du_weights, dv_weights);
Far::ConstIndexArray cv = osd_data.patch_table->GetPatchVertices(handle);
/* Compution position. */
typename T::AccumType value = subd_data[cv[0]] * p_weights[0];
for (int k = 1; k < cv.size(); k++) {
value += subd_data[cv[k]] * p_weights[k];
}
mesh_data[vert_index[i]] = T::output(value);
/* Optionally compute normal. */
if (mesh_normal_data) {
if constexpr (std::is_same_v<typename T::AccumType, float3>) {
float3 du = zero_float3();
float3 dv = zero_float3();
for (int k = 0; k < cv.size(); k++) {
const float3 p = subd_data[cv[k]];
du += p * du_weights[k];
dv += p * dv_weights[k];
}
mesh_normal_data[vert_index[i]] = packed_normal(
safe_normalize_fallback(cross(du, dv), make_float3(0.0f, 0.0f, 1.0f)));
}
}
}
};
vertex_attributes.push_back(std::move(attr));
}
#endif
template<typename T>
void SubdAttributeInterpolation::setup_attribute_corner_linear(const Attribute &subd_attr,
Attribute &mesh_attr,
const int motion_step)
{
SubdAttribute attr;
/* Interpolate values at corners. */
const int attr_step = motion_step + 1;
assert(attr_step == 0 || subd_attr.has_motion());
const typename T::Type *subd_data = subd_attr.data<typename T::Type>(attr_step);
typename T::Type *mesh_data = mesh_attr.data_for_write<typename T::Type>(attr_step);
assert(mesh_data != nullptr);
attr.interp = [this, subd_data, mesh_data](const int /*patch_index*/,
const int face_index,
const int corner,
const int *triangle_index,
const float2 *triangle_uv,
const int triangle_num) {
Mesh::SubdFace face = mesh.get_subd_face(face_index);
if (face.is_quad()) {
/* Simple case for quads. */
const typename T::AccumType value0 = T::read(subd_data[face.start_corner + 0]);
const typename T::AccumType value1 = T::read(subd_data[face.start_corner + 1]);
const typename T::AccumType value2 = T::read(subd_data[face.start_corner + 2]);
const typename T::AccumType value3 = T::read(subd_data[face.start_corner + 3]);
for (size_t i = 0; i < triangle_num; i++) {
for (int j = 0; j < 3; j++) {
const float2 uv = triangle_uv[(i * 3) + j];
const typename T::AccumType value = interp(
interp(value0, value1, uv.x), interp(value3, value2, uv.x), uv.y);
mesh_data[triangle_index[i] * 3 + j] = T::output(value);
}
}
}
else {
/* Other n-gons are split into n quads. */
/* Compute value at center of polygon. */
typename T::AccumType value_center = T::read(subd_data[face.start_corner]);
for (int j = 1; j < face.num_corners; j++) {
value_center += T::read(subd_data[face.start_corner + j]);
}
value_center /= (float)face.num_corners;
/* Compute value at corner at adjacent vertices. */
const typename T::AccumType value_corner = T::read(subd_data[face.start_corner + corner]);
const typename T::AccumType value_prev =
0.5f * (value_corner +
T::read(subd_data[face.start_corner + mod(corner - 1, face.num_corners)]));
const typename T::AccumType value_next =
0.5f * (value_corner +
T::read(subd_data[face.start_corner + mod(corner + 1, face.num_corners)]));
for (size_t i = 0; i < triangle_num; i++) {
for (int j = 0; j < 3; j++) {
const float2 uv = triangle_uv[(i * 3) + j];
/* Interpolate. */
const typename T::AccumType value = interp(interp(value_corner, value_next, uv.x),
interp(value_prev, value_center, uv.x),
uv.y);
mesh_data[triangle_index[i] * 3 + j] = T::output(value);
}
}
}
};
triangle_attributes.push_back(std::move(attr));
}
#ifdef WITH_OPENSUBDIV
template<typename T>
void SubdAttributeInterpolation::setup_attribute_corner_smooth(Attribute &mesh_attr,
const int channel,
const vector<char> &merged_values)
{
SubdAttribute attr;
// TODO: Avoid computing derivative weights when not needed
const int num_refiner_fvars = osd_data.refiner->GetNumFVarValuesTotal(channel);
const int num_local_points = osd_data.patch_table->GetNumLocalPointsFaceVarying(channel);
const int num_base_fvars = osd_data.refiner->GetLevel(0).GetNumFVarValues(channel);
/* Refine attribute data to get patch coordinates. */
attr.refined_data.resize((num_refiner_fvars + num_local_points) * sizeof(typename T::AccumType));
typename T::AccumType *refined_data = reinterpret_cast<typename T::AccumType *>(
attr.refined_data.data());
const typename T::Type *base_src = reinterpret_cast<const typename T::Type *>(
merged_values.data());
typename T::AccumType *base_dst = refined_data;
for (int i = 0; i < num_base_fvars; i++) {
base_dst[i] = T::read(base_src[i]);
}
Far::PrimvarRefiner primvar_refiner(*osd_data.refiner);
typename T::AccumType *src = refined_data;
for (int i = 0; i < osd_data.refiner->GetMaxLevel(); i++) {
typename T::AccumType *dest = src + osd_data.refiner->GetLevel(i).GetNumFVarValues(channel);
primvar_refiner.InterpolateFaceVarying(i + 1,
(OsdValue<typename T::AccumType> *)src,
(OsdValue<typename T::AccumType> *&)dest,
channel);
src = dest;
}
if (num_local_points) {
osd_data.patch_table->ComputeLocalPointValuesFaceVarying(
(OsdValue<typename T::AccumType> *)refined_data,
(OsdValue<typename T::AccumType> *)(refined_data + num_refiner_fvars),
channel);
}
/* Evaluate patches at limit. */
const typename T::AccumType *subd_data = refined_data;
typename T::Type *mesh_data = reinterpret_cast<typename T::Type *>(mesh_attr.data_for_write());
assert(mesh_data != nullptr);
attr.interp = [this, subd_data, mesh_data, channel](const int patch_index,
const int /*face_index*/,
const int /*corner*/,
const int *triangle_index,
const float2 *triangle_uv,
const int triangle_num) {
for (size_t i = 0; i < triangle_num; i++) {
for (int j = 0; j < 3; j++) {
/* Compute patch weights. */
const float2 uv = triangle_uv[(i * 3) + j];
const Far::PatchTable::PatchHandle &handle = *osd_data.patch_map->FindPatch(
patch_index, (double)uv.x, (double)uv.y);
float p_weights[20], du_weights[20], dv_weights[20];
osd_data.patch_table->EvaluateBasisFaceVarying(handle,
uv.x,
uv.y,
p_weights,
du_weights,
dv_weights,
nullptr,
nullptr,
nullptr,
channel);
Far::ConstIndexArray cv = osd_data.patch_table->GetPatchFVarValues(handle, channel);
/* Compution position. */
typename T::AccumType value = subd_data[cv[0]] * p_weights[0];
for (int k = 1; k < cv.size(); k++) {
value += subd_data[cv[k]] * p_weights[k];
}
mesh_data[triangle_index[i] * 3 + j] = T::output(value);
}
}
};
triangle_attributes.push_back(std::move(attr));
}
#endif
template<typename T>
void SubdAttributeInterpolation::setup_attribute_face(const Attribute &subd_attr,
Attribute &mesh_attr)
{
/* Copy value from face to triangle. */
SubdAttribute attr;
const typename T::Type *subd_data = reinterpret_cast<const typename T::Type *>(subd_attr.data());
typename T::Type *mesh_data = reinterpret_cast<typename T::Type *>(mesh_attr.data_for_write());
assert(mesh_data != nullptr);
attr.interp = [subd_data, mesh_data](const int /*patch_index*/,
const int face_index,
const int /*corner*/,
const int *triangle_index,
const float2 * /*triangle_uv*/,
const int triangle_num) {
for (int i = 0; i < triangle_num; i++) {
mesh_data[triangle_index[i]] = subd_data[face_index];
}
};
triangle_attributes.push_back(std::move(attr));
}
template<typename T>
void SubdAttributeInterpolation::setup_attribute_type(const Attribute &subd_attr,
Attribute &mesh_attr)
{
switch (subd_attr.element) {
case ATTR_ELEMENT_OBJECT:
case ATTR_ELEMENT_MESH: {
/* Uniform attributes don't need interpolation, just copy data. */
assert(mesh_attr.size == subd_attr.size);
memcpy(mesh_attr.data_for_write(), subd_attr.data(), subd_attr.data_sizeof());
break;
}
case ATTR_ELEMENT_VERTEX:
case ATTR_ELEMENT_VERTEX_NORMAL: {
/* Center step. Position center is computed by patch evaluation
* during dicing, so skip it here. */
if (subd_attr.std != ATTR_STD_POSITION) {
#ifdef WITH_OPENSUBDIV
if (mesh.get_subdivision_type() == Mesh::SUBDIVISION_CATMULL_CLARK) {
/* Only smoothly interpolation known position-like attributes. */
switch (subd_attr.std) {
case ATTR_STD_GENERATED:
case ATTR_STD_POSITION_UNDEFORMED:
case ATTR_STD_POSITION_UNDISPLACED:
setup_attribute_vertex_smooth<T>(subd_attr, mesh_attr);
break;
default:
setup_attribute_vertex_linear<T>(subd_attr, mesh_attr);
break;
}
}
else
#endif
{
setup_attribute_vertex_linear<T>(subd_attr, mesh_attr);
}
}
/* Motion steps. */
if (subd_attr.has_motion()) {
for (int step = 0; step < subd_attr.motion.size(); step++) {
#ifdef WITH_OPENSUBDIV
if (mesh.get_subdivision_type() == Mesh::SUBDIVISION_CATMULL_CLARK) {
setup_attribute_vertex_smooth<T>(subd_attr, mesh_attr, step);
}
else
#endif
{
setup_attribute_vertex_linear<T>(subd_attr, mesh_attr, step);
}
}
}
break;
}
case ATTR_ELEMENT_CORNER:
case ATTR_ELEMENT_CORNER_BYTE:
case ATTR_ELEMENT_CORNER_NORMAL: {
#ifdef WITH_OPENSUBDIV
if (osd_mesh.use_smooth_fvar(subd_attr)) {
for (const auto &merged_fvar : osd_mesh.merged_fvars) {
if (&merged_fvar.attr == &subd_attr) {
if constexpr (std::is_same_v<typename T::Type, float2>) {
setup_attribute_corner_smooth<T>(mesh_attr, merged_fvar.channel, merged_fvar.values);
return;
}
}
}
}
#endif
setup_attribute_corner_linear<T>(subd_attr, mesh_attr);
/* Motion steps. */
if (subd_attr.has_motion()) {
for (int step = 0; step < subd_attr.motion.size(); step++) {
setup_attribute_corner_linear<T>(subd_attr, mesh_attr, step);
}
}
break;
}
case ATTR_ELEMENT_FACE: {
setup_attribute_face<T>(subd_attr, mesh_attr);
break;
}
default:
break;
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,80 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "subd/osd.h"
#include "util/types.h"
#include "util/vector.h"
#include <cstddef>
#include <functional>
CCL_NAMESPACE_BEGIN
class Mesh;
class Attribute;
/* Attribute Interpolation. */
struct SubdAttribute {
std::function<void(const int, const int, const int, const int *, const float2 *, const int)>
interp;
vector<char> refined_data;
};
class SubdAttributeInterpolation {
protected:
Mesh &mesh;
#ifdef WITH_OPENSUBDIV
OsdMesh &osd_mesh;
OsdData &osd_data;
#endif
public:
vector<SubdAttribute> vertex_attributes;
vector<SubdAttribute> triangle_attributes;
#ifdef WITH_OPENSUBDIV
SubdAttributeInterpolation(Mesh &mesh, OsdMesh &osd_mesh, OsdData &osd_data);
#else
SubdAttributeInterpolation(Mesh &mesh);
#endif
void setup();
protected:
bool support_interp_attribute(const Attribute &attr) const;
void setup_attribute(const Attribute &subd_attr, Attribute &mesh_attr);
template<typename T>
void setup_attribute_vertex_linear(const Attribute &subd_attr,
Attribute &mesh_attr,
const int motion_step = -1);
template<typename T>
void setup_attribute_corner_linear(const Attribute &subd_attr,
Attribute &mesh_attr,
const int motion_step = -1);
#ifdef WITH_OPENSUBDIV
template<typename T>
void setup_attribute_vertex_smooth(const Attribute &subd_attr,
Attribute &mesh_attr,
const int motion_step = -1);
template<typename T>
void setup_attribute_corner_smooth(Attribute &mesh_attr,
const int channel,
const vector<char> &merged_values);
#endif
template<typename T> void setup_attribute_face(const Attribute &subd_attr, Attribute &mesh_attr);
template<typename T> void setup_attribute_type(const Attribute &subd_attr, Attribute &mesh_attr);
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,406 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_OPENSUBDIV
# include "subd/osd.h"
# include "scene/attribute.h"
# include "scene/mesh.h"
# include "util/log.h"
/* Specialization of TopologyRefinerFactory for OsdMesh */
namespace OpenSubdiv::OPENSUBDIV_VERSION::Far {
using namespace ccl;
template<>
bool TopologyRefinerFactory<OsdMesh>::resizeComponentTopology(TopologyRefiner &refiner,
OsdMesh const &osd_mesh)
{
const Mesh &mesh = osd_mesh.mesh;
const int num_base_verts = mesh.get_num_subd_base_verts();
const int num_base_faces = mesh.get_num_subd_faces();
const int *subd_num_corners = mesh.get_subd_num_corners().data();
setNumBaseVertices(refiner, num_base_verts);
setNumBaseFaces(refiner, num_base_faces);
for (int i = 0; i < num_base_faces; i++) {
setNumBaseFaceVertices(refiner, i, subd_num_corners[i]);
}
return true;
}
template<>
bool TopologyRefinerFactory<OsdMesh>::assignComponentTopology(TopologyRefiner &refiner,
OsdMesh const &osd_mesh)
{
const Mesh &mesh = osd_mesh.mesh;
const int num_base_faces = mesh.get_num_subd_faces();
const int *subd_face_corners = mesh.get_subd_face_corners().data();
const int *subd_start_corner = mesh.get_subd_start_corner().data();
const int *subd_num_corners = mesh.get_subd_num_corners().data();
for (int i = 0; i < num_base_faces; i++) {
IndexArray face_verts = getBaseFaceVertices(refiner, i);
const int start_corner = subd_start_corner[i];
const int *corner = &subd_face_corners[start_corner];
for (int j = 0; j < subd_num_corners[i]; j++, corner++) {
face_verts[j] = *corner;
}
}
return true;
}
template<>
bool TopologyRefinerFactory<OsdMesh>::assignComponentTags(TopologyRefiner &refiner,
OsdMesh const &osd_mesh)
{
const Mesh &mesh = osd_mesh.mesh;
/* Historical maximum crease weight used at Pixar, influencing the maximum in OpenSubDiv. */
static constexpr float CREASE_SCALE = 10.0f;
const size_t num_creases = mesh.get_subd_creases_weight().size();
const size_t num_vertex_creases = mesh.get_subd_vert_creases().size();
/* The last loop is over the vertices, so early exit to avoid iterating them needlessly. */
if (num_creases == 0 && num_vertex_creases == 0) {
return true;
}
for (int i = 0; i < num_creases; i++) {
const Mesh::SubdEdgeCrease crease = mesh.get_subd_crease(i);
const Index edge = findBaseEdge(refiner, crease.v[0], crease.v[1]);
if (edge != INDEX_INVALID) {
setBaseEdgeSharpness(refiner, edge, crease.crease * CREASE_SCALE);
}
}
std::map<int, float> vertex_creases;
for (size_t i = 0; i < num_vertex_creases; ++i) {
const int vertex_idx = mesh.get_subd_vert_creases()[i];
const float weight = mesh.get_subd_vert_creases_weight()[i];
vertex_creases[vertex_idx] = weight * CREASE_SCALE;
}
const int num_base_verts = mesh.get_num_subd_base_verts();
for (int i = 0; i < num_base_verts; i++) {
float sharpness = 0.0f;
const std::map<int, float>::const_iterator iter = vertex_creases.find(i);
if (iter != vertex_creases.end()) {
sharpness = iter->second;
}
const ConstIndexArray vert_edges = getBaseVertexEdges(refiner, i);
if (vert_edges.size() == 2) {
const float sharpness0 = refiner.getLevel(0).getEdgeSharpness(vert_edges[0]);
const float sharpness1 = refiner.getLevel(0).getEdgeSharpness(vert_edges[1]);
sharpness += min(sharpness0, sharpness1);
sharpness = min(sharpness, CREASE_SCALE);
}
if (sharpness != 0.0f) {
setBaseVertexSharpness(refiner, i, sharpness);
}
}
return true;
}
template<typename T>
static void merge_smooth_fvar(const Mesh &mesh,
const Attribute &subd_attr,
OsdMesh::MergedFVar &merged_fvar,
vector<int> &merged_next,
vector<int> &merged_face_corners)
{
const int num_base_verts = mesh.get_num_subd_base_verts();
const int num_base_faces = mesh.get_num_subd_faces();
const int *subd_face_corners = mesh.get_subd_face_corners().data();
const T *values = reinterpret_cast<const T *>(subd_attr.data());
merged_fvar.values.resize(num_base_verts * sizeof(T));
// Merge identical corner values with the same vertex. The first value is stored at the vertex
// index, and any different values are pushed backed onto the array. merged_next creates a
// linked list between all values for the same vertex.
const int state_uninitialized = 0;
const int state_end = -1;
merged_next.resize(num_base_verts, state_uninitialized);
for (int f = 0, i = 0; f < num_base_faces; f++) {
Mesh::SubdFace face = mesh.get_subd_face(f);
for (int corner = 0; corner < face.num_corners; corner++) {
int v = subd_face_corners[face.start_corner + corner];
const T value = values[i++];
if (merged_next[v] == state_uninitialized) {
// First corner to initialize vertex.
reinterpret_cast<T *>(merged_fvar.values.data())[v] = value;
merged_next[v] = state_end;
merged_face_corners.push_back(v);
}
else {
// Find vertex with matching value, following linked list per vertex.
int v_prev = v;
for (; v != state_end; v_prev = v, v = merged_next[v]) {
if (reinterpret_cast<T *>(merged_fvar.values.data())[v] == value) {
// Matching value found, reuse merged vertex.
merged_face_corners.push_back(v);
break;
}
}
if (v == state_end) {
// Non-matching value, add new merged vertex and add to linked list.
const int next = merged_next.size();
merged_fvar.values.resize((next + 1) * sizeof(T));
reinterpret_cast<T *>(merged_fvar.values.data())[next] = value;
merged_next.push_back(state_end);
merged_next[v_prev] = next;
merged_face_corners.push_back(next);
}
}
}
}
}
template<>
bool TopologyRefinerFactory<OsdMesh>::assignFaceVaryingTopology(TopologyRefiner &refiner,
OsdMesh const &osd_mesh)
{
const Mesh &mesh = osd_mesh.mesh;
auto &merged_fvars = const_cast<OsdMesh &>(osd_mesh).merged_fvars;
for (const Attribute &subd_attr : mesh.subd_attributes.attributes) {
if (!osd_mesh.use_smooth_fvar(subd_attr)) {
continue;
}
// Created merged FVar, for use in subdivide_attribute_corner_smooth.
OsdMesh::MergedFVar merged_fvar{subd_attr};
vector<int> merged_next;
vector<int> merged_face_corners;
if (subd_attr.element == ATTR_ELEMENT_CORNER_BYTE) {
merge_smooth_fvar<uchar4>(mesh, subd_attr, merged_fvar, merged_next, merged_face_corners);
}
else if (Attribute::same_storage(subd_attr.type, TypeFloat)) {
merge_smooth_fvar<float>(mesh, subd_attr, merged_fvar, merged_next, merged_face_corners);
}
else if (Attribute::same_storage(subd_attr.type, TypeFloat2)) {
merge_smooth_fvar<float2>(mesh, subd_attr, merged_fvar, merged_next, merged_face_corners);
}
else if (Attribute::same_storage(subd_attr.type, TypeVector)) {
merge_smooth_fvar<float3>(mesh, subd_attr, merged_fvar, merged_next, merged_face_corners);
}
else if (Attribute::same_storage(subd_attr.type, TypeFloat4)) {
merge_smooth_fvar<float4>(mesh, subd_attr, merged_fvar, merged_next, merged_face_corners);
}
// Create FVar channel and topology for OpenUSD.
merged_fvar.channel = createBaseFVarChannel(refiner, merged_next.size());
const int num_base_faces = mesh.get_num_subd_faces();
for (int f = 0, i = 0; f < num_base_faces; f++) {
Far::IndexArray dst_face_uvs = getBaseFaceFVarValues(refiner, f, merged_fvar.channel);
const int num_corners = dst_face_uvs.size();
for (int corner = 0; corner < num_corners; corner++) {
dst_face_uvs[corner] = merged_face_corners[i++];
}
}
merged_fvars.push_back(std::move(merged_fvar));
}
return true;
}
template<>
void TopologyRefinerFactory<OsdMesh>::reportInvalidTopology(TopologyError /*err_code*/,
char const *msg,
OsdMesh const &osd_mesh)
{
const Mesh &mesh = osd_mesh.mesh;
LOG_WARNING << "Invalid subdivision topology for '" << mesh.name.c_str() << "': " << msg;
}
} // namespace OpenSubdiv::OPENSUBDIV_VERSION::Far
CCL_NAMESPACE_BEGIN
/* OsdMesh */
Sdc::Options OsdMesh::sdc_options()
{
Sdc::Options options;
switch (mesh.get_subdivision_fvar_interpolation()) {
case Mesh::SUBDIVISION_FVAR_LINEAR_NONE:
options.SetFVarLinearInterpolation(Sdc::Options::FVAR_LINEAR_NONE);
break;
case Mesh::SUBDIVISION_FVAR_LINEAR_CORNERS_ONLY:
options.SetFVarLinearInterpolation(Sdc::Options::FVAR_LINEAR_CORNERS_ONLY);
break;
case Mesh::SUBDIVISION_FVAR_LINEAR_CORNERS_PLUS1:
options.SetFVarLinearInterpolation(Sdc::Options::FVAR_LINEAR_CORNERS_PLUS1);
break;
case Mesh::SUBDIVISION_FVAR_LINEAR_CORNERS_PLUS2:
options.SetFVarLinearInterpolation(Sdc::Options::FVAR_LINEAR_CORNERS_PLUS2);
break;
case Mesh::SUBDIVISION_FVAR_LINEAR_BOUNDARIES:
options.SetFVarLinearInterpolation(Sdc::Options::FVAR_LINEAR_BOUNDARIES);
break;
case Mesh::SUBDIVISION_FVAR_LINEAR_ALL:
options.SetFVarLinearInterpolation(Sdc::Options::FVAR_LINEAR_ALL);
break;
}
switch (mesh.get_subdivision_boundary_interpolation()) {
case Mesh::SUBDIVISION_BOUNDARY_NONE:
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_NONE);
break;
case Mesh::SUBDIVISION_BOUNDARY_EDGE_ONLY:
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
break;
case Mesh::SUBDIVISION_BOUNDARY_EDGE_AND_CORNER:
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_AND_CORNER);
break;
}
return options;
}
bool OsdMesh::use_smooth_fvar(const Attribute &attr) const
{
return mesh.get_subdivision_fvar_interpolation() != Mesh::SUBDIVISION_FVAR_LINEAR_ALL &&
attr.element == ATTR_ELEMENT_CORNER &&
(attr.std == ATTR_STD_UV || (attr.flags & ATTR_SUBDIVIDE_SMOOTH_FVAR));
}
bool OsdMesh::use_smooth_fvar() const
{
for (const Attribute &attr : mesh.subd_attributes.attributes) {
if (use_smooth_fvar(attr)) {
return true;
}
}
return false;
}
/* OsdData */
void OsdData::build(OsdMesh &osd_mesh)
{
/* create refiner */
refiner.reset(Far::TopologyRefinerFactory<OsdMesh>::Create(
osd_mesh,
Far::TopologyRefinerFactory<OsdMesh>::Options(Sdc::SCHEME_CATMARK, osd_mesh.sdc_options())));
/* adaptive refinement */
const bool has_fvar = osd_mesh.use_smooth_fvar();
const int max_isolation = 3; // TODO: get from Blender
Far::TopologyRefiner::AdaptiveOptions adaptive_options(max_isolation);
adaptive_options.considerFVarChannels = has_fvar;
adaptive_options.useInfSharpPatch = true;
refiner->RefineAdaptive(adaptive_options);
/* create patch table */
Far::PatchTableFactory::Options patch_options;
patch_options.endCapType = Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS;
patch_options.generateFVarTables = has_fvar;
patch_options.generateFVarLegacyLinearPatches = false;
patch_options.useInfSharpPatch = true;
patch_table.reset(Far::PatchTableFactory::Create(*refiner, patch_options));
/* interpolate verts */
const int num_refiner_verts = refiner->GetNumVerticesTotal();
const int num_local_points = patch_table->GetNumLocalPoints();
const int num_base_verts = osd_mesh.mesh.get_num_subd_base_verts();
const Attribute *attr_P = osd_mesh.mesh.subd_attributes.find(ATTR_STD_POSITION);
const packed_float3 *verts_data = attr_P->data<packed_float3>();
refined_verts.resize(num_refiner_verts + num_local_points);
for (int i = 0; i < num_base_verts; i++) {
refined_verts[i].value = float3(verts_data[i]);
}
OsdValue<packed_float3> *src = refined_verts.data();
for (int i = 0; i < refiner->GetMaxLevel(); i++) {
OsdValue<packed_float3> *dest = src + refiner->GetLevel(i).GetNumVertices();
Far::PrimvarRefiner(*refiner).Interpolate(i + 1, src, dest);
src = dest;
}
if (num_local_points) {
patch_table->ComputeLocalPointValues(refined_verts.data(), &refined_verts[num_refiner_verts]);
}
/* Create patch map */
patch_map = make_unique<Far::PatchMap>(*patch_table);
}
/* OsdPatch */
void OsdPatch::eval(
float3 *P, float3 *dPdu, float3 *dPdv, float3 *N, const float u, const float v) const
{
const Far::PatchTable::PatchHandle &handle = *osd_data.patch_map->FindPatch(
patch_index, (double)u, (double)v);
float p_weights[20], du_weights[20], dv_weights[20];
osd_data.patch_table->EvaluateBasis(handle, u, v, p_weights, du_weights, dv_weights);
const Far::ConstIndexArray cv = osd_data.patch_table->GetPatchVertices(handle);
if (P) {
*P = zero_float3();
}
float3 du = zero_float3();
float3 dv = zero_float3();
for (int i = 0; i < cv.size(); i++) {
const float3 p = osd_data.refined_verts[cv[i]].value;
if (P) {
*P += p * p_weights[i];
}
du += p * du_weights[i];
dv += p * dv_weights[i];
}
if (dPdu) {
*dPdu = du;
}
if (dPdv) {
*dPdv = dv;
}
if (N) {
*N = safe_normalize_fallback(cross(du, dv), make_float3(0.0f, 0.0f, 1.0f));
}
}
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,104 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_OPENSUBDIV
/* OpenSubdiv headers use M_PI. */
# if defined(_MSC_VER) && !defined(_USE_MATH_DEFINES)
# define _USE_MATH_DEFINES
# endif
# include <opensubdiv/far/patchMap.h>
# include <opensubdiv/far/patchTableFactory.h>
# include <opensubdiv/far/primvarRefiner.h>
# include <opensubdiv/far/topologyRefinerFactory.h>
# include "subd/patch.h"
# include "util/types.h"
# include "util/unique_ptr.h"
# include "util/vector.h"
CCL_NAMESPACE_BEGIN
/* Directly use some OpenSubdiv namespaces for brevity. */
namespace Far = OpenSubdiv::Far;
namespace Sdc = OpenSubdiv::Sdc;
class Attribute;
class Mesh;
/* OpenSubdiv interface for vertex and attribute values. */
template<typename T> struct OsdValue {
T value;
OsdValue() = default;
void Clear(void *unused = nullptr)
{
(void)unused;
memset((void *)&value, 0, sizeof(T));
}
void AddWithWeight(OsdValue<T> const &src, float weight)
{
if constexpr (std::is_same_v<T, packed_float3>) {
value = float3(value) + float3(src.value) * weight;
}
else {
value += src.value * weight;
}
}
};
/* Wrapper around Mesh for TopologyRefinerFactory. */
class OsdMesh {
public:
/* Face-varying attribute that requires merging of corners with the same value, typically a UV
* map. The resulting topology after merging is stored in a topology refiner fvar channel. The
* merged attribute values are stored here, in a generic buffer used for different data types. */
struct MergedFVar {
const Attribute &attr;
int channel = -1;
vector<char> values;
};
Mesh &mesh;
vector<MergedFVar> merged_fvars;
explicit OsdMesh(Mesh &mesh) : mesh(mesh) {}
Sdc::Options sdc_options();
bool use_smooth_fvar(const Attribute &attr) const;
bool use_smooth_fvar() const;
};
/* OpenSubdiv refiner and patch data structures. */
struct OsdData {
unique_ptr<Far::TopologyRefiner> refiner;
unique_ptr<Far::PatchTable> patch_table;
unique_ptr<Far::PatchMap> patch_map;
vector<OsdValue<packed_float3>> refined_verts;
void build(OsdMesh &osd_mesh);
};
/* Patch with OpenSubdiv evaluation. */
struct OsdPatch final : Patch {
OsdData &osd_data;
explicit OsdPatch(OsdData &data) : osd_data(data) {}
void eval(float3 *P, float3 *dPdu, float3 *dPdv, float3 *N, const float u, const float v)
const override;
};
CCL_NAMESPACE_END
#endif

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
/* Parts adapted from code in the public domain in NVidia Mesh Tools. */
#include "subd/patch.h"
#include "util/math.h"
#include "util/types.h"
CCL_NAMESPACE_BEGIN
/* De Casteljau Evaluation */
static void decasteljau_cubic(float3 *P, float3 *dt, const float t, const float3 cp[4])
{
float3 d0 = cp[0] + t * (cp[1] - cp[0]);
float3 d1 = cp[1] + t * (cp[2] - cp[1]);
const float3 d2 = cp[2] + t * (cp[3] - cp[2]);
d0 += t * (d1 - d0);
d1 += t * (d2 - d1);
*P = d0 + t * (d1 - d0);
if (dt) {
*dt = d1 - d0;
}
}
static void decasteljau_bicubic(
float3 *P, float3 *du, float3 *dv, const float3 cp[16], float u, const float v)
{
float3 ucp[4];
float3 utn[4];
/* interpolate over u */
decasteljau_cubic(ucp + 0, utn + 0, u, cp);
decasteljau_cubic(ucp + 1, utn + 1, u, cp + 4);
decasteljau_cubic(ucp + 2, utn + 2, u, cp + 8);
decasteljau_cubic(ucp + 3, utn + 3, u, cp + 12);
/* interpolate over v */
decasteljau_cubic(P, dv, v, ucp);
if (du) {
decasteljau_cubic(du, nullptr, v, utn);
}
}
/* Linear Quad Patch */
void LinearQuadPatch::eval(
float3 *P, float3 *dPdu, float3 *dPdv, float3 *N, const float u, float v) const
{
const float3 d0 = interp(hull[0], hull[1], u);
const float3 d1 = interp(hull[2], hull[3], u);
*P = interp(d0, d1, v);
if (N || (dPdu && dPdv)) {
const float3 dPdu_ = interp(hull[1] - hull[0], hull[3] - hull[2], v);
const float3 dPdv_ = interp(hull[2] - hull[0], hull[3] - hull[1], u);
if (dPdu && dPdv) {
*dPdu = dPdu_;
*dPdv = dPdv_;
}
if (N) {
*N = normalize(cross(dPdu_, dPdv_));
}
}
}
BoundBox LinearQuadPatch::bound()
{
BoundBox bbox = BoundBox::empty;
for (int i = 0; i < 4; i++) {
bbox.grow(hull[i]);
}
return bbox;
}
/* Bicubic Patch */
void BicubicPatch::eval(
float3 *P, float3 *dPdu, float3 *dPdv, float3 *N, const float u, const float v) const
{
if (N) {
float3 dPdu_;
float3 dPdv_;
decasteljau_bicubic(P, &dPdu_, &dPdv_, hull, u, v);
if (dPdu && dPdv) {
*dPdu = dPdu_;
*dPdv = dPdv_;
}
*N = normalize(cross(dPdu_, dPdv_));
}
else {
decasteljau_bicubic(P, dPdu, dPdv, hull, u, v);
}
}
BoundBox BicubicPatch::bound()
{
BoundBox bbox = BoundBox::empty;
for (int i = 0; i < 16; i++) {
bbox.grow(hull[i]);
}
return bbox;
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,48 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/boundbox.h"
#include "util/types.h"
CCL_NAMESPACE_BEGIN
class Patch {
public:
Patch() = default;
virtual ~Patch() = default;
virtual void eval(
float3 *P, float3 *dPdu, float3 *dPdv, float3 *N, const float u, float v) const = 0;
int patch_index = 0;
int shader = 0;
bool smooth = true;
bool from_ngon = false;
};
/* Linear Quad Patch */
class LinearQuadPatch final : public Patch {
public:
float3 hull[4];
void eval(
float3 *P, float3 *dPdu, float3 *dPdv, float3 *N, const float u, float v) const override;
BoundBox bound();
};
/* Bicubic Patch */
class BicubicPatch final : public Patch {
public:
float3 hull[16];
void eval(
float3 *P, float3 *dPdu, float3 *dPdv, float3 *N, const float u, float v) const override;
BoundBox bound();
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,782 @@
/* 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 */
DiagSplit::DiagSplit(const SubdParams &params_) : 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 int depth, bool &was_missing)
{
const SubEdge edge(v0, v1, depth);
const auto it = edges.find(edge);
was_missing = (it == edges.end());
return const_cast<SubEdge *>(was_missing ? &*(edges.emplace(edge).first) : &*it);
}
void DiagSplit::alloc_edge(SubPatch::Edge *sub_edge,
const int v0,
const int v1,
const int depth,
const bool want_to_own_edge,
const bool want_to_own_vertex)
{
bool was_missing;
sub_edge->edge = (v0 < v1) ? alloc_edge(v0, v1, depth, was_missing) :
alloc_edge(v1, v0, depth, was_missing);
sub_edge->own_vertex = false;
sub_edge->own_edge = was_missing && want_to_own_edge;
sub_edge->reversed = sub_edge->edge->start_vert_index != v0;
if (want_to_own_vertex) {
if (v0 < owned_verts.size()) {
/* Vertex in original mesh. */
if (!owned_verts[v0]) {
owned_verts[v0] = true;
sub_edge->own_vertex = true;
}
}
else {
/* Mid edge vertex. */
sub_edge->own_vertex = true;
}
}
}
void DiagSplit::alloc_subpatch(SubPatch &&sub)
{
assert(sub.edges[0].edge->T >= 1);
assert(sub.edges[1].edge->T >= 1);
assert(sub.edges[2].edge->T >= 1);
if (sub.shape == SubPatch::QUAD) {
assert(sub.edges[3].edge->T >= 1);
}
sub.inner_grid_vert_offset = alloc_verts(sub.calc_num_inner_verts());
sub.triangles_offset = num_triangles;
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(&params.objecttoworld, P);
}
return P;
}
std::pair<int, float> DiagSplit::T(const Patch *patch,
float2 uv_start,
float2 uv_end,
const int depth,
const bool recursive_resolve)
{
/* May not be necessary, but better to be safe. */
if (uv_end.x < uv_start.x || uv_end.y < uv_start.y) {
swap(uv_start, uv_end);
}
float Lsum = 0.0f;
float Lmax = 0.0f;
float Lsum_world = 0.0f;
float3 Plast = to_world(patch, uv_start);
for (int i = 1; i < params.test_steps; i++) {
const float t = i / (float)(params.test_steps - 1);
const float3 P = to_world(patch, uv_start + t * (uv_end - uv_start));
float L = len(P - Plast);
Lsum_world += L;
if (params.camera) {
Camera *cam = params.camera;
const float pixel_width = cam->world_to_raster_size((P + Plast) * 0.5f);
L /= 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 uv_mid = (uv_start + uv_end) * 0.5f;
const auto result_a = T(patch, uv_start, uv_mid, depth, true);
const auto result_b = T(patch, uv_mid, uv_end, depth, true);
res = result_a.first + result_b.first;
Lsum_world = result_a.second + result_b.second;
}
}
if (!recursive_resolve && res > DSPLIT_MAX_SEGMENTS) {
res = DSPLIT_NON_UNIFORM;
}
res = limit_edge_factor(patch, uv_start, uv_end, res);
/* Limit edge factor so we don't go beyond max depth. -3 is so that
* for triangle patches, all 3 edges get an opportunity to get split. */
if (depth >= DSPLIT_MAX_DEPTH - 3 && res == DSPLIT_NON_UNIFORM) {
res = DSPLIT_MAX_SEGMENTS;
}
return std::make_pair(res, Lsum_world);
}
int DiagSplit::limit_edge_factor(const Patch *patch,
const float2 uv_start,
const float2 uv_end,
const int T)
{
const int max_t = 1 << params.max_level;
int max_t_for_edge = int(max_t * len(uv_start - uv_end));
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 != 0);
return limit_T;
}
void DiagSplit::assign_edge_factor(SubEdge *edge,
const Patch *patch,
float2 uv_start,
float2 uv_end,
const bool recursive_resolve)
{
assert(edge->T <= 0);
const auto result = T(patch, uv_start, uv_end, edge->depth, recursive_resolve);
edge->T = result.first;
edge->length = result.second;
/* Ensure we can always split at depth - 1. */
if (edge->depth == -1 && edge->T == 1) {
edge->T = 2;
}
if (edge->T > 0) {
edge->second_vert_index = alloc_verts(edge->T - 1);
}
}
void DiagSplit::resolve_edge_factors(const SubPatch &sub)
{
SubEdge *edge0 = sub.edges[0].edge;
SubEdge *edge1 = sub.edges[1].edge;
SubEdge *edge2 = sub.edges[2].edge;
/* Compute edge factor if not already set. */
if (edge0->T == 0) {
assign_edge_factor(edge0, sub.patch, sub.uvs[0], sub.uvs[1], true);
}
if (edge1->T == 0) {
assign_edge_factor(edge1, sub.patch, sub.uvs[1], sub.uvs[2], true);
}
if (sub.shape == SubPatch::TRIANGLE) {
if (edge2->T == 0) {
assign_edge_factor(edge2, sub.patch, sub.uvs[2], sub.uvs[0], true);
}
}
else {
SubEdge *edge3 = sub.edges[3].edge;
if (edge2->T == 0) {
assign_edge_factor(edge2, sub.patch, sub.uvs[2], sub.uvs[3], true);
}
if (edge3->T == 0) {
assign_edge_factor(edge3, sub.patch, sub.uvs[3], sub.uvs[0], true);
}
}
}
float2 DiagSplit::split_edge(const Patch *patch,
SubPatch::Edge *subedge,
SubPatch::Edge *subedge_a,
SubPatch::Edge *subedge_b,
float2 uv_start,
float2 uv_end)
{
/* This splits following the direction of the edge itself, not subpatch edge direction. */
if (subedge->reversed) {
swap(uv_start, uv_end);
}
SubEdge *edge = subedge->edge;
if (edge->must_split()) {
/* Split down the middle. */
const float2 P = 0.5f * (uv_start + uv_end);
if (edge->mid_vert_index == -1) {
/* Allocate mid vertex and edges. */
edge->mid_vert_index = alloc_verts(1);
bool unused;
SubEdge *edge_a = alloc_edge(
edge->start_vert_index, edge->mid_vert_index, edge->depth + 1, unused);
SubEdge *edge_b = alloc_edge(
edge->mid_vert_index, edge->end_vert_index, edge->depth + 1, unused);
assign_edge_factor(edge_a, patch, uv_start, P);
assign_edge_factor(edge_b, patch, P, uv_end);
}
/* Allocate sub edges and set ownership. */
alloc_edge(subedge_a,
subedge->start_vert_index(),
subedge->mid_vert_index(),
edge->depth + 1,
false,
false);
alloc_edge(subedge_b,
subedge->mid_vert_index(),
subedge->end_vert_index(),
edge->depth + 1,
false,
false);
subedge_a->own_edge = subedge->own_edge;
subedge_b->own_edge = subedge->own_edge;
subedge_a->own_vertex = subedge->own_vertex;
subedge_b->own_vertex = subedge->own_edge;
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;
bool unused;
SubEdge *edge_a = alloc_edge(
edge->start_vert_index, edge->mid_vert_index, edge->depth + 1, unused);
SubEdge *edge_b = alloc_edge(
edge->mid_vert_index, edge->end_vert_index, edge->depth + 1, unused);
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;
}
/* Allocate sub edges and set ownership. */
alloc_edge(subedge_a,
subedge->start_vert_index(),
subedge->mid_vert_index(),
edge->depth + 1,
false,
false);
alloc_edge(subedge_b,
subedge->mid_vert_index(),
subedge->end_vert_index(),
edge->depth + 1,
false,
false);
subedge_a->own_edge = subedge->own_edge;
subedge_b->own_edge = subedge->own_edge;
subedge_a->own_vertex = subedge->own_vertex;
subedge_b->own_vertex = subedge->own_edge;
const float2 P = interp(uv_start, uv_end, 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_quad(SubPatch &&sub)
{
/* Set edge factors if we haven't already. */
resolve_edge_factors(sub);
/* Split subpatch if edges are marked as must split,
* 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.edges[0].edge->T, sub.edges[2].edge->T);
const int max_T_u = max(sub.edges[0].edge->T, sub.edges[2].edge->T);
const int min_T_v = min(sub.edges[3].edge->T, sub.edges[1].edge->T);
const int max_T_v = max(sub.edges[3].edge->T, sub.edges[1].edge->T);
bool split_u = sub.edges[0].edge->must_split() || sub.edges[2].edge->must_split() ||
(min_T_u >= 2 && min_T_v > DSPLIT_MAX_SEGMENTS && max_T_v / min_T_v > 1.5f);
bool split_v = sub.edges[3].edge->must_split() || sub.edges[1].edge->must_split() ||
(min_T_v >= 2 && min_T_u > DSPLIT_MAX_SEGMENTS && max_T_u / min_T_u > 1.5f);
/* If both need to split, pick longest axis. */
if (split_u && split_v) {
/* Slight bias so that for square quads, we get consistent results across
* platforms rather than choice being decided by precision. */
const float bias = 1.00012345f;
if ((sub.edges[0].edge->length + sub.edges[2].edge->length) * bias >=
sub.edges[1].edge->length + sub.edges[3].edge->length)
{
split_u = true;
split_v = false;
}
else {
split_u = false;
split_v = true;
}
}
if (!split_u && !split_v) {
/* Add the unsplit subpatch. */
alloc_subpatch(std::move(sub));
return;
}
/* Split into triangles if one side must the split, and the opposite side has
* only a single segment. Then we can't do an even split across the quad. */
if ((split_u && (sub.edges[0].edge->T == 1 || sub.edges[2].edge->T == 1)) ||
(!split_u && (sub.edges[1].edge->T == 1 || sub.edges[3].edge->T == 1)))
{
split_quad_into_triangles(std::move(sub));
return;
}
/* Copy into new subpatches. */
SubPatch sub_a(sub);
SubPatch sub_b(sub);
for (int i = 0; i < 4; i++) {
sub_a.edges[i].own_edge = false;
sub_a.edges[i].own_vertex = false;
sub_b.edges[i].own_edge = false;
sub_b.edges[i].own_vertex = false;
}
/* 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 *uv_a;
float2 *uv_b;
float2 *uv_c;
float2 *uv_d;
/* Set pointers based on split axis. */
if (split_u) {
/*
* sub_across_1
* -------uv_a uv_c-------
* | | | |
* | A | | B |
* | | | |
* -------uv_b uv_d-------
* sub_across_0
*/
sub_across_0 = &sub.edges[0];
sub_across_1 = &sub.edges[2];
sub_a_across_0 = &sub_a.edges[0];
sub_a_across_1 = &sub_a.edges[2];
sub_b_across_0 = &sub_b.edges[0];
sub_b_across_1 = &sub_b.edges[2];
sub_a.edges[3].own_edge = sub.edges[3].own_edge;
sub_a.edges[3].own_vertex = sub.edges[3].own_vertex;
sub_b.edges[1].own_edge = sub.edges[1].own_edge;
sub_b.edges[1].own_vertex = sub.edges[1].own_vertex;
sub_a_split = &sub_a.edges[1];
sub_b_split = &sub_b.edges[3];
uv_a = &sub_a.uvs[2];
uv_b = &sub_a.uvs[1];
uv_c = &sub_b.uvs[3];
uv_d = &sub_b.uvs[0];
}
else {
/*
* --------------------
* | A |
* uv_b------------uv_a
* sub_across_0 sub_across_1
* uv_d------------uv_c
* | B |
* --------------------
*/
sub_across_0 = &sub.edges[3];
sub_across_1 = &sub.edges[1];
sub_a_across_0 = &sub_a.edges[3];
sub_a_across_1 = &sub_a.edges[1];
sub_b_across_0 = &sub_b.edges[3];
sub_b_across_1 = &sub_b.edges[1];
sub_a.edges[2].own_edge = sub.edges[2].own_edge;
sub_a.edges[2].own_vertex = sub.edges[2].own_vertex;
sub_b.edges[0].own_edge = sub.edges[0].own_edge;
sub_b.edges[0].own_vertex = sub.edges[0].own_vertex;
sub_a_split = &sub_a.edges[0];
sub_b_split = &sub_b.edges[2];
uv_a = &sub_a.uvs[1];
uv_b = &sub_a.uvs[0];
uv_c = &sub_b.uvs[2];
uv_d = &sub_b.uvs[3];
}
/* Allocate new edges and vertices. */
const float2 uv0 = split_edge(
sub.patch, sub_across_0, sub_a_across_0, sub_b_across_0, *uv_d, *uv_b);
const float2 uv1 = split_edge(
sub.patch, sub_across_1, sub_b_across_1, sub_a_across_1, *uv_a, *uv_c);
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 */
*uv_a = uv1;
*uv_b = uv0;
*uv_c = uv1;
*uv_d = uv0;
/* Create new edge */
const int split_edge_depth = (split_u) ?
max(sub.edges[1].edge->depth, sub.edges[3].edge->depth) :
max(sub.edges[0].edge->depth, sub.edges[2].edge->depth);
alloc_edge(sub_a_split,
sub_across_0->mid_vert_index(),
sub_across_1->mid_vert_index(),
split_edge_depth,
true,
false);
alloc_edge(sub_b_split,
sub_across_1->mid_vert_index(),
sub_across_0->mid_vert_index(),
split_edge_depth,
true,
false);
/* Set T for split edge. */
assign_edge_factor(sub_a_split->edge, sub.patch, uv0, uv1);
/* Recurse */
split_quad(std::move(sub_a));
split_quad(std::move(sub_b));
}
void DiagSplit::split_quad_into_triangles(SubPatch &&sub)
{
assert(sub.shape == SubPatch::QUAD);
/* Copy into new subpatches. */
SubPatch sub_a(sub);
SubPatch sub_b(sub);
sub_a.shape = SubPatch::TRIANGLE;
sub_b.shape = SubPatch::TRIANGLE;
for (int i = 0; i < 4; i++) {
sub_a.edges[i].own_edge = false;
sub_a.edges[i].own_vertex = false;
sub_b.edges[i].own_edge = false;
sub_b.edges[i].own_vertex = false;
}
const int split_edge_depth = std::max({sub.edges[0].edge->depth,
sub.edges[1].edge->depth,
sub.edges[2].edge->depth,
sub.edges[3].edge->depth});
sub_a.edges[0] = sub.edges[0];
sub_a.edges[1] = sub.edges[1];
sub_a.uvs[0] = sub.uvs[0];
sub_a.uvs[1] = sub.uvs[1];
sub_a.uvs[2] = sub.uvs[2];
alloc_edge(&sub_a.edges[2],
sub.edges[2].start_vert_index(),
sub.edges[0].start_vert_index(),
split_edge_depth,
true,
false);
sub_b.edges[1] = sub.edges[2];
sub_b.edges[2] = sub.edges[3];
sub_b.uvs[0] = sub.uvs[0];
sub_b.uvs[1] = sub.uvs[2];
sub_b.uvs[2] = sub.uvs[3];
alloc_edge(&sub_b.edges[0],
sub.edges[0].start_vert_index(),
sub.edges[2].start_vert_index(),
split_edge_depth,
true,
false);
/* Set T for new edge. */
assign_edge_factor(sub_b.edges[0].edge, sub.patch, sub.uvs[0], sub.uvs[2]);
/* Recurse */
split_triangle(std::move(sub_a));
split_triangle(std::move(sub_b));
}
void DiagSplit::split_triangle(SubPatch &&sub)
{
assert(sub.shape == SubPatch::TRIANGLE);
/* Set edge factors if we haven't already. */
resolve_edge_factors(sub);
const bool do_split = sub.edges[0].edge->must_split() || sub.edges[1].edge->must_split() ||
sub.edges[2].edge->must_split();
if (!do_split) {
/* Add the unsplit subpatch. */
alloc_subpatch(std::move(sub));
return;
}
/* Slight bias so that for equal length edges, we get consistent results across
* platforms rather than choice being decided by precision. */
const float bias = 1.00012345f;
/* Pick longest edge that must be split. Note that in degenerate cases edges may have
* zero length but still requires splitting at depth 0. */
float max_length = 0.0f;
int split_index_0 = -1;
for (int i = 0; i < 3; i++) {
if (sub.edges[i].edge->must_split() &&
(split_index_0 == -1 || sub.edges[i].edge->length > max_length))
{
split_index_0 = i;
max_length = sub.edges[i].edge->length * bias;
}
}
/* Copy into new subpatches. */
SubPatch sub_a(sub);
SubPatch sub_b(sub);
for (int i = 0; i < 4; i++) {
sub_a.edges[i].own_edge = false;
sub_a.edges[i].own_vertex = false;
sub_b.edges[i].own_edge = false;
sub_b.edges[i].own_vertex = false;
}
const int split_index_1 = (split_index_0 + 1) % 3;
const int split_index_2 = (split_index_0 + 2) % 3;
sub_a.edges[2] = sub.edges[split_index_2];
sub_b.edges[1] = sub.edges[split_index_1];
/*
* uv_opposite
* 2 2
* / | | \
* / | | \
* / A | | B \
* / | | \
* 0 --- 1 0 --- 1
* uv_split
*/
/* Allocate new edges and vertices. */
const float2 uv_split = split_edge(sub.patch,
&sub.edges[split_index_0],
&sub_a.edges[0],
&sub_b.edges[0],
sub.uvs[split_index_0],
sub.uvs[split_index_1]);
/* Set UVs. */
sub_a.uvs[0] = sub.uvs[split_index_0];
sub_a.uvs[1] = uv_split;
sub_a.uvs[2] = sub.uvs[split_index_2];
sub_b.uvs[0] = uv_split;
sub_b.uvs[1] = sub.uvs[split_index_1];
sub_b.uvs[2] = sub.uvs[split_index_2];
/* Create new edge */
const int vsplit = sub.edges[split_index_0].mid_vert_index();
const int vopposite = sub.edges[split_index_2].start_vert_index();
const int split_edge_depth = sub.edges[split_index_0].edge->depth + 1;
alloc_edge(&sub_a.edges[1], vsplit, vopposite, split_edge_depth, true, false);
alloc_edge(&sub_b.edges[2], vopposite, vsplit, split_edge_depth, true, false);
/* Set T for split edge. */
const float2 uv_opposite = sub.uvs[split_index_2];
assign_edge_factor(sub_a.edges[1].edge, sub.patch, uv_split, uv_opposite);
/* Recurse */
split_triangle(std::move(sub_a));
split_triangle(std::move(sub_b));
}
void DiagSplit::split_quad(const Mesh::SubdFace &face, const int face_index, const Patch *patch)
{
const int *subd_face_corners = params.mesh->get_subd_face_corners().data();
const int v0 = subd_face_corners[face.start_corner + 0];
const int v1 = subd_face_corners[face.start_corner + 1];
const int v2 = subd_face_corners[face.start_corner + 2];
const int v3 = subd_face_corners[face.start_corner + 3];
const int depth = -1;
SubPatch subpatch(patch, face_index);
alloc_edge(&subpatch.edges[0], v0, v1, depth, true, true);
alloc_edge(&subpatch.edges[1], v1, v2, depth, true, true);
alloc_edge(&subpatch.edges[2], v2, v3, depth, true, true);
alloc_edge(&subpatch.edges[3], v3, v0, depth, true, true);
/* Forces a split in both axis for quads, needed to match split of ngons into quads. */
subpatch.edges[0].edge->T = DSPLIT_NON_UNIFORM;
subpatch.edges[3].edge->T = DSPLIT_NON_UNIFORM;
subpatch.edges[2].edge->T = DSPLIT_NON_UNIFORM;
subpatch.edges[1].edge->T = DSPLIT_NON_UNIFORM;
split_quad(std::move(subpatch));
}
void DiagSplit::split_ngon(const Mesh::SubdFace &face,
const int face_index,
const Patch *patches,
const size_t patches_byte_stride)
{
const int *subd_face_corners = params.mesh->get_subd_face_corners().data();
const int v2 = alloc_verts(1);
const int depth = 0;
/* Allocate edges of n-gon. */
array<SubPatch::Edge> edges(face.num_corners);
for (int corner = 0; corner < face.num_corners; corner++) {
const int v = subd_face_corners[face.start_corner + corner];
const int vnext = subd_face_corners[face.start_corner + mod(corner + 1, face.num_corners)];
alloc_edge(&edges[corner], v, vnext, depth, true, true);
if (edges[corner].edge->mid_vert_index == -1) {
edges[corner].edge->mid_vert_index = alloc_verts(1);
}
}
/* Allocate patches. */
for (int corner = 0; corner < face.num_corners; corner++) {
const Patch *patch = (const Patch *)(((char *)patches) + (corner * patches_byte_stride));
/* v_prev .
* . .
* . edge2 .
* v3 ←------- v2 . . .
* | ↑
* edge3 | | edge1
* ↓ |
* v0 ------→ v1 . . v_next
* edge0
*/
SubPatch::Edge &edge3 = edges[mod(corner + face.num_corners - 1, face.num_corners)];
SubPatch::Edge &edge0 = edges[corner];
/* Setup edges. */
const int v0 = edge0.start_vert_index();
const int v1 = edge0.mid_vert_index();
const int v3 = edge3.mid_vert_index();
SubPatch subpatch(patch, face_index, corner);
alloc_edge(&subpatch.edges[0], v0, v1, depth, false, false);
alloc_edge(&subpatch.edges[1], v1, v2, depth, true, false);
alloc_edge(&subpatch.edges[2], v2, v3, depth, true, corner == 0);
alloc_edge(&subpatch.edges[3], v3, v0, depth, false, false);
subpatch.edges[0].own_edge = edge0.own_edge;
subpatch.edges[0].own_vertex = edge0.own_vertex;
subpatch.edges[3].own_edge = edge3.own_edge;
subpatch.edges[3].own_vertex = edge3.own_edge;
/* Perform split. */
split_quad(std::move(subpatch));
}
}
void DiagSplit::split_patches(const Patch *patches, const size_t patches_byte_stride)
{
/* TODO: reuse edge factor vertex position computations. */
/* TODO: support not splitting n-gons if not needed. */
/* TODO: multi-threading. */
/* Keep base mesh vertices, create new triangles. */
num_verts = params.mesh->get_num_subd_base_verts();
num_triangles = 0;
owned_verts.resize(num_verts, false);
/* Split all faces in the mesh. */
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) +
(face.ptex_offset * patches_byte_stride));
if (face.is_quad()) {
split_quad(face, f, patch);
}
else {
split_ngon(face, f, patch, patches_byte_stride);
}
}
}
CCL_NAMESPACE_END

View File

@@ -0,0 +1,107 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
/* DiagSplit: Parallel, Crack-free, Adaptive Tessellation for Micro-polygon Rendering
* Splits up patches and determines edge tessellation factors for dicing. Patch
* evaluation at arbitrary points is required for this to work. See the paper
* for more details. */
#include "scene/mesh.h"
#include "subd/dice.h"
#include "subd/subpatch.h"
#include "util/set.h"
#include "util/types.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
class Mesh;
class Patch;
class SubdAttributeInterpolation;
class DiagSplit {
private:
SubdParams params;
vector<SubPatch> subpatches;
vector<bool> owned_verts;
unordered_set<SubEdge, SubEdge::Hash, SubEdge::Equal> edges;
int num_verts = 0;
int num_triangles = 0;
/* Allocate vertices, edges and subpatches. */
int alloc_verts(const int num);
SubEdge *alloc_edge(const int v0, const int v1, const int depth, bool &was_missing);
void alloc_edge(SubPatch::Edge *sub_edge,
const int v0,
const int v1,
const int depth,
const bool want_to_own_edge,
const bool want_to_own_vertex);
void alloc_subpatch(SubPatch &&sub);
/* Compute edge factors. */
float3 to_world(const Patch *patch, const float2 uv);
std::pair<int, float> T(const Patch *patch,
const float2 uv_start,
const float2 uv_end,
const int depth,
const bool recursive_resolve = false);
int limit_edge_factor(const Patch *patch,
const float2 uv_start,
const float2 uv_end,
const int T);
void assign_edge_factor(SubEdge *edge,
const Patch *patch,
const float2 uv_start,
const float2 uv_end,
const bool recursive_resolve = false);
void resolve_edge_factors(const SubPatch &sub);
/* Split edge, subpatch, quad and n-gon. */
float2 split_edge(const Patch *patch,
SubPatch::Edge *subedge,
SubPatch::Edge *subedge_a,
SubPatch::Edge *subedge_b,
float2 uv_start,
float2 uv_end);
void split_quad(SubPatch &&sub);
void split_triangle(SubPatch &&sub);
void split_quad_into_triangles(SubPatch &&sub);
void split_quad(const Mesh::SubdFace &face, const int face_index, const Patch *patch);
void split_ngon(const Mesh::SubdFace &face,
const int face_index,
const Patch *patches,
const size_t patches_byte_stride);
public:
explicit DiagSplit(const SubdParams &params);
void split_patches(const Patch *patches, const size_t patches_byte_stride);
size_t get_num_subpatches() const
{
return subpatches.size();
}
const SubPatch &get_subpatch(const size_t i) const
{
return subpatches[i];
}
int get_num_verts() const
{
return num_verts;
}
int get_num_triangles() const
{
return num_triangles;
}
};
CCL_NAMESPACE_END

View File

@@ -0,0 +1,310 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#include "util/hash.h"
#include "util/math_float2.h"
CCL_NAMESPACE_BEGIN
class Patch;
enum {
DSPLIT_NON_UNIFORM = -1,
DSPLIT_MAX_DEPTH = 16,
DSPLIT_MAX_SEGMENTS = 8,
};
/* SubEdge */
struct SubEdge {
SubEdge(const int start_vert_index, const int end_vert_index, const int depth)
: start_vert_index(start_vert_index), end_vert_index(end_vert_index), depth(depth)
{
}
/* Vertex indices. */
int start_vert_index;
int end_vert_index;
/* If edge was split, vertex index in the middle. */
int mid_vert_index = -1;
/* Number of segments the edge will be diced into, see DiagSplit paper. */
int T = 0;
/* Estimated length of edge, for determining preferred split direction. */
float length = 0.0f;
/* Index of the second vert from this edges corner along the edge towards the next corner. */
int second_vert_index = -1;
/* How many times an edge was subdivided to get this edge. */
int depth = 0;
SubEdge() = default;
int get_vert_along_edge(const int n) const
{
assert(n >= 0 && n <= T);
if (n == 0) {
return start_vert_index;
}
if (n == T) {
return end_vert_index;
}
return second_vert_index + n - 1;
}
bool must_split() const
{
return T == DSPLIT_NON_UNIFORM;
}
struct Hash {
size_t operator()(const SubEdge &edge) const
{
int a = edge.start_vert_index;
int b = edge.end_vert_index;
if (b > a) {
std::swap(a, b);
}
return hash_uint2(a, b);
}
};
struct Equal {
size_t operator()(const SubEdge &a, const SubEdge &b) const
{
return (a.start_vert_index == b.start_vert_index && a.end_vert_index == b.end_vert_index) ||
(a.start_vert_index == b.end_vert_index && a.end_vert_index == b.start_vert_index);
}
};
};
/* SubPatch */
class SubPatch {
public:
/* Patch this is a subpatch of. */
const Patch *patch = nullptr;
/* Face and corner. */
int face_index = 0;
int corner = 0;
/* Is a triangular patch instead of a quad patch? */
enum { TRIANGLE, QUAD } shape = QUAD;
/* Vertex indices for inner grid start at this index. */
int inner_grid_vert_offset = 0;
/* Triangle indices. */
int triangles_offset = 0;
/* Edge of patch. */
struct Edge {
SubEdge *edge;
/* Is the direction of this edge reverse compared to SubEdge? */
bool reversed;
/* Is this subpatch responsible for owning attributes for the start vertex? */
bool own_vertex;
/* Is this subpatch responsible for owning attributes for edge vertices? */
bool own_edge;
/* Get vertex indices in the direction of this patch edge, will take into
* account the reversed flag to flip the indices. */
int start_vert_index() const
{
return (reversed) ? edge->end_vert_index : edge->start_vert_index;
}
int mid_vert_index() const
{
return edge->mid_vert_index;
}
int end_vert_index() const
{
return (reversed) ? edge->start_vert_index : edge->end_vert_index;
}
int get_vert_along_edge(const int n_relative) const
{
assert(n_relative >= 0 && n_relative <= edge->T);
const int n = (reversed) ? edge->T - n_relative : n_relative;
return edge->get_vert_along_edge(n);
}
};
/*
* edge2
* uv3 ←------------ uv2
* | ↑
* edge3 | | edge1
* ↓ |
* uv0 ------------→ uv1
* edge0
*
* uv2
* | \
* | \
* edge2 | \ edge1
* | \
* ↓ \
* uv0 --→ uv1
* edge0
*/
/* UV within patch, counter-clockwise starting from uv (0, 0) towards (1, 0) etc. */
float2 uvs[4] = {zero_float2(), make_float2(1.0f, 0.0f), one_float2(), make_float2(0.0f, 1.0f)};
/* Edges of this subpatch. */
Edge edges[4] = {};
explicit SubPatch(const Patch *patch, const int face_index, const int corner = 0)
: patch(patch), face_index(face_index), corner(corner)
{
}
int calc_num_inner_verts() const
{
if (shape == TRIANGLE) {
const int M = max(max(edges[0].edge->T, edges[1].edge->T), edges[2].edge->T);
if (M <= 2) {
/* No inner grid. */
return 0;
}
/* 1 + 2 + .. + M-1 */
return M * (M - 1) / 2;
}
const int Mu = max(edges[0].edge->T, edges[2].edge->T);
const int Mv = max(edges[3].edge->T, edges[1].edge->T);
return (Mu - 1) * (Mv - 1);
}
int calc_num_triangles() const
{
if (shape == TRIANGLE) {
const int M = max(max(edges[0].edge->T, edges[1].edge->T), edges[2].edge->T);
if (M == 1) {
return 1;
}
if (M == 2) {
return edges[0].edge->T + edges[1].edge->T + edges[2].edge->T - 2;
}
const int inner_M = M - 2;
const int inner_triangles = inner_M * inner_M;
const int edge_triangles = edges[0].edge->T + edges[1].edge->T + edges[2].edge->T +
inner_M * 3;
return inner_triangles + edge_triangles;
}
const int Mu = max(edges[0].edge->T, edges[2].edge->T);
const int Mv = max(edges[3].edge->T, edges[1].edge->T);
if (Mu == 1) {
return edges[3].edge->T + edges[1].edge->T;
}
if (Mv == 1) {
return edges[0].edge->T + edges[2].edge->T;
}
const int inner_triangles = (Mu - 2) * (Mv - 2) * 2;
const int edge_triangles = edges[0].edge->T + edges[2].edge->T + edges[3].edge->T +
edges[1].edge->T + ((Mu - 2) * 2) + ((Mv - 2) * 2);
return inner_triangles + edge_triangles;
}
int get_vert_along_edge(const int edge, const int n) const
{
return edges[edge].get_vert_along_edge(n);
}
int get_vert_along_edge_reverse(const int edge, const int n) const
{
return get_vert_along_edge(edge, edges[edge].edge->T - n);
}
int get_inner_grid_vert_triangle(int i, int j) const
{
/* Rows `(1 + 2 + .. + j)`, and column `i`. */
const int offset = j * (j + 1) / 2 + i;
assert(offset < calc_num_inner_verts());
return inner_grid_vert_offset + offset;
}
int get_vert_along_grid_edge(const int edge, const int n) const
{
if (shape == TRIANGLE) {
const int M = max(max(edges[0].edge->T, edges[1].edge->T), edges[2].edge->T);
const int inner_M = M - 2;
assert(M >= 2);
switch (edge) {
case 0: {
return get_inner_grid_vert_triangle(n, n);
}
case 1: {
return get_inner_grid_vert_triangle(inner_M - n, inner_M);
}
case 2: {
return get_inner_grid_vert_triangle(0, inner_M - n);
}
default:
assert(0);
break;
}
return -1;
}
const int Mu = max(edges[0].edge->T, edges[2].edge->T);
const int Mv = max(edges[3].edge->T, edges[1].edge->T);
assert(Mu >= 2 && Mv >= 2);
switch (edge) {
case 0: {
return inner_grid_vert_offset + n;
}
case 1: {
return inner_grid_vert_offset + (Mu - 2) + n * (Mu - 1);
}
case 2: {
const int reverse_n = (Mu - 2) - n;
return inner_grid_vert_offset + (Mu - 1) * (Mv - 2) + reverse_n;
}
case 3: {
const int reverse_n = (Mv - 2) - n;
return inner_grid_vert_offset + reverse_n * (Mu - 1);
}
default:
assert(0);
break;
}
return -1;
}
float2 map_uv(float2 uv) const
{
/* Map UV from subpatch to patch parametric coordinates. */
if (shape == TRIANGLE) {
return clamp((1.0f - uv.x - uv.y) * uvs[0] + uv.x * uvs[1] + uv.y * uvs[2],
zero_float2(),
one_float2());
}
const float2 d0 = interp(uvs[0], uvs[3], uv.y);
const float2 d1 = interp(uvs[1], uvs[2], uv.y);
return clamp(interp(d0, d1, uv.x), zero_float2(), one_float2());
}
};
CCL_NAMESPACE_END