UI: Highlight Selected Enum #111074
|
@ -217,10 +217,6 @@ bool BKE_paint_always_hide_test(Object *ob);
|
|||
|
||||
/* Partial visibility. */
|
||||
|
||||
/**
|
||||
* Returns non-zero if any of the face's vertices are hidden, zero otherwise.
|
||||
*/
|
||||
bool paint_is_face_hidden(const int *looptri_faces, const bool *hide_poly, int tri_index);
|
||||
/**
|
||||
* Returns non-zero if any of the corners of the grid
|
||||
* face whose inner corner is at (x, y) are hidden, zero otherwise.
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include "BLI_bitmap.h"
|
||||
#include "BLI_compiler_compat.h"
|
||||
#include "BLI_ghash.h"
|
||||
#include "BLI_index_mask.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_offset_indices.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
@ -211,7 +210,7 @@ void BKE_pbvh_build_grids(PBVH *pbvh,
|
|||
CCGElem **grids,
|
||||
int totgrid,
|
||||
CCGKey *key,
|
||||
blender::Span<int> gridfaces,
|
||||
void **gridfaces,
|
||||
DMFlagMat *flagmats,
|
||||
unsigned int **grid_hidden,
|
||||
Mesh *me,
|
||||
|
@ -444,12 +443,10 @@ void BKE_pbvh_update_vertex_data(PBVH *pbvh, int flags);
|
|||
void BKE_pbvh_update_visibility(PBVH *pbvh);
|
||||
void BKE_pbvh_update_normals(PBVH *pbvh, SubdivCCG *subdiv_ccg);
|
||||
void BKE_pbvh_redraw_BB(PBVH *pbvh, float bb_min[3], float bb_max[3]);
|
||||
blender::IndexMask BKE_pbvh_get_grid_updates(const PBVH *pbvh,
|
||||
blender::Span<const PBVHNode *> nodes,
|
||||
blender::IndexMaskMemory &memory);
|
||||
void BKE_pbvh_get_grid_updates(PBVH *pbvh, bool clear, void ***r_gridfaces, int *r_totface);
|
||||
void BKE_pbvh_grids_update(PBVH *pbvh,
|
||||
CCGElem **grids,
|
||||
blender::Span<int> gridfaces,
|
||||
void **gridfaces,
|
||||
DMFlagMat *flagmats,
|
||||
unsigned int **grid_hidden,
|
||||
CCGKey *key);
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_array.hh"
|
||||
#include "BLI_bitmap.h"
|
||||
#include "BLI_offset_indices.hh"
|
||||
#include "BLI_sys_types.h"
|
||||
|
@ -160,7 +159,7 @@ struct SubdivCCG {
|
|||
int num_faces;
|
||||
SubdivCCGFace *faces;
|
||||
/* Indexed by grid index, points to corresponding face from `faces`. */
|
||||
blender::Array<int> grid_faces;
|
||||
SubdivCCGFace **grid_faces;
|
||||
|
||||
/* Edges which are adjacent to faces.
|
||||
* Used for faster grid stitching, in the cost of extra memory.
|
||||
|
@ -236,14 +235,17 @@ void BKE_subdiv_ccg_key_top_level(CCGKey *key, const SubdivCCG *subdiv_ccg);
|
|||
void BKE_subdiv_ccg_recalc_normals(SubdivCCG *subdiv_ccg);
|
||||
|
||||
/* Update normals of affected faces. */
|
||||
void BKE_subdiv_ccg_update_normals(SubdivCCG *subdiv_ccg, const blender::IndexMask &face_mask);
|
||||
void BKE_subdiv_ccg_update_normals(SubdivCCG *subdiv_ccg,
|
||||
CCGFace **effected_faces,
|
||||
int num_effected_faces);
|
||||
|
||||
/* Average grid coordinates and normals along the grid boundaries. */
|
||||
void BKE_subdiv_ccg_average_grids(SubdivCCG *subdiv_ccg);
|
||||
|
||||
/* Similar to above, but only updates given faces. */
|
||||
void BKE_subdiv_ccg_average_stitch_faces(SubdivCCG *subdiv_ccg,
|
||||
const blender::IndexMask &face_mask);
|
||||
CCGFace **effected_faces,
|
||||
int num_effected_faces);
|
||||
|
||||
/* Get geometry counters at the current subdivision level. */
|
||||
void BKE_subdiv_ccg_topology_counters(const SubdivCCG *subdiv_ccg,
|
||||
|
|
|
@ -1226,11 +1226,12 @@ void multires_stitch_grids(Object *ob)
|
|||
/* NOTE: Currently CCG does not keep track of faces, making it impossible
|
||||
* to use BKE_pbvh_get_grid_updates().
|
||||
*/
|
||||
blender::IndexMaskMemory memory;
|
||||
blender::Vector<PBVHNode *> nodes = blender::bke::pbvh::search_gather(pbvh, nullptr, nullptr);
|
||||
const blender::IndexMask mask = BKE_pbvh_get_grid_updates(pbvh, nodes, memory);
|
||||
if (!mask.is_empty()) {
|
||||
BKE_subdiv_ccg_average_stitch_faces(subdiv_ccg, mask);
|
||||
CCGFace **faces;
|
||||
int num_faces;
|
||||
BKE_pbvh_get_grid_updates(pbvh, false, (void ***)&faces, &num_faces);
|
||||
if (num_faces) {
|
||||
BKE_subdiv_ccg_average_stitch_faces(subdiv_ccg, faces, num_faces);
|
||||
MEM_freeN(faces);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1293,14 +1293,6 @@ void BKE_paint_blend_read_lib(BlendLibReader *reader, Scene *sce, Paint *p)
|
|||
}
|
||||
}
|
||||
|
||||
bool paint_is_face_hidden(const int *looptri_faces, const bool *hide_poly, const int tri_index)
|
||||
{
|
||||
if (!hide_poly) {
|
||||
return false;
|
||||
}
|
||||
return hide_poly[looptri_faces[tri_index]];
|
||||
}
|
||||
|
||||
bool paint_is_grid_face_hidden(const uint *grid_hidden, int gridsize, int x, int y)
|
||||
{
|
||||
/* Skip face if any of its corners are hidden. */
|
||||
|
@ -2260,7 +2252,7 @@ static PBVH *build_pbvh_from_ccg(Object *ob, SubdivCCG *subdiv_ccg)
|
|||
subdiv_ccg->grids,
|
||||
subdiv_ccg->num_grids,
|
||||
&key,
|
||||
subdiv_ccg->grid_faces,
|
||||
(void **)subdiv_ccg->grid_faces,
|
||||
subdiv_ccg->grid_flag_mats,
|
||||
subdiv_ccg->grid_hidden,
|
||||
base_mesh,
|
||||
|
@ -2349,7 +2341,7 @@ void BKE_sculpt_bvh_update_from_ccg(PBVH *pbvh, SubdivCCG *subdiv_ccg)
|
|||
|
||||
BKE_pbvh_grids_update(pbvh,
|
||||
subdiv_ccg->grids,
|
||||
subdiv_ccg->grid_faces,
|
||||
(void **)subdiv_ccg->grid_faces,
|
||||
subdiv_ccg->grid_flag_mats,
|
||||
subdiv_ccg->grid_hidden,
|
||||
&key);
|
||||
|
|
|
@ -318,29 +318,20 @@ static int map_insert_vert(
|
|||
/* Find vertices used by the faces in this node and update the draw buffers */
|
||||
static void build_mesh_leaf_node(PBVH *pbvh, PBVHNode *node)
|
||||
{
|
||||
bool has_visible = false;
|
||||
|
||||
node->uniq_verts = node->face_verts = 0;
|
||||
const int totface = node->prim_indices.size();
|
||||
const Span<int> prim_indices = node->prim_indices;
|
||||
|
||||
/* reserve size is rough guess */
|
||||
blender::Map<int, int> map;
|
||||
map.reserve(totface);
|
||||
map.reserve(prim_indices.size());
|
||||
|
||||
node->face_vert_indices.reinitialize(totface);
|
||||
node->face_vert_indices.reinitialize(prim_indices.size());
|
||||
|
||||
for (int i = 0; i < totface; i++) {
|
||||
const MLoopTri *lt = &pbvh->looptri[node->prim_indices[i]];
|
||||
for (const int i : prim_indices.index_range()) {
|
||||
const MLoopTri &tri = pbvh->looptri[prim_indices[i]];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
node->face_vert_indices[i][j] = map_insert_vert(
|
||||
pbvh, map, &node->face_verts, &node->uniq_verts, pbvh->corner_verts[lt->tri[j]]);
|
||||
}
|
||||
|
||||
if (has_visible == false) {
|
||||
if (!paint_is_face_hidden(
|
||||
pbvh->looptri_faces.data(), pbvh->hide_poly, node->prim_indices[i])) {
|
||||
has_visible = true;
|
||||
}
|
||||
pbvh, map, &node->face_verts, &node->uniq_verts, pbvh->corner_verts[tri.tri[j]]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -356,19 +347,22 @@ static void build_mesh_leaf_node(PBVH *pbvh, PBVHNode *node)
|
|||
node->vert_indices[value] = item.key;
|
||||
}
|
||||
|
||||
for (int i = 0; i < totface; i++) {
|
||||
const int sides = 3;
|
||||
|
||||
for (int j = 0; j < sides; j++) {
|
||||
for (const int i : prim_indices.index_range()) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (node->face_vert_indices[i][j] < 0) {
|
||||
node->face_vert_indices[i][j] = -node->face_vert_indices[i][j] + node->uniq_verts - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool fully_hidden = pbvh->hide_poly &&
|
||||
std::all_of(
|
||||
prim_indices.begin(), prim_indices.end(), [&](const int tri) {
|
||||
const int face = pbvh->looptri_faces[tri];
|
||||
return pbvh->hide_poly[face];
|
||||
});
|
||||
BKE_pbvh_node_fully_hidden_set(node, fully_hidden);
|
||||
BKE_pbvh_node_mark_rebuild_draw(node);
|
||||
|
||||
BKE_pbvh_node_fully_hidden_set(node, !has_visible);
|
||||
}
|
||||
|
||||
static void update_vb(PBVH *pbvh, PBVHNode *node, const Span<BBC> prim_bbc, int offset, int count)
|
||||
|
@ -927,7 +921,7 @@ void BKE_pbvh_build_grids(PBVH *pbvh,
|
|||
CCGElem **grids,
|
||||
int totgrid,
|
||||
CCGKey *key,
|
||||
blender::Span<int> gridfaces,
|
||||
void **gridfaces,
|
||||
DMFlagMat *flagmats,
|
||||
BLI_bitmap **grid_hidden,
|
||||
Mesh *me,
|
||||
|
@ -1742,21 +1736,49 @@ void BKE_pbvh_redraw_BB(PBVH *pbvh, float bb_min[3], float bb_max[3])
|
|||
copy_v3_v3(bb_max, bb.bmax);
|
||||
}
|
||||
|
||||
blender::IndexMask BKE_pbvh_get_grid_updates(const PBVH *pbvh,
|
||||
const Span<const PBVHNode *> nodes,
|
||||
blender::IndexMaskMemory &memory)
|
||||
void BKE_pbvh_get_grid_updates(PBVH *pbvh, bool clear, void ***r_gridfaces, int *r_totface)
|
||||
{
|
||||
using namespace blender;
|
||||
Array<bool> faces_to_update(pbvh->faces_num, false);
|
||||
threading::parallel_for(nodes.index_range(), 1, [&](const IndexRange range) {
|
||||
for (const PBVHNode *node : nodes.slice(range)) {
|
||||
GSet *face_set = BLI_gset_ptr_new(__func__);
|
||||
PBVHNode *node;
|
||||
PBVHIter iter;
|
||||
|
||||
pbvh_iter_begin(&iter, pbvh, nullptr, nullptr);
|
||||
|
||||
while ((node = pbvh_iter_next(&iter, PBVH_Leaf))) {
|
||||
if (node->flag & PBVH_UpdateNormals) {
|
||||
for (const int grid : node->prim_indices) {
|
||||
const int face = pbvh->gridfaces[grid];
|
||||
faces_to_update[face] = true;
|
||||
void *face = pbvh->gridfaces[grid];
|
||||
BLI_gset_add(face_set, face);
|
||||
}
|
||||
|
||||
if (clear) {
|
||||
node->flag &= ~PBVH_UpdateNormals;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return IndexMask::from_bools(faces_to_update, memory);
|
||||
|
||||
pbvh_iter_end(&iter);
|
||||
|
||||
const int tot = BLI_gset_len(face_set);
|
||||
if (tot == 0) {
|
||||
*r_totface = 0;
|
||||
*r_gridfaces = nullptr;
|
||||
BLI_gset_free(face_set, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
void **faces = static_cast<void **>(MEM_mallocN(sizeof(*faces) * tot, __func__));
|
||||
|
||||
GSetIterator gs_iter;
|
||||
int i;
|
||||
GSET_ITER_INDEX (gs_iter, face_set, i) {
|
||||
faces[i] = BLI_gsetIterator_getKey(&gs_iter);
|
||||
}
|
||||
|
||||
BLI_gset_free(face_set, nullptr);
|
||||
|
||||
*r_totface = tot;
|
||||
*r_gridfaces = faces;
|
||||
}
|
||||
|
||||
/***************************** PBVH Access ***********************************/
|
||||
|
@ -2280,7 +2302,7 @@ static bool pbvh_faces_node_raycast(PBVH *pbvh,
|
|||
const MLoopTri *lt = &pbvh->looptri[looptri_i];
|
||||
const blender::int3 face_verts = node->face_vert_indices[i];
|
||||
|
||||
if (paint_is_face_hidden(pbvh->looptri_faces.data(), pbvh->hide_poly, looptri_i)) {
|
||||
if (pbvh->hide_poly && pbvh->hide_poly[pbvh->looptri_faces[looptri_i]]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -2627,7 +2649,7 @@ static bool pbvh_faces_node_nearest_to_ray(PBVH *pbvh,
|
|||
const MLoopTri *lt = &pbvh->looptri[looptri_i];
|
||||
const blender::int3 face_verts = node->face_vert_indices[i];
|
||||
|
||||
if (paint_is_face_hidden(pbvh->looptri_faces.data(), pbvh->hide_poly, looptri_i)) {
|
||||
if (pbvh->hide_poly && pbvh->hide_poly[pbvh->looptri_faces[looptri_i]]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -2817,7 +2839,6 @@ bool BKE_pbvh_node_frustum_exclude_AABB(PBVHNode *node, void *data)
|
|||
|
||||
void BKE_pbvh_update_normals(PBVH *pbvh, SubdivCCG *subdiv_ccg)
|
||||
{
|
||||
using namespace blender;
|
||||
/* Update normals */
|
||||
Vector<PBVHNode *> nodes = blender::bke::pbvh::search_gather(
|
||||
pbvh, update_search_cb, POINTER_FROM_INT(PBVH_UpdateNormals));
|
||||
|
@ -2830,11 +2851,12 @@ void BKE_pbvh_update_normals(PBVH *pbvh, SubdivCCG *subdiv_ccg)
|
|||
pbvh_faces_update_normals(pbvh, nodes);
|
||||
}
|
||||
else if (pbvh->header.type == PBVH_GRIDS) {
|
||||
IndexMaskMemory memory;
|
||||
const IndexMask faces_to_update = BKE_pbvh_get_grid_updates(pbvh, nodes, memory);
|
||||
BKE_subdiv_ccg_update_normals(subdiv_ccg, faces_to_update);
|
||||
for (PBVHNode *node : nodes) {
|
||||
node->flag &= ~PBVH_UpdateNormals;
|
||||
CCGFace **faces;
|
||||
int num_faces;
|
||||
BKE_pbvh_get_grid_updates(pbvh, true, (void ***)&faces, &num_faces);
|
||||
if (num_faces > 0) {
|
||||
BKE_subdiv_ccg_update_normals(subdiv_ccg, faces, num_faces);
|
||||
MEM_freeN(faces);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2953,7 +2975,7 @@ void BKE_pbvh_draw_debug_cb(PBVH *pbvh,
|
|||
|
||||
void BKE_pbvh_grids_update(PBVH *pbvh,
|
||||
CCGElem **grids,
|
||||
blender::Span<int> gridfaces,
|
||||
void **gridfaces,
|
||||
DMFlagMat *flagmats,
|
||||
BLI_bitmap **grid_hidden,
|
||||
CCGKey *key)
|
||||
|
|
|
@ -176,7 +176,7 @@ struct PBVH {
|
|||
/* Grid Data */
|
||||
CCGKey gridkey;
|
||||
CCGElem **grids;
|
||||
blender::Span<int> gridfaces;
|
||||
void **gridfaces;
|
||||
const DMFlagMat *grid_flag_mats;
|
||||
int totgrid;
|
||||
BLI_bitmap **grid_hidden;
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_enumerable_thread_specific.hh"
|
||||
#include "BLI_ghash.h"
|
||||
#include "BLI_math_bits.h"
|
||||
#include "BLI_math_geom.h"
|
||||
|
@ -41,7 +40,8 @@ static void subdiv_ccg_average_inner_face_grids(SubdivCCG *subdiv_ccg,
|
|||
|
||||
void subdiv_ccg_average_faces_boundaries_and_corners(SubdivCCG *subdiv_ccg,
|
||||
CCGKey *key,
|
||||
const blender::IndexMask &face_mask);
|
||||
CCGFace **effected_faces,
|
||||
int num_effected_faces);
|
||||
|
||||
/** \} */
|
||||
|
||||
|
@ -152,7 +152,8 @@ static void subdiv_ccg_alloc_elements(SubdivCCG *subdiv_ccg, Subdiv *subdiv)
|
|||
if (num_faces) {
|
||||
subdiv_ccg->faces = static_cast<SubdivCCGFace *>(
|
||||
MEM_calloc_arrayN(num_faces, sizeof(SubdivCCGFace), "Subdiv CCG faces"));
|
||||
subdiv_ccg->grid_faces.reinitialize(num_grids);
|
||||
subdiv_ccg->grid_faces = static_cast<SubdivCCGFace **>(
|
||||
MEM_calloc_arrayN(num_grids, sizeof(SubdivCCGFace *), "Subdiv CCG grid faces"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,7 +232,7 @@ static void subdiv_ccg_eval_regular_grid(CCGEvalGridsData *data, const int face_
|
|||
const float grid_size_1_inv = 1.0f / (grid_size - 1);
|
||||
const int element_size = element_size_bytes_get(subdiv_ccg);
|
||||
SubdivCCGFace *faces = subdiv_ccg->faces;
|
||||
blender::MutableSpan<int> grid_faces = subdiv_ccg->grid_faces;
|
||||
SubdivCCGFace **grid_faces = subdiv_ccg->grid_faces;
|
||||
const SubdivCCGFace *face = &faces[face_index];
|
||||
for (int corner = 0; corner < face->num_grids; corner++) {
|
||||
const int grid_index = face->start_grid_index + corner;
|
||||
|
@ -248,7 +249,7 @@ static void subdiv_ccg_eval_regular_grid(CCGEvalGridsData *data, const int face_
|
|||
}
|
||||
}
|
||||
/* Assign grid's face. */
|
||||
grid_faces[grid_index] = face_index;
|
||||
grid_faces[grid_index] = &faces[face_index];
|
||||
/* Assign material flags. */
|
||||
subdiv_ccg->grid_flag_mats[grid_index] = data->material_flags_evaluator->eval_material_flags(
|
||||
data->material_flags_evaluator, face_index);
|
||||
|
@ -262,7 +263,7 @@ static void subdiv_ccg_eval_special_grid(CCGEvalGridsData *data, const int face_
|
|||
const float grid_size_1_inv = 1.0f / (grid_size - 1);
|
||||
const int element_size = element_size_bytes_get(subdiv_ccg);
|
||||
SubdivCCGFace *faces = subdiv_ccg->faces;
|
||||
blender::MutableSpan<int> grid_faces = subdiv_ccg->grid_faces;
|
||||
SubdivCCGFace **grid_faces = subdiv_ccg->grid_faces;
|
||||
const SubdivCCGFace *face = &faces[face_index];
|
||||
for (int corner = 0; corner < face->num_grids; corner++) {
|
||||
const int grid_index = face->start_grid_index + corner;
|
||||
|
@ -278,7 +279,7 @@ static void subdiv_ccg_eval_special_grid(CCGEvalGridsData *data, const int face_
|
|||
}
|
||||
}
|
||||
/* Assign grid's face. */
|
||||
grid_faces[grid_index] = face_index;
|
||||
grid_faces[grid_index] = &faces[face_index];
|
||||
/* Assign material flags. */
|
||||
subdiv_ccg->grid_flag_mats[grid_index] = data->material_flags_evaluator->eval_material_flags(
|
||||
data->material_flags_evaluator, face_index);
|
||||
|
@ -572,7 +573,7 @@ SubdivCCG *BKE_subdiv_to_ccg(Subdiv *subdiv,
|
|||
SubdivCCGMaterialFlagsEvaluator *material_flags_evaluator)
|
||||
{
|
||||
BKE_subdiv_stats_begin(&subdiv->stats, SUBDIV_STATS_SUBDIV_TO_CCG);
|
||||
SubdivCCG *subdiv_ccg = MEM_new<SubdivCCG>(__func__);
|
||||
SubdivCCG *subdiv_ccg = MEM_cnew<SubdivCCG>(__func__);
|
||||
subdiv_ccg->subdiv = subdiv;
|
||||
subdiv_ccg->level = bitscan_forward_i(settings->resolution - 1);
|
||||
subdiv_ccg->grid_size = BKE_subdiv_grid_size_from_level(subdiv_ccg->level);
|
||||
|
@ -639,6 +640,7 @@ void BKE_subdiv_ccg_destroy(SubdivCCG *subdiv_ccg)
|
|||
BKE_subdiv_free(subdiv_ccg->subdiv);
|
||||
}
|
||||
MEM_SAFE_FREE(subdiv_ccg->faces);
|
||||
MEM_SAFE_FREE(subdiv_ccg->grid_faces);
|
||||
/* Free map of adjacent edges. */
|
||||
for (int i = 0; i < subdiv_ccg->num_adjacent_edges; i++) {
|
||||
SubdivCCGAdjacentEdge *adjacent_edge = &subdiv_ccg->adjacent_edges[i];
|
||||
|
@ -655,7 +657,7 @@ void BKE_subdiv_ccg_destroy(SubdivCCG *subdiv_ccg)
|
|||
}
|
||||
MEM_SAFE_FREE(subdiv_ccg->adjacent_vertices);
|
||||
MEM_SAFE_FREE(subdiv_ccg->cache_.start_face_grid_index);
|
||||
MEM_delete(subdiv_ccg);
|
||||
MEM_freeN(subdiv_ccg);
|
||||
}
|
||||
|
||||
void BKE_subdiv_ccg_key(CCGKey *key, const SubdivCCG *subdiv_ccg, int level)
|
||||
|
@ -684,20 +686,32 @@ void BKE_subdiv_ccg_key_top_level(CCGKey *key, const SubdivCCG *subdiv_ccg)
|
|||
/** \name Normals
|
||||
* \{ */
|
||||
|
||||
struct RecalcInnerNormalsData {
|
||||
SubdivCCG *subdiv_ccg;
|
||||
CCGKey *key;
|
||||
};
|
||||
|
||||
struct RecalcInnerNormalsTLSData {
|
||||
float (*face_normals)[3];
|
||||
};
|
||||
|
||||
/* Evaluate high-res face normals, for faces which corresponds to grid elements
|
||||
*
|
||||
* {(x, y), {x + 1, y}, {x + 1, y + 1}, {x, y + 1}}
|
||||
*
|
||||
* The result is stored in normals storage from TLS. */
|
||||
static void subdiv_ccg_recalc_inner_face_normals(
|
||||
SubdivCCG *subdiv_ccg,
|
||||
CCGKey *key,
|
||||
blender::MutableSpan<blender::float3> face_normals,
|
||||
const int grid_index)
|
||||
static void subdiv_ccg_recalc_inner_face_normals(SubdivCCG *subdiv_ccg,
|
||||
CCGKey *key,
|
||||
RecalcInnerNormalsTLSData *tls,
|
||||
const int grid_index)
|
||||
{
|
||||
const int grid_size = subdiv_ccg->grid_size;
|
||||
const int grid_size_1 = grid_size - 1;
|
||||
CCGElem *grid = subdiv_ccg->grids[grid_index];
|
||||
if (tls->face_normals == nullptr) {
|
||||
tls->face_normals = static_cast<float(*)[3]>(
|
||||
MEM_malloc_arrayN(grid_size_1 * grid_size_1, sizeof(float[3]), "CCG TLS normals"));
|
||||
}
|
||||
for (int y = 0; y < grid_size - 1; y++) {
|
||||
for (int x = 0; x < grid_size - 1; x++) {
|
||||
CCGElem *grid_elements[4] = {
|
||||
|
@ -713,22 +727,22 @@ static void subdiv_ccg_recalc_inner_face_normals(
|
|||
CCG_elem_co(key, grid_elements[3]),
|
||||
};
|
||||
const int face_index = y * grid_size_1 + x;
|
||||
float *face_normal = face_normals[face_index];
|
||||
float *face_normal = tls->face_normals[face_index];
|
||||
normal_quad_v3(face_normal, co[0], co[1], co[2], co[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Average normals at every grid element, using adjacent faces normals. */
|
||||
static void subdiv_ccg_average_inner_face_normals(
|
||||
SubdivCCG *subdiv_ccg,
|
||||
CCGKey *key,
|
||||
const blender::Span<blender::float3> face_normals,
|
||||
const int grid_index)
|
||||
static void subdiv_ccg_average_inner_face_normals(SubdivCCG *subdiv_ccg,
|
||||
CCGKey *key,
|
||||
RecalcInnerNormalsTLSData *tls,
|
||||
const int grid_index)
|
||||
{
|
||||
const int grid_size = subdiv_ccg->grid_size;
|
||||
const int grid_size_1 = grid_size - 1;
|
||||
CCGElem *grid = subdiv_ccg->grids[grid_index];
|
||||
const float(*face_normals)[3] = tls->face_normals;
|
||||
for (int y = 0; y < grid_size; y++) {
|
||||
for (int x = 0; x < grid_size; x++) {
|
||||
float normal_acc[3] = {0.0f, 0.0f, 0.0f};
|
||||
|
@ -758,25 +772,42 @@ static void subdiv_ccg_average_inner_face_normals(
|
|||
}
|
||||
}
|
||||
|
||||
static void subdiv_ccg_recalc_inner_normal_task(void *__restrict userdata_v,
|
||||
const int grid_index,
|
||||
const TaskParallelTLS *__restrict tls_v)
|
||||
{
|
||||
RecalcInnerNormalsData *data = static_cast<RecalcInnerNormalsData *>(userdata_v);
|
||||
RecalcInnerNormalsTLSData *tls = static_cast<RecalcInnerNormalsTLSData *>(tls_v->userdata_chunk);
|
||||
subdiv_ccg_recalc_inner_face_normals(data->subdiv_ccg, data->key, tls, grid_index);
|
||||
subdiv_ccg_average_inner_face_normals(data->subdiv_ccg, data->key, tls, grid_index);
|
||||
}
|
||||
|
||||
static void subdiv_ccg_recalc_inner_normal_free(const void *__restrict /*userdata*/,
|
||||
void *__restrict tls_v)
|
||||
{
|
||||
RecalcInnerNormalsTLSData *tls = static_cast<RecalcInnerNormalsTLSData *>(tls_v);
|
||||
MEM_SAFE_FREE(tls->face_normals);
|
||||
}
|
||||
|
||||
/* Recalculate normals which corresponds to non-boundaries elements of grids. */
|
||||
static void subdiv_ccg_recalc_inner_grid_normals(SubdivCCG *subdiv_ccg)
|
||||
{
|
||||
using namespace blender;
|
||||
const int grid_size_1 = subdiv_ccg->grid_size - 1;
|
||||
|
||||
threading::EnumerableThreadSpecific<Array<float3>> face_normals_tls(
|
||||
[&]() { return Array<float3>(grid_size_1 * grid_size_1); });
|
||||
|
||||
CCGKey key;
|
||||
BKE_subdiv_ccg_key_top_level(&key, subdiv_ccg);
|
||||
|
||||
threading::parallel_for(IndexRange(subdiv_ccg->num_grids), 512, [&](const IndexRange range) {
|
||||
MutableSpan<float3> face_normals = face_normals_tls.local();
|
||||
for (const int grid_index : range) {
|
||||
subdiv_ccg_recalc_inner_face_normals(subdiv_ccg, &key, face_normals, grid_index);
|
||||
subdiv_ccg_average_inner_face_normals(subdiv_ccg, &key, face_normals, grid_index);
|
||||
}
|
||||
});
|
||||
RecalcInnerNormalsData data{};
|
||||
data.subdiv_ccg = subdiv_ccg;
|
||||
data.key = &key;
|
||||
RecalcInnerNormalsTLSData tls_data = {nullptr};
|
||||
TaskParallelSettings parallel_range_settings;
|
||||
BLI_parallel_range_settings_defaults(¶llel_range_settings);
|
||||
parallel_range_settings.userdata_chunk = &tls_data;
|
||||
parallel_range_settings.userdata_chunk_size = sizeof(tls_data);
|
||||
parallel_range_settings.func_free = subdiv_ccg_recalc_inner_normal_free;
|
||||
BLI_task_parallel_range(0,
|
||||
subdiv_ccg->num_grids,
|
||||
&data,
|
||||
subdiv_ccg_recalc_inner_normal_task,
|
||||
¶llel_range_settings);
|
||||
}
|
||||
|
||||
void BKE_subdiv_ccg_recalc_normals(SubdivCCG *subdiv_ccg)
|
||||
|
@ -789,48 +820,80 @@ void BKE_subdiv_ccg_recalc_normals(SubdivCCG *subdiv_ccg)
|
|||
BKE_subdiv_ccg_average_grids(subdiv_ccg);
|
||||
}
|
||||
|
||||
static void subdiv_ccg_recalc_modified_inner_grid_normals(SubdivCCG *subdiv_ccg,
|
||||
const blender::IndexMask &face_mask)
|
||||
struct RecalcModifiedInnerNormalsData {
|
||||
SubdivCCG *subdiv_ccg;
|
||||
CCGKey *key;
|
||||
SubdivCCGFace **effected_ccg_faces;
|
||||
};
|
||||
|
||||
static void subdiv_ccg_recalc_modified_inner_normal_task(void *__restrict userdata_v,
|
||||
const int face_index,
|
||||
const TaskParallelTLS *__restrict tls_v)
|
||||
{
|
||||
using namespace blender;
|
||||
const int grid_size_1 = subdiv_ccg->grid_size - 1;
|
||||
|
||||
threading::EnumerableThreadSpecific<Array<float3>> face_normals_tls(
|
||||
[&]() { return Array<float3>(grid_size_1 * grid_size_1); });
|
||||
|
||||
CCGKey key;
|
||||
BKE_subdiv_ccg_key_top_level(&key, subdiv_ccg);
|
||||
const SubdivCCGFace *faces = subdiv_ccg->faces;
|
||||
face_mask.foreach_segment(GrainSize(512), [&](const IndexMaskSegment segment) {
|
||||
MutableSpan<float3> face_normals = face_normals_tls.local();
|
||||
for (const int face_index : segment) {
|
||||
const SubdivCCGFace &face = faces[face_index];
|
||||
const int num_face_grids = face.num_grids;
|
||||
for (int i = 0; i < num_face_grids; i++) {
|
||||
const int grid_index = face.start_grid_index + i;
|
||||
subdiv_ccg_recalc_inner_face_normals(subdiv_ccg, &key, face_normals, grid_index);
|
||||
subdiv_ccg_average_inner_face_normals(subdiv_ccg, &key, face_normals, grid_index);
|
||||
}
|
||||
}
|
||||
});
|
||||
RecalcModifiedInnerNormalsData *data = static_cast<RecalcModifiedInnerNormalsData *>(userdata_v);
|
||||
SubdivCCG *subdiv_ccg = data->subdiv_ccg;
|
||||
CCGKey *key = data->key;
|
||||
RecalcInnerNormalsTLSData *tls = static_cast<RecalcInnerNormalsTLSData *>(tls_v->userdata_chunk);
|
||||
SubdivCCGFace **faces = data->effected_ccg_faces;
|
||||
SubdivCCGFace *face = faces[face_index];
|
||||
const int num_face_grids = face->num_grids;
|
||||
for (int i = 0; i < num_face_grids; i++) {
|
||||
const int grid_index = face->start_grid_index + i;
|
||||
subdiv_ccg_recalc_inner_face_normals(data->subdiv_ccg, data->key, tls, grid_index);
|
||||
subdiv_ccg_average_inner_face_normals(data->subdiv_ccg, data->key, tls, grid_index);
|
||||
}
|
||||
subdiv_ccg_average_inner_face_grids(subdiv_ccg, key, face);
|
||||
}
|
||||
|
||||
void BKE_subdiv_ccg_update_normals(SubdivCCG *subdiv_ccg, const blender::IndexMask &face_mask)
|
||||
static void subdiv_ccg_recalc_modified_inner_normal_free(const void *__restrict /*userdata*/,
|
||||
void *__restrict tls_v)
|
||||
{
|
||||
RecalcInnerNormalsTLSData *tls = static_cast<RecalcInnerNormalsTLSData *>(tls_v);
|
||||
MEM_SAFE_FREE(tls->face_normals);
|
||||
}
|
||||
|
||||
static void subdiv_ccg_recalc_modified_inner_grid_normals(SubdivCCG *subdiv_ccg,
|
||||
CCGFace **effected_faces,
|
||||
int num_effected_faces)
|
||||
{
|
||||
CCGKey key;
|
||||
BKE_subdiv_ccg_key_top_level(&key, subdiv_ccg);
|
||||
RecalcModifiedInnerNormalsData data{};
|
||||
data.subdiv_ccg = subdiv_ccg;
|
||||
data.key = &key;
|
||||
data.effected_ccg_faces = (SubdivCCGFace **)effected_faces;
|
||||
RecalcInnerNormalsTLSData tls_data = {nullptr};
|
||||
TaskParallelSettings parallel_range_settings;
|
||||
BLI_parallel_range_settings_defaults(¶llel_range_settings);
|
||||
parallel_range_settings.userdata_chunk = &tls_data;
|
||||
parallel_range_settings.userdata_chunk_size = sizeof(tls_data);
|
||||
parallel_range_settings.func_free = subdiv_ccg_recalc_modified_inner_normal_free;
|
||||
BLI_task_parallel_range(0,
|
||||
num_effected_faces,
|
||||
&data,
|
||||
subdiv_ccg_recalc_modified_inner_normal_task,
|
||||
¶llel_range_settings);
|
||||
}
|
||||
|
||||
void BKE_subdiv_ccg_update_normals(SubdivCCG *subdiv_ccg,
|
||||
CCGFace **effected_faces,
|
||||
int num_effected_faces)
|
||||
{
|
||||
if (!subdiv_ccg->has_normal) {
|
||||
/* Grids don't have normals, can do early output. */
|
||||
return;
|
||||
}
|
||||
if (face_mask.is_empty()) {
|
||||
if (num_effected_faces == 0) {
|
||||
/* No faces changed, so nothing to do here. */
|
||||
return;
|
||||
}
|
||||
subdiv_ccg_recalc_modified_inner_grid_normals(subdiv_ccg, face_mask);
|
||||
subdiv_ccg_recalc_modified_inner_grid_normals(subdiv_ccg, effected_faces, num_effected_faces);
|
||||
|
||||
CCGKey key;
|
||||
BKE_subdiv_ccg_key_top_level(&key, subdiv_ccg);
|
||||
|
||||
subdiv_ccg_average_faces_boundaries_and_corners(subdiv_ccg, &key, face_mask);
|
||||
subdiv_ccg_average_faces_boundaries_and_corners(
|
||||
subdiv_ccg, &key, effected_faces, num_effected_faces);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
@ -1164,7 +1227,8 @@ void BKE_subdiv_ccg_average_grids(SubdivCCG *subdiv_ccg)
|
|||
}
|
||||
|
||||
static void subdiv_ccg_affected_face_adjacency(SubdivCCG *subdiv_ccg,
|
||||
const blender::IndexMask &face_mask,
|
||||
CCGFace **effected_faces,
|
||||
int num_effected_faces,
|
||||
GSet *r_adjacent_vertices,
|
||||
GSet *r_adjacent_edges)
|
||||
{
|
||||
|
@ -1177,8 +1241,9 @@ static void subdiv_ccg_affected_face_adjacency(SubdivCCG *subdiv_ccg,
|
|||
static_or_heap_storage_init(&face_vertices_storage);
|
||||
static_or_heap_storage_init(&face_edges_storage);
|
||||
|
||||
face_mask.foreach_index([&](const int face_index) {
|
||||
SubdivCCGFace *face = &subdiv_ccg->faces[face_index];
|
||||
for (int i = 0; i < num_effected_faces; i++) {
|
||||
SubdivCCGFace *face = (SubdivCCGFace *)effected_faces[i];
|
||||
int face_index = face - subdiv_ccg->faces;
|
||||
const int num_face_grids = face->num_grids;
|
||||
const int num_face_edges = num_face_grids;
|
||||
int *face_vertices = static_or_heap_storage_get(&face_vertices_storage, num_face_edges);
|
||||
|
@ -1201,7 +1266,7 @@ static void subdiv_ccg_affected_face_adjacency(SubdivCCG *subdiv_ccg,
|
|||
SubdivCCGAdjacentVertex *adjacent_vertex = &subdiv_ccg->adjacent_vertices[vertex_index];
|
||||
BLI_gset_add(r_adjacent_vertices, adjacent_vertex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static_or_heap_storage_free(&face_vertices_storage);
|
||||
static_or_heap_storage_free(&face_edges_storage);
|
||||
|
@ -1209,13 +1274,15 @@ static void subdiv_ccg_affected_face_adjacency(SubdivCCG *subdiv_ccg,
|
|||
|
||||
void subdiv_ccg_average_faces_boundaries_and_corners(SubdivCCG *subdiv_ccg,
|
||||
CCGKey *key,
|
||||
const blender::IndexMask &face_mask)
|
||||
CCGFace **effected_faces,
|
||||
int num_effected_faces)
|
||||
{
|
||||
GSet *adjacent_vertices = BLI_gset_ptr_new(__func__);
|
||||
GSet *adjacent_edges = BLI_gset_ptr_new(__func__);
|
||||
GSetIterator gi;
|
||||
|
||||
subdiv_ccg_affected_face_adjacency(subdiv_ccg, face_mask, adjacent_vertices, adjacent_edges);
|
||||
subdiv_ccg_affected_face_adjacency(
|
||||
subdiv_ccg, effected_faces, num_effected_faces, adjacent_vertices, adjacent_edges);
|
||||
|
||||
int *adjacent_vertex_index_map;
|
||||
int *adjacent_edge_index_map;
|
||||
|
@ -1253,17 +1320,42 @@ void subdiv_ccg_average_faces_boundaries_and_corners(SubdivCCG *subdiv_ccg,
|
|||
static_or_heap_storage_free(&index_heap);
|
||||
}
|
||||
|
||||
void BKE_subdiv_ccg_average_stitch_faces(SubdivCCG *subdiv_ccg,
|
||||
const blender::IndexMask &face_mask)
|
||||
struct StitchFacesInnerGridsData {
|
||||
SubdivCCG *subdiv_ccg;
|
||||
CCGKey *key;
|
||||
CCGFace **effected_ccg_faces;
|
||||
};
|
||||
|
||||
static void subdiv_ccg_stitch_face_inner_grids_task(void *__restrict userdata_v,
|
||||
const int face_index,
|
||||
const TaskParallelTLS *__restrict /*tls_v*/)
|
||||
{
|
||||
StitchFacesInnerGridsData *data = static_cast<StitchFacesInnerGridsData *>(userdata_v);
|
||||
SubdivCCG *subdiv_ccg = data->subdiv_ccg;
|
||||
CCGKey *key = data->key;
|
||||
CCGFace **effected_ccg_faces = data->effected_ccg_faces;
|
||||
CCGFace *effected_ccg_face = effected_ccg_faces[face_index];
|
||||
SubdivCCGFace *face = (SubdivCCGFace *)effected_ccg_face;
|
||||
subdiv_ccg_average_inner_face_grids(subdiv_ccg, key, face);
|
||||
}
|
||||
|
||||
void BKE_subdiv_ccg_average_stitch_faces(SubdivCCG *subdiv_ccg,
|
||||
CCGFace **effected_faces,
|
||||
int num_effected_faces)
|
||||
{
|
||||
using namespace blender;
|
||||
CCGKey key;
|
||||
BKE_subdiv_ccg_key_top_level(&key, subdiv_ccg);
|
||||
|
||||
face_mask.foreach_index(GrainSize(512), [&](const int face_index) {
|
||||
subdiv_ccg_average_inner_face_grids(subdiv_ccg, &key, &subdiv_ccg->faces[face_index]);
|
||||
});
|
||||
|
||||
StitchFacesInnerGridsData data{};
|
||||
data.subdiv_ccg = subdiv_ccg;
|
||||
data.key = &key;
|
||||
data.effected_ccg_faces = effected_faces;
|
||||
TaskParallelSettings parallel_range_settings;
|
||||
BLI_parallel_range_settings_defaults(¶llel_range_settings);
|
||||
BLI_task_parallel_range(0,
|
||||
num_effected_faces,
|
||||
&data,
|
||||
subdiv_ccg_stitch_face_inner_grids_task,
|
||||
¶llel_range_settings);
|
||||
/* TODO(sergey): Only average elements which are adjacent to modified
|
||||
* faces. */
|
||||
subdiv_ccg_average_all_boundaries_and_corners(subdiv_ccg, &key);
|
||||
|
@ -1421,7 +1513,7 @@ static SubdivCCGCoord coord_step_inside_from_boundary(const SubdivCCG *subdiv_cc
|
|||
BLI_INLINE
|
||||
int next_grid_index_from_coord(const SubdivCCG *subdiv_ccg, const SubdivCCGCoord *coord)
|
||||
{
|
||||
SubdivCCGFace *face = &subdiv_ccg->faces[subdiv_ccg->grid_faces[coord->grid_index]];
|
||||
SubdivCCGFace *face = subdiv_ccg->grid_faces[coord->grid_index];
|
||||
const int face_grid_index = coord->grid_index;
|
||||
int next_face_grid_index = face_grid_index + 1 - face->start_grid_index;
|
||||
if (next_face_grid_index == face->num_grids) {
|
||||
|
@ -1431,7 +1523,7 @@ int next_grid_index_from_coord(const SubdivCCG *subdiv_ccg, const SubdivCCGCoord
|
|||
}
|
||||
BLI_INLINE int prev_grid_index_from_coord(const SubdivCCG *subdiv_ccg, const SubdivCCGCoord *coord)
|
||||
{
|
||||
SubdivCCGFace *face = &subdiv_ccg->faces[subdiv_ccg->grid_faces[coord->grid_index]];
|
||||
SubdivCCGFace *face = subdiv_ccg->grid_faces[coord->grid_index];
|
||||
const int face_grid_index = coord->grid_index;
|
||||
int prev_face_grid_index = face_grid_index - 1 - face->start_grid_index;
|
||||
if (prev_face_grid_index < 0) {
|
||||
|
@ -1447,7 +1539,7 @@ static void neighbor_coords_corner_center_get(const SubdivCCG *subdiv_ccg,
|
|||
const bool include_duplicates,
|
||||
SubdivCCGNeighbors *r_neighbors)
|
||||
{
|
||||
const SubdivCCGFace *face = &subdiv_ccg->faces[subdiv_ccg->grid_faces[coord->grid_index]];
|
||||
SubdivCCGFace *face = subdiv_ccg->grid_faces[coord->grid_index];
|
||||
const int num_adjacent_grids = face->num_grids;
|
||||
|
||||
subdiv_ccg_neighbors_init(
|
||||
|
@ -1475,7 +1567,7 @@ static int adjacent_vertex_index_from_coord(const SubdivCCG *subdiv_ccg,
|
|||
Subdiv *subdiv = subdiv_ccg->subdiv;
|
||||
OpenSubdiv_TopologyRefiner *topology_refiner = subdiv->topology_refiner;
|
||||
|
||||
const SubdivCCGFace *face = &subdiv_ccg->faces[subdiv_ccg->grid_faces[coord->grid_index]];
|
||||
const SubdivCCGFace *face = subdiv_ccg->grid_faces[coord->grid_index];
|
||||
const int face_index = face - subdiv_ccg->faces;
|
||||
const int face_grid_index = coord->grid_index - face->start_grid_index;
|
||||
const int num_face_grids = face->num_grids;
|
||||
|
@ -1562,7 +1654,7 @@ static int adjacent_edge_index_from_coord(const SubdivCCG *subdiv_ccg, const Sub
|
|||
{
|
||||
Subdiv *subdiv = subdiv_ccg->subdiv;
|
||||
OpenSubdiv_TopologyRefiner *topology_refiner = subdiv->topology_refiner;
|
||||
const SubdivCCGFace *face = &subdiv_ccg->faces[subdiv_ccg->grid_faces[coord->grid_index]];
|
||||
SubdivCCGFace *face = subdiv_ccg->grid_faces[coord->grid_index];
|
||||
|
||||
const int face_grid_index = coord->grid_index - face->start_grid_index;
|
||||
const int face_index = face - subdiv_ccg->faces;
|
||||
|
@ -1859,7 +1951,9 @@ void BKE_subdiv_ccg_neighbor_coords_get(const SubdivCCG *subdiv_ccg,
|
|||
|
||||
int BKE_subdiv_ccg_grid_to_face_index(const SubdivCCG *subdiv_ccg, const int grid_index)
|
||||
{
|
||||
return subdiv_ccg->grid_faces[grid_index];
|
||||
const SubdivCCGFace *face = subdiv_ccg->grid_faces[grid_index];
|
||||
const int face_index = face - subdiv_ccg->faces;
|
||||
return face_index;
|
||||
}
|
||||
|
||||
const int *BKE_subdiv_ccg_start_face_grid_index_ensure(SubdivCCG *subdiv_ccg)
|
||||
|
|
|
@ -168,7 +168,7 @@ static bool menu_items_from_ui_create_item_from_button(MenuSearch_Data *data,
|
|||
MenuSearch_Item *item = nullptr;
|
||||
|
||||
/* Use override if the name is empty, this can happen with popovers. */
|
||||
std::string drawstr_override = nullptr;
|
||||
std::string drawstr_override;
|
||||
const char *drawstr_sep = (but->flag & UI_BUT_HAS_SEP_CHAR) ?
|
||||
strrchr(but->drawstr, UI_SEP_CHAR) :
|
||||
nullptr;
|
||||
|
|
|
@ -120,13 +120,6 @@ extern "C" eAttrDomain BKE_id_attribute_domain(const struct ID * /*id*/,
|
|||
/* -------------------------------------------------------------------- */
|
||||
/** \name Stubs of BKE_paint.hh
|
||||
* \{ */
|
||||
bool paint_is_face_hidden(const int * /*looptri_faces*/,
|
||||
const bool * /*hide_poly*/,
|
||||
int /*tri_index*/)
|
||||
{
|
||||
BLI_assert_unreachable();
|
||||
return false;
|
||||
}
|
||||
|
||||
void BKE_paint_face_set_overlay_color_get(const int /*face_set*/,
|
||||
const int /*seed*/,
|
||||
|
|
|
@ -1485,13 +1485,10 @@ static std::string rna_operator_description_cb(bContext *C,
|
|||
ot->rna_ext.call(C, &ptr, func, &list);
|
||||
|
||||
RNA_parameter_get_lookup(&list, "result", &ret);
|
||||
const char *result = (const char *)ret;
|
||||
std::string result = ret ? std::string(static_cast<const char *>(ret)) : "";
|
||||
|
||||
RNA_parameter_list_free(&list);
|
||||
|
||||
if (!result) {
|
||||
return "";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue