Implements #95967. Currently the `MPoly` struct is 12 bytes, and stores the index of a face's first corner and the number of corners/verts/edges. Polygons and corners are always created in order by Blender, meaning each face's corners will be after the previous face's corners. We can take advantage of this fact and eliminate the redundancy in mesh face storage by only storing a single integer corner offset for each face. The size of the face is then encoded by the offset of the next face. The size of a single integer is 4 bytes, so this reduces memory usage by 3 times. The same method is used for `CurvesGeometry`, so Blender already has an abstraction to simplify using these offsets called `OffsetIndices`. This class is used to easily retrieve a range of corner indices for each face. This also gives the opportunity for sharing some logic with curves. Another benefit of the change is that the offsets and sizes stored in `MPoly` can no longer disagree with each other. Storing faces in the order of their corners can simplify some code too. Face/polygon variables now use the `IndexRange` type, which comes with quite a few utilities that can simplify code. Some: - The offset integer array has to be one longer than the face count to avoid a branch for every face, which means the data is no longer part of the mesh's `CustomData`. - We lose the ability to "reference" an original mesh's offset array until more reusable CoW from #104478 is committed. That will be added in a separate commit. - Since they aren't part of `CustomData`, poly offsets often have to be copied manually. - To simplify using `OffsetIndices` in many places, some functions and structs in headers were moved to only compile in C++. - All meshes created by Blender use the same order for faces and face corners, but just in case, meshes with mismatched order are fixed by versioning code. - `MeshPolygon.totloop` is no longer editable in RNA. This API break is necessary here unfortunately. It should be worth it in 3.6, since that's the best way to allow loading meshes from 4.0, which is important for an LTS version. Pull Request: https://projects.blender.org/blender/blender/pulls/105938
437 lines
14 KiB
C
437 lines
14 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
/** \file
|
|
* \ingroup modifiers
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
|
|
#include "BLI_utildefines.h"
|
|
|
|
#include "BLI_math.h"
|
|
#include "BLI_path_util.h"
|
|
#include "BLI_string.h"
|
|
|
|
#include "BLT_translation.h"
|
|
|
|
#include "DNA_defaults.h"
|
|
#include "DNA_mesh_types.h"
|
|
#include "DNA_meshdata_types.h"
|
|
#include "DNA_object_types.h"
|
|
#include "DNA_scene_types.h"
|
|
#include "DNA_screen_types.h"
|
|
|
|
#include "BKE_context.h"
|
|
#include "BKE_deform.h"
|
|
#include "BKE_lib_id.h"
|
|
#include "BKE_main.h"
|
|
#include "BKE_mesh.h"
|
|
#include "BKE_mesh_wrapper.h"
|
|
#include "BKE_scene.h"
|
|
#include "BKE_screen.h"
|
|
|
|
#include "UI_interface.h"
|
|
#include "UI_resources.h"
|
|
|
|
#include "RNA_access.h"
|
|
#include "RNA_prototypes.h"
|
|
|
|
#include "DEG_depsgraph_query.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "MOD_meshcache_util.h" /* utility functions */
|
|
#include "MOD_modifiertypes.h"
|
|
#include "MOD_ui_common.h"
|
|
#include "MOD_util.h"
|
|
|
|
static void initData(ModifierData *md)
|
|
{
|
|
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
|
|
|
|
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(mcmd, modifier));
|
|
|
|
MEMCPY_STRUCT_AFTER(mcmd, DNA_struct_default_get(MeshCacheModifierData), modifier);
|
|
}
|
|
|
|
static bool dependsOnTime(struct Scene *UNUSED(scene), ModifierData *md)
|
|
{
|
|
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
|
|
return (mcmd->play_mode == MOD_MESHCACHE_PLAY_CFEA);
|
|
}
|
|
|
|
static bool isDisabled(const struct Scene *UNUSED(scene),
|
|
ModifierData *md,
|
|
bool UNUSED(useRenderParams))
|
|
{
|
|
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
|
|
|
|
/* leave it up to the modifier to check the file is valid on calculation */
|
|
return (mcmd->factor <= 0.0f) || (mcmd->filepath[0] == '\0');
|
|
}
|
|
|
|
static void meshcache_do(MeshCacheModifierData *mcmd,
|
|
Scene *scene,
|
|
Object *ob,
|
|
Mesh *mesh,
|
|
float (*vertexCos_Real)[3],
|
|
int verts_num)
|
|
{
|
|
const bool use_factor = mcmd->factor < 1.0f;
|
|
int influence_group_index;
|
|
const MDeformVert *dvert;
|
|
MOD_get_vgroup(ob, mesh, mcmd->defgrp_name, &dvert, &influence_group_index);
|
|
|
|
float(*vertexCos_Store)[3] = (use_factor || influence_group_index != -1 ||
|
|
(mcmd->deform_mode == MOD_MESHCACHE_DEFORM_INTEGRATE)) ?
|
|
MEM_malloc_arrayN(
|
|
verts_num, sizeof(*vertexCos_Store), __func__) :
|
|
NULL;
|
|
float(*vertexCos)[3] = vertexCos_Store ? vertexCos_Store : vertexCos_Real;
|
|
|
|
const float fps = FPS;
|
|
|
|
char filepath[FILE_MAX];
|
|
const char *err_str = NULL;
|
|
bool ok;
|
|
|
|
float time;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Interpret Time (the reading functions also do some of this ) */
|
|
if (mcmd->play_mode == MOD_MESHCACHE_PLAY_CFEA) {
|
|
const float ctime = BKE_scene_ctime_get(scene);
|
|
|
|
switch (mcmd->time_mode) {
|
|
case MOD_MESHCACHE_TIME_FRAME: {
|
|
time = ctime;
|
|
break;
|
|
}
|
|
case MOD_MESHCACHE_TIME_SECONDS: {
|
|
time = ctime / fps;
|
|
break;
|
|
}
|
|
case MOD_MESHCACHE_TIME_FACTOR:
|
|
default: {
|
|
time = ctime / fps;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* apply offset and scale */
|
|
time = (mcmd->frame_scale * time) - mcmd->frame_start;
|
|
}
|
|
else { /* if (mcmd->play_mode == MOD_MESHCACHE_PLAY_EVAL) { */
|
|
switch (mcmd->time_mode) {
|
|
case MOD_MESHCACHE_TIME_FRAME: {
|
|
time = mcmd->eval_frame;
|
|
break;
|
|
}
|
|
case MOD_MESHCACHE_TIME_SECONDS: {
|
|
time = mcmd->eval_time;
|
|
break;
|
|
}
|
|
case MOD_MESHCACHE_TIME_FACTOR:
|
|
default: {
|
|
time = mcmd->eval_factor;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Read the File (or error out when the file is bad) */
|
|
|
|
/* would be nice if we could avoid doing this _every_ frame */
|
|
BLI_strncpy(filepath, mcmd->filepath, sizeof(filepath));
|
|
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL((ID *)ob));
|
|
|
|
switch (mcmd->type) {
|
|
case MOD_MESHCACHE_TYPE_MDD:
|
|
ok = MOD_meshcache_read_mdd_times(
|
|
filepath, vertexCos, verts_num, mcmd->interp, time, fps, mcmd->time_mode, &err_str);
|
|
break;
|
|
case MOD_MESHCACHE_TYPE_PC2:
|
|
ok = MOD_meshcache_read_pc2_times(
|
|
filepath, vertexCos, verts_num, mcmd->interp, time, fps, mcmd->time_mode, &err_str);
|
|
break;
|
|
default:
|
|
ok = false;
|
|
break;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* tricky shape key integration (slow!) */
|
|
if (mcmd->deform_mode == MOD_MESHCACHE_DEFORM_INTEGRATE) {
|
|
Mesh *me = ob->data;
|
|
|
|
/* we could support any object type */
|
|
if (UNLIKELY(ob->type != OB_MESH)) {
|
|
BKE_modifier_set_error(ob, &mcmd->modifier, "'Integrate' only valid for Mesh objects");
|
|
}
|
|
else if (UNLIKELY(me->totvert != verts_num)) {
|
|
BKE_modifier_set_error(ob, &mcmd->modifier, "'Integrate' original mesh vertex mismatch");
|
|
}
|
|
else if (UNLIKELY(me->totpoly == 0)) {
|
|
BKE_modifier_set_error(ob, &mcmd->modifier, "'Integrate' requires faces");
|
|
}
|
|
else {
|
|
float(*vertexCos_New)[3] = MEM_malloc_arrayN(verts_num, sizeof(*vertexCos_New), __func__);
|
|
|
|
BKE_mesh_calc_relative_deform(
|
|
BKE_mesh_poly_offsets(me),
|
|
me->totpoly,
|
|
BKE_mesh_corner_verts(me),
|
|
me->totvert,
|
|
BKE_mesh_vert_positions(me), /* From the original Mesh. */
|
|
(const float(*)[3])vertexCos_Real, /* the input we've been given (shape keys!) */
|
|
(const float(*)[3])vertexCos, /* The result of this modifier. */
|
|
vertexCos_New /* The result of this function. */
|
|
);
|
|
|
|
/* write the corrected locations back into the result */
|
|
memcpy(vertexCos, vertexCos_New, sizeof(*vertexCos) * verts_num);
|
|
|
|
MEM_freeN(vertexCos_New);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Apply the transformation matrix (if needed) */
|
|
if (UNLIKELY(err_str)) {
|
|
BKE_modifier_set_error(ob, &mcmd->modifier, "%s", err_str);
|
|
}
|
|
else if (ok) {
|
|
bool use_matrix = false;
|
|
float mat[3][3];
|
|
unit_m3(mat);
|
|
|
|
if (mat3_from_axis_conversion(mcmd->forward_axis, mcmd->up_axis, 1, 2, mat)) {
|
|
use_matrix = true;
|
|
}
|
|
|
|
if (mcmd->flip_axis) {
|
|
float tmat[3][3];
|
|
unit_m3(tmat);
|
|
if (mcmd->flip_axis & (1 << 0)) {
|
|
tmat[0][0] = -1.0f;
|
|
}
|
|
if (mcmd->flip_axis & (1 << 1)) {
|
|
tmat[1][1] = -1.0f;
|
|
}
|
|
if (mcmd->flip_axis & (1 << 2)) {
|
|
tmat[2][2] = -1.0f;
|
|
}
|
|
mul_m3_m3m3(mat, tmat, mat);
|
|
|
|
use_matrix = true;
|
|
}
|
|
|
|
if (use_matrix) {
|
|
int i;
|
|
for (i = 0; i < verts_num; i++) {
|
|
mul_m3_v3(mat, vertexCos[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (vertexCos_Store) {
|
|
if (ok) {
|
|
if (influence_group_index != -1) {
|
|
const float global_factor = (mcmd->flag & MOD_MESHCACHE_INVERT_VERTEX_GROUP) ?
|
|
-mcmd->factor :
|
|
mcmd->factor;
|
|
const float global_offset = (mcmd->flag & MOD_MESHCACHE_INVERT_VERTEX_GROUP) ?
|
|
mcmd->factor :
|
|
0.0f;
|
|
if (BKE_mesh_deform_verts(mesh) != NULL) {
|
|
for (int i = 0; i < verts_num; i++) {
|
|
/* For each vertex, compute its blending factor between the mesh cache (for `fac = 0`)
|
|
* and the former position of the vertex (for `fac = 1`). */
|
|
const MDeformVert *currentIndexDVert = dvert + i;
|
|
const float local_vertex_fac = global_offset +
|
|
BKE_defvert_find_weight(currentIndexDVert,
|
|
influence_group_index) *
|
|
global_factor;
|
|
interp_v3_v3v3(
|
|
vertexCos_Real[i], vertexCos_Real[i], vertexCos_Store[i], local_vertex_fac);
|
|
}
|
|
}
|
|
}
|
|
else if (use_factor) {
|
|
/* Influence_group_index is -1. */
|
|
interp_vn_vn(*vertexCos_Real, *vertexCos_Store, mcmd->factor, verts_num * 3);
|
|
}
|
|
else {
|
|
memcpy(vertexCos_Real, vertexCos_Store, sizeof(*vertexCos_Store) * verts_num);
|
|
}
|
|
}
|
|
|
|
MEM_freeN(vertexCos_Store);
|
|
}
|
|
}
|
|
|
|
static void deformVerts(ModifierData *md,
|
|
const ModifierEvalContext *ctx,
|
|
Mesh *mesh,
|
|
float (*vertexCos)[3],
|
|
int verts_num)
|
|
{
|
|
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
|
|
Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph);
|
|
|
|
Mesh *mesh_src = NULL;
|
|
|
|
if (ctx->object->type == OB_MESH && mcmd->defgrp_name[0] != '\0') {
|
|
/* `mesh_src` is only needed for vertex groups. */
|
|
mesh_src = MOD_deform_mesh_eval_get(ctx->object, NULL, mesh, NULL, verts_num, false);
|
|
}
|
|
meshcache_do(mcmd, scene, ctx->object, mesh_src, vertexCos, verts_num);
|
|
|
|
if (!ELEM(mesh_src, NULL, mesh)) {
|
|
BKE_id_free(NULL, mesh_src);
|
|
}
|
|
}
|
|
|
|
static void deformVertsEM(ModifierData *md,
|
|
const ModifierEvalContext *ctx,
|
|
struct BMEditMesh *editData,
|
|
Mesh *mesh,
|
|
float (*vertexCos)[3],
|
|
int verts_num)
|
|
{
|
|
MeshCacheModifierData *mcmd = (MeshCacheModifierData *)md;
|
|
Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph);
|
|
|
|
Mesh *mesh_src = NULL;
|
|
|
|
if (ctx->object->type == OB_MESH && mcmd->defgrp_name[0] != '\0') {
|
|
/* `mesh_src` is only needed for vertex groups. */
|
|
mesh_src = MOD_deform_mesh_eval_get(ctx->object, editData, mesh, NULL, verts_num, false);
|
|
}
|
|
if (mesh_src != NULL) {
|
|
BKE_mesh_wrapper_ensure_mdata(mesh_src);
|
|
}
|
|
|
|
meshcache_do(mcmd, scene, ctx->object, mesh_src, vertexCos, verts_num);
|
|
|
|
if (!ELEM(mesh_src, NULL, mesh)) {
|
|
BKE_id_free(NULL, mesh_src);
|
|
}
|
|
}
|
|
|
|
static void panel_draw(const bContext *UNUSED(C), Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
|
|
PointerRNA ob_ptr;
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr);
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
|
|
uiItemR(layout, ptr, "cache_format", 0, NULL, ICON_NONE);
|
|
uiItemR(layout, ptr, "filepath", 0, NULL, ICON_NONE);
|
|
|
|
uiItemR(layout, ptr, "factor", UI_ITEM_R_SLIDER, NULL, ICON_NONE);
|
|
uiItemR(layout, ptr, "deform_mode", 0, NULL, ICON_NONE);
|
|
uiItemR(layout, ptr, "interpolation", 0, NULL, ICON_NONE);
|
|
modifier_vgroup_ui(layout, ptr, &ob_ptr, "vertex_group", "invert_vertex_group", NULL);
|
|
|
|
modifier_panel_end(layout, ptr);
|
|
}
|
|
|
|
static void time_remapping_panel_draw(const bContext *UNUSED(C), Panel *panel)
|
|
{
|
|
uiLayout *layout = panel->layout;
|
|
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL);
|
|
|
|
uiItemR(layout, ptr, "time_mode", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
|
|
uiItemR(layout, ptr, "play_mode", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
|
|
|
|
if (RNA_enum_get(ptr, "play_mode") == MOD_MESHCACHE_PLAY_CFEA) {
|
|
uiItemR(layout, ptr, "frame_start", 0, NULL, ICON_NONE);
|
|
uiItemR(layout, ptr, "frame_scale", 0, NULL, ICON_NONE);
|
|
}
|
|
else { /* play_mode == MOD_MESHCACHE_PLAY_EVAL */
|
|
int time_mode = RNA_enum_get(ptr, "time_mode");
|
|
if (time_mode == MOD_MESHCACHE_TIME_FRAME) {
|
|
uiItemR(layout, ptr, "eval_frame", 0, NULL, ICON_NONE);
|
|
}
|
|
else if (time_mode == MOD_MESHCACHE_TIME_SECONDS) {
|
|
uiItemR(layout, ptr, "eval_time", 0, NULL, ICON_NONE);
|
|
}
|
|
else { /* time_mode == MOD_MESHCACHE_TIME_FACTOR */
|
|
uiItemR(layout, ptr, "eval_factor", 0, NULL, ICON_NONE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void axis_mapping_panel_draw(const bContext *UNUSED(C), Panel *panel)
|
|
{
|
|
uiLayout *col;
|
|
uiLayout *layout = panel->layout;
|
|
|
|
PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL);
|
|
|
|
uiLayoutSetPropSep(layout, true);
|
|
|
|
col = uiLayoutColumn(layout, true);
|
|
uiLayoutSetRedAlert(col, RNA_enum_get(ptr, "forward_axis") == RNA_enum_get(ptr, "up_axis"));
|
|
uiItemR(col, ptr, "forward_axis", 0, NULL, ICON_NONE);
|
|
uiItemR(col, ptr, "up_axis", 0, NULL, ICON_NONE);
|
|
|
|
uiItemR(layout, ptr, "flip_axis", UI_ITEM_R_EXPAND, NULL, ICON_NONE);
|
|
}
|
|
|
|
static void panelRegister(ARegionType *region_type)
|
|
{
|
|
PanelType *panel_type = modifier_panel_register(
|
|
region_type, eModifierType_MeshCache, panel_draw);
|
|
modifier_subpanel_register(region_type,
|
|
"time_remapping",
|
|
"Time Remapping",
|
|
NULL,
|
|
time_remapping_panel_draw,
|
|
panel_type);
|
|
modifier_subpanel_register(
|
|
region_type, "axis_mapping", "Axis Mapping", NULL, axis_mapping_panel_draw, panel_type);
|
|
}
|
|
|
|
ModifierTypeInfo modifierType_MeshCache = {
|
|
/*name*/ N_("MeshCache"),
|
|
/*structName*/ "MeshCacheModifierData",
|
|
/*structSize*/ sizeof(MeshCacheModifierData),
|
|
/*srna*/ &RNA_MeshCacheModifier,
|
|
/*type*/ eModifierTypeType_OnlyDeform,
|
|
/*flags*/ eModifierTypeFlag_AcceptsCVs | eModifierTypeFlag_AcceptsVertexCosOnly |
|
|
eModifierTypeFlag_SupportsEditmode,
|
|
/*icon*/ ICON_MOD_MESHDEFORM, /* TODO: Use correct icon. */
|
|
|
|
/*copyData*/ BKE_modifier_copydata_generic,
|
|
|
|
/*deformVerts*/ deformVerts,
|
|
/*deformMatrices*/ NULL,
|
|
/*deformVertsEM*/ deformVertsEM,
|
|
/*deformMatricesEM*/ NULL,
|
|
/*modifyMesh*/ NULL,
|
|
/*modifyGeometrySet*/ NULL,
|
|
|
|
/*initData*/ initData,
|
|
/*requiredDataMask*/ NULL,
|
|
/*freeData*/ NULL,
|
|
/*isDisabled*/ isDisabled,
|
|
/*updateDepsgraph*/ NULL,
|
|
/*dependsOnTime*/ dependsOnTime,
|
|
/*dependsOnNormals*/ NULL,
|
|
/*foreachIDLink*/ NULL,
|
|
/*foreachTexLink*/ NULL,
|
|
/*freeRuntimeData*/ NULL,
|
|
/*panelRegister*/ panelRegister,
|
|
/*blendWrite*/ NULL,
|
|
/*blendRead*/ NULL,
|
|
};
|