- Tangent calculation functions no longer use the CustomData struct as an input and output. - The "orco" and UV map calculations are exposed as separate API functions to avoid a confusing internal choice between the two - Redundancy in the API is removed, function names are clarified - Code is moved to C++ namespace - The "orco" case is clarified in the mesh draw tangent VBO extraction - CD_TANGENT layers are not stored in CustomData anymore, so some of that infrastructure is removed. - Broken logic for caching tangents in CustomData is removed. That hasn't worked for many years, if it every worked. We could investigate adding caching again later if that's helpful. Overall the change is motivated by the need to move away from CustomData for #122398. But the changes should be a general improvement that makes the code easier to understand either way. Testing for this PR included using the default render UV in materials, referencing specific UV tangents by name, using the spherical position- based tangents in a material, and baking textures (multires and normal baking). Pull Request: https://projects.blender.org/blender/blender/pulls/141799
288 lines
8.1 KiB
C++
288 lines
8.1 KiB
C++
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup bke
|
|
*/
|
|
|
|
#include "BLI_math_geom.h"
|
|
#include "BLI_math_vector.h"
|
|
#include "BLI_task.hh"
|
|
|
|
#include "DNA_customdata_types.h"
|
|
|
|
#include "BKE_customdata.hh"
|
|
#include "BKE_editmesh.hh"
|
|
#include "BKE_editmesh_tangent.hh"
|
|
#include "BKE_mesh.hh"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
/* interface */
|
|
#include "mikktspace.hh"
|
|
|
|
using blender::Array;
|
|
using blender::float3;
|
|
using blender::float4;
|
|
using blender::Span;
|
|
using blender::StringRef;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Tangent Space Calculation
|
|
* \{ */
|
|
|
|
/* Necessary complexity to handle looptris as quads for correct tangents. */
|
|
#define USE_LOOPTRI_DETECT_QUADS
|
|
|
|
struct SGLSLEditMeshToTangent {
|
|
uint GetNumFaces()
|
|
{
|
|
#ifdef USE_LOOPTRI_DETECT_QUADS
|
|
return uint(num_face_as_quad_map);
|
|
#else
|
|
return uint(numTessFaces);
|
|
#endif
|
|
}
|
|
|
|
uint GetNumVerticesOfFace(const uint face_num)
|
|
{
|
|
#ifdef USE_LOOPTRI_DETECT_QUADS
|
|
if (face_as_quad_map) {
|
|
if (looptris[face_as_quad_map[face_num]][0]->f->len == 4) {
|
|
return 4;
|
|
}
|
|
}
|
|
return 3;
|
|
#else
|
|
UNUSED_VARS(pContext, face_num);
|
|
return 3;
|
|
#endif
|
|
}
|
|
|
|
const BMLoop *GetLoop(const uint face_num, uint vert_index)
|
|
{
|
|
// BLI_assert(vert_index >= 0 && vert_index < 4);
|
|
BMLoop *const *ltri;
|
|
const BMLoop *l;
|
|
|
|
#ifdef USE_LOOPTRI_DETECT_QUADS
|
|
if (face_as_quad_map) {
|
|
ltri = looptris[face_as_quad_map[face_num]].data();
|
|
if (ltri[0]->f->len == 4) {
|
|
l = BM_FACE_FIRST_LOOP(ltri[0]->f);
|
|
while (vert_index--) {
|
|
l = l->next;
|
|
}
|
|
return l;
|
|
}
|
|
/* fall through to regular triangle */
|
|
}
|
|
else {
|
|
ltri = looptris[face_num].data();
|
|
}
|
|
#else
|
|
ltri = looptris[face_num].data();
|
|
#endif
|
|
return ltri[vert_index];
|
|
}
|
|
|
|
mikk::float3 GetPosition(const uint face_num, const uint vert_index)
|
|
{
|
|
const BMLoop *l = GetLoop(face_num, vert_index);
|
|
return mikk::float3(l->v->co);
|
|
}
|
|
|
|
mikk::float3 GetTexCoord(const uint face_num, const uint vert_index)
|
|
{
|
|
const BMLoop *l = GetLoop(face_num, vert_index);
|
|
if (has_uv()) {
|
|
const float *uv = (const float *)BM_ELEM_CD_GET_VOID_P(l, cd_loop_uv_offset);
|
|
return mikk::float3(uv[0], uv[1], 1.0f);
|
|
}
|
|
const float *orco_p = orco[BM_elem_index_get(l->v)];
|
|
float u, v;
|
|
map_to_sphere(&u, &v, orco_p[0], orco_p[1], orco_p[2]);
|
|
return mikk::float3(u, v, 1.0f);
|
|
}
|
|
|
|
mikk::float3 GetNormal(const uint face_num, const uint vert_index)
|
|
{
|
|
const BMLoop *l = GetLoop(face_num, vert_index);
|
|
if (!corner_normals.is_empty()) {
|
|
return mikk::float3(corner_normals[BM_elem_index_get(l)]);
|
|
}
|
|
if (BM_elem_flag_test(l->f, BM_ELEM_SMOOTH) == 0) { /* flat */
|
|
if (!face_normals.is_empty()) {
|
|
return mikk::float3(face_normals[BM_elem_index_get(l->f)]);
|
|
}
|
|
return mikk::float3(l->f->no);
|
|
}
|
|
return mikk::float3(l->v->no);
|
|
}
|
|
|
|
void SetTangentSpace(const uint face_num,
|
|
const uint vert_index,
|
|
mikk::float3 T,
|
|
bool orientation)
|
|
{
|
|
const BMLoop *l = GetLoop(face_num, vert_index);
|
|
float *p_res = tangent[BM_elem_index_get(l)];
|
|
copy_v4_fl4(p_res, T.x, T.y, T.z, orientation ? 1.0f : -1.0f);
|
|
}
|
|
|
|
bool has_uv()
|
|
{
|
|
return cd_loop_uv_offset != -1;
|
|
}
|
|
|
|
Span<float3> face_normals;
|
|
Span<float3> corner_normals;
|
|
Span<std::array<BMLoop *, 3>> looptris;
|
|
int cd_loop_uv_offset; /* texture coordinates */
|
|
Span<float3> orco;
|
|
float (*tangent)[4]; /* destination */
|
|
int numTessFaces;
|
|
|
|
#ifdef USE_LOOPTRI_DETECT_QUADS
|
|
/* map from 'fake' face index to looptris,
|
|
* quads will point to the first looptri of the quad */
|
|
const int *face_as_quad_map;
|
|
int num_face_as_quad_map;
|
|
#endif
|
|
};
|
|
|
|
static void calc_face_as_quad_map(
|
|
BMEditMesh *&em, BMesh *&bm, int &totface, int &num_face_as_quad_map, int *&face_as_quad_map)
|
|
{
|
|
#ifdef USE_LOOPTRI_DETECT_QUADS
|
|
if (em->looptris.size() != bm->totface) {
|
|
/* Over allocate, since we don't know how many ngon or quads we have. */
|
|
|
|
/* map fake face index to looptri */
|
|
face_as_quad_map = MEM_malloc_arrayN<int>(size_t(totface), __func__);
|
|
int i, j;
|
|
for (i = 0, j = 0; j < totface; i++, j++) {
|
|
face_as_quad_map[i] = j;
|
|
/* step over all quads */
|
|
if (em->looptris[j][0]->f->len == 4) {
|
|
j++; /* Skips the next looptri. */
|
|
}
|
|
}
|
|
num_face_as_quad_map = i;
|
|
}
|
|
else {
|
|
num_face_as_quad_map = totface;
|
|
}
|
|
#else
|
|
num_face_as_quad_map = totface;
|
|
face_as_quad_map = nullptr;
|
|
#endif
|
|
}
|
|
|
|
Array<Array<float4>> BKE_editmesh_uv_tangents_calc(BMEditMesh *em,
|
|
const Span<float3> face_normals,
|
|
const Span<float3> corner_normals,
|
|
const Span<StringRef> uv_names)
|
|
{
|
|
using namespace blender;
|
|
if (em->looptris.is_empty()) {
|
|
return {};
|
|
}
|
|
|
|
BMesh *bm = em->bm;
|
|
|
|
int totface = em->looptris.size();
|
|
int num_face_as_quad_map;
|
|
int *face_as_quad_map = nullptr;
|
|
calc_face_as_quad_map(em, bm, totface, num_face_as_quad_map, face_as_quad_map);
|
|
|
|
Array<Array<float4>> result(uv_names.size());
|
|
|
|
/* needed for indexing loop-tangents */
|
|
int htype_index = BM_LOOP;
|
|
if (!face_normals.is_empty()) {
|
|
/* needed for face normal lookups */
|
|
htype_index |= BM_FACE;
|
|
}
|
|
BM_mesh_elem_index_ensure(bm, htype_index);
|
|
|
|
threading::parallel_for(uv_names.index_range(), 1, [&](const IndexRange range) {
|
|
for (const int n : range) {
|
|
SGLSLEditMeshToTangent mesh2tangent{};
|
|
mesh2tangent.numTessFaces = em->looptris.size();
|
|
mesh2tangent.face_as_quad_map = face_as_quad_map;
|
|
mesh2tangent.num_face_as_quad_map = num_face_as_quad_map;
|
|
mesh2tangent.face_normals = face_normals;
|
|
/* NOTE: we assume we do have tessellated loop normals at this point
|
|
* (in case it is object-enabled), have to check this is valid. */
|
|
mesh2tangent.corner_normals = corner_normals;
|
|
mesh2tangent.cd_loop_uv_offset = CustomData_get_offset_named(
|
|
&bm->ldata, CD_PROP_FLOAT2, uv_names[n]);
|
|
BLI_assert(mesh2tangent.cd_loop_uv_offset != -1);
|
|
|
|
mesh2tangent.looptris = em->looptris;
|
|
result[n].reinitialize(bm->totloop);
|
|
mesh2tangent.tangent = reinterpret_cast<float(*)[4]>(result[n].data());
|
|
|
|
mikk::Mikktspace<SGLSLEditMeshToTangent> mikk(mesh2tangent);
|
|
mikk.genTangSpace();
|
|
}
|
|
});
|
|
|
|
MEM_SAFE_FREE(face_as_quad_map);
|
|
|
|
return result;
|
|
}
|
|
|
|
Array<float4> BKE_editmesh_orco_tangents_calc(BMEditMesh *em,
|
|
const Span<float3> face_normals,
|
|
const Span<float3> corner_normals,
|
|
const Span<float3> vert_orco)
|
|
{
|
|
if (em->looptris.is_empty()) {
|
|
return {};
|
|
}
|
|
|
|
BMesh *bm = em->bm;
|
|
|
|
int totface = em->looptris.size();
|
|
int num_face_as_quad_map;
|
|
int *face_as_quad_map = nullptr;
|
|
calc_face_as_quad_map(em, bm, totface, num_face_as_quad_map, face_as_quad_map);
|
|
|
|
Array<float4> result(bm->totloop);
|
|
|
|
/* needed for indexing loop-tangents */
|
|
int htype_index = BM_LOOP;
|
|
/* needed for orco lookups */
|
|
htype_index |= BM_VERT;
|
|
if (!face_normals.is_empty()) {
|
|
/* needed for face normal lookups */
|
|
htype_index |= BM_FACE;
|
|
}
|
|
BM_mesh_elem_index_ensure(bm, htype_index);
|
|
|
|
SGLSLEditMeshToTangent mesh2tangent{};
|
|
mesh2tangent.numTessFaces = em->looptris.size();
|
|
mesh2tangent.face_as_quad_map = face_as_quad_map;
|
|
mesh2tangent.num_face_as_quad_map = num_face_as_quad_map;
|
|
mesh2tangent.face_normals = face_normals;
|
|
/* NOTE: we assume we do have tessellated loop normals at this point
|
|
* (in case it is object-enabled), have to check this is valid. */
|
|
mesh2tangent.corner_normals = corner_normals;
|
|
mesh2tangent.cd_loop_uv_offset = -1;
|
|
mesh2tangent.orco = vert_orco;
|
|
|
|
mesh2tangent.looptris = em->looptris;
|
|
mesh2tangent.tangent = reinterpret_cast<float(*)[4]>(result.data());
|
|
mikk::Mikktspace<SGLSLEditMeshToTangent> mikk(mesh2tangent);
|
|
mikk.genTangSpace();
|
|
|
|
MEM_SAFE_FREE(face_as_quad_map);
|
|
|
|
return result;
|
|
}
|
|
|
|
/** \} */
|