forked from bartvdbraak/blender
Basic solver result feedback from the mass-spring (cloth/hair) solver.
This returns a general status (success/no-convergence/other) along with basic statistics (min/max/average) for the error value and the number of iterations. It allows some general estimation of the simulation quality and detection of critical settings that could become a problem. Better visualization and extended feedback can follow later.
This commit is contained in:
parent
00bb836e17
commit
491e7493c7
@ -304,6 +304,7 @@ class PARTICLE_PT_hair_dynamics(ParticleButtonsPanel, Panel):
|
||||
|
||||
cloth_md = psys.cloth
|
||||
cloth = cloth_md.settings
|
||||
result = cloth_md.solver_result
|
||||
|
||||
layout.enabled = psys.use_hair_dynamics and psys.point_cache.is_baked is False
|
||||
|
||||
@ -335,6 +336,24 @@ class PARTICLE_PT_hair_dynamics(ParticleButtonsPanel, Panel):
|
||||
|
||||
col.prop(cloth_md, "show_debug_data", text="Debug")
|
||||
|
||||
if result:
|
||||
box = layout.box()
|
||||
|
||||
if not result.status:
|
||||
label = " "
|
||||
icon = 'NONE'
|
||||
elif result.status == {'SUCCESS'}:
|
||||
label = "Success"
|
||||
icon = 'NONE'
|
||||
elif result.status - {'SUCCESS'} == {'NO_CONVERGENCE'}:
|
||||
label = "No Convergence"
|
||||
icon = 'ERROR'
|
||||
else:
|
||||
label = "ERROR"
|
||||
icon = 'ERROR'
|
||||
box.label(label, icon=icon)
|
||||
box.label("Iterations: %d .. %d (avg. %d)" % (result.min_iterations, result.max_iterations, result.avg_iterations))
|
||||
box.label("Error: %.5f .. %.5f (avg. %.5f)" % (result.min_error, result.max_error, result.avg_error))
|
||||
|
||||
|
||||
class PARTICLE_PT_cache(ParticleButtonsPanel, Panel):
|
||||
|
@ -64,6 +64,14 @@ typedef struct ClothHairRoot {
|
||||
float rot[3][3];
|
||||
} ClothHairRoot;
|
||||
|
||||
typedef struct ClothSolverResult {
|
||||
int status;
|
||||
|
||||
int max_iterations, min_iterations;
|
||||
float avg_iterations;
|
||||
float max_error, min_error, avg_error;
|
||||
} ClothSolverResult;
|
||||
|
||||
/**
|
||||
* This structure describes a cloth object against which the
|
||||
* simulation can run.
|
||||
|
@ -4683,6 +4683,7 @@ static void direct_link_modifiers(FileData *fd, ListBase *lb)
|
||||
}
|
||||
}
|
||||
|
||||
clmd->solver_result = NULL;
|
||||
clmd->debug_data = NULL;
|
||||
}
|
||||
else if (md->type == eModifierType_Fluidsim) {
|
||||
|
@ -567,6 +567,8 @@ typedef struct ClothModifierData {
|
||||
/* XXX nasty hack, remove once hair can be separated from cloth modifier data */
|
||||
struct ClothHairRoot *roots;
|
||||
|
||||
struct ClothSolverResult *solver_result;
|
||||
|
||||
struct SimDebugData *debug_data; /* debug info */
|
||||
} ClothModifierData;
|
||||
|
||||
|
@ -293,6 +293,7 @@ blender_include_dirs(
|
||||
../../ikplugin
|
||||
../../makesdna
|
||||
../../nodes/
|
||||
../../physics
|
||||
../../windowmanager
|
||||
../../editors/include
|
||||
../../render/extern/include
|
||||
|
@ -38,6 +38,8 @@
|
||||
#include "BKE_cloth.h"
|
||||
#include "BKE_modifier.h"
|
||||
|
||||
#include "BPH_mass_spring.h"
|
||||
|
||||
#include "WM_api.h"
|
||||
#include "WM_types.h"
|
||||
|
||||
@ -268,6 +270,64 @@ static char *rna_ClothCollisionSettings_path(PointerRNA *ptr)
|
||||
|
||||
#else
|
||||
|
||||
static void rna_def_cloth_solver_result(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
static EnumPropertyItem status_items[] = {
|
||||
{BPH_SOLVER_SUCCESS, "SUCCESS", 0, "Success", "Computation was successful"},
|
||||
{BPH_SOLVER_NUMERICAL_ISSUE, "NUMERICAL_ISSUE", 0, "Numerical Issue", "The provided data did not satisfy the prerequisites"},
|
||||
{BPH_SOLVER_NO_CONVERGENCE, "NO_CONVERGENCE", 0, "No Convergence", "Iterative procedure did not converge"},
|
||||
{BPH_SOLVER_INVALID_INPUT, "INVALID_INPUT", 0, "Invalid Input", "The inputs are invalid, or the algorithm has been improperly called"},
|
||||
{0, NULL, 0, NULL, NULL}
|
||||
};
|
||||
|
||||
srna = RNA_def_struct(brna, "ClothSolverResult", NULL);
|
||||
RNA_def_struct_ui_text(srna, "Solver Result", "Result of cloth solver iteration");
|
||||
|
||||
RNA_define_verify_sdna(0);
|
||||
|
||||
prop = RNA_def_property(srna, "status", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_enum_items(prop, status_items);
|
||||
RNA_def_property_enum_sdna(prop, NULL, "status");
|
||||
RNA_def_property_flag(prop, PROP_ENUM_FLAG);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(prop, "Status", "Status of the solver iteration");
|
||||
|
||||
prop = RNA_def_property(srna, "max_error", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_float_sdna(prop, NULL, "max_error");
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(prop, "Maximum Error", "Maximum error during substeps");
|
||||
|
||||
prop = RNA_def_property(srna, "min_error", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_float_sdna(prop, NULL, "min_error");
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(prop, "Minimum Error", "Minimum error during substeps");
|
||||
|
||||
prop = RNA_def_property(srna, "avg_error", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_float_sdna(prop, NULL, "avg_error");
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(prop, "Average Error", "Average error during substeps");
|
||||
|
||||
prop = RNA_def_property(srna, "max_iterations", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, NULL, "max_iterations");
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(prop, "Maximum Iterations", "Maximum iterations during substeps");
|
||||
|
||||
prop = RNA_def_property(srna, "min_iterations", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_int_sdna(prop, NULL, "min_iterations");
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(prop, "Minimum Iterations", "Minimum iterations during substeps");
|
||||
|
||||
prop = RNA_def_property(srna, "avg_iterations", PROP_FLOAT, PROP_NONE);
|
||||
RNA_def_property_float_sdna(prop, NULL, "avg_iterations");
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_ui_text(prop, "Average Iterations", "Average iterations during substeps");
|
||||
|
||||
RNA_define_verify_sdna(1);
|
||||
}
|
||||
|
||||
static void rna_def_cloth_sim_settings(BlenderRNA *brna)
|
||||
{
|
||||
StructRNA *srna;
|
||||
@ -660,6 +720,7 @@ static void rna_def_cloth_collision_settings(BlenderRNA *brna)
|
||||
|
||||
void RNA_def_cloth(BlenderRNA *brna)
|
||||
{
|
||||
rna_def_cloth_solver_result(brna);
|
||||
rna_def_cloth_sim_settings(brna);
|
||||
rna_def_cloth_collision_settings(brna);
|
||||
}
|
||||
|
@ -2488,6 +2488,11 @@ static void rna_def_modifier_cloth(BlenderRNA *brna)
|
||||
RNA_def_property_pointer_sdna(prop, NULL, "coll_parms");
|
||||
RNA_def_property_ui_text(prop, "Cloth Collision Settings", "");
|
||||
|
||||
prop = RNA_def_property(srna, "solver_result", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_struct_type(prop, "ClothSolverResult");
|
||||
RNA_def_property_pointer_sdna(prop, NULL, "solver_result");
|
||||
RNA_def_property_ui_text(prop, "Solver Result", "");
|
||||
|
||||
prop = RNA_def_property(srna, "point_cache", PROP_POINTER, PROP_NONE);
|
||||
RNA_def_property_flag(prop, PROP_NEVER_NULL);
|
||||
RNA_def_property_ui_text(prop, "Point Cache", "");
|
||||
|
@ -179,6 +179,7 @@ static void copyData(ModifierData *md, ModifierData *target)
|
||||
tclmd->point_cache->step = 1;
|
||||
tclmd->clothObject = NULL;
|
||||
tclmd->roots = NULL;
|
||||
tclmd->solver_result = NULL;
|
||||
tclmd->debug_data = NULL;
|
||||
}
|
||||
|
||||
@ -211,6 +212,9 @@ static void freeData(ModifierData *md)
|
||||
if (clmd->roots)
|
||||
MEM_freeN(clmd->roots);
|
||||
|
||||
if (clmd->solver_result)
|
||||
MEM_freeN(clmd->solver_result);
|
||||
|
||||
BKE_sim_debug_data_free(clmd->debug_data);
|
||||
}
|
||||
}
|
||||
|
@ -35,9 +35,18 @@ extern "C" {
|
||||
struct Implicit_Data;
|
||||
struct ClothModifierData;
|
||||
|
||||
typedef enum eMassSpringSolverStatus {
|
||||
BPH_SOLVER_SUCCESS = (1 << 0),
|
||||
BPH_SOLVER_NUMERICAL_ISSUE = (1 << 1),
|
||||
BPH_SOLVER_NO_CONVERGENCE = (1 << 2),
|
||||
BPH_SOLVER_INVALID_INPUT = (1 << 3),
|
||||
} eMassSpringSolverStatus;
|
||||
|
||||
struct Implicit_Data *BPH_mass_spring_solver_create(int numverts, int numsprings);
|
||||
void BPH_mass_spring_solver_free(struct Implicit_Data *id);
|
||||
|
||||
const struct MassSpringSolverResult *BPH_mass_spring_solver_result(struct Implicit_Data *data);
|
||||
|
||||
int BPH_cloth_solver_init(struct Object *ob, struct ClothModifierData *clmd);
|
||||
void BPH_cloth_solver_free(struct ClothModifierData *clmd);
|
||||
int BPH_cloth_solve(struct Object *ob, float frame, struct ClothModifierData *clmd, struct ListBase *effectors);
|
||||
|
@ -530,6 +530,46 @@ static void cloth_calc_force(ClothModifierData *clmd, float UNUSED(frame), ListB
|
||||
}
|
||||
}
|
||||
|
||||
static void cloth_clear_result(ClothModifierData *clmd)
|
||||
{
|
||||
ClothSolverResult *sres = clmd->solver_result;
|
||||
|
||||
sres->status = 0;
|
||||
sres->max_error = sres->min_error = sres->avg_error = 0.0f;
|
||||
sres->max_iterations = sres->min_iterations = 0;
|
||||
sres->avg_iterations = 0.0f;
|
||||
}
|
||||
|
||||
static void cloth_record_result(ClothModifierData *clmd, ImplicitSolverResult *result, int steps)
|
||||
{
|
||||
ClothSolverResult *sres = clmd->solver_result;
|
||||
|
||||
if (sres->status) { /* already initialized ? */
|
||||
/* error only makes sense for successful iterations */
|
||||
if (result->status == BPH_SOLVER_SUCCESS) {
|
||||
sres->min_error = min_ff(sres->min_error, result->error);
|
||||
sres->max_error = max_ff(sres->max_error, result->error);
|
||||
sres->avg_error += result->error / (float)steps;
|
||||
}
|
||||
|
||||
sres->min_iterations = min_ii(sres->min_iterations, result->iterations);
|
||||
sres->max_iterations = max_ii(sres->max_iterations, result->iterations);
|
||||
sres->avg_iterations += (float)result->iterations / (float)steps;
|
||||
}
|
||||
else {
|
||||
/* error only makes sense for successful iterations */
|
||||
if (result->status == BPH_SOLVER_SUCCESS) {
|
||||
sres->min_error = sres->max_error = result->error;
|
||||
sres->avg_error += result->error / (float)steps;
|
||||
}
|
||||
|
||||
sres->min_iterations = sres->max_iterations = result->iterations;
|
||||
sres->avg_iterations += (float)result->iterations / (float)steps;
|
||||
}
|
||||
|
||||
sres->status |= result->status;
|
||||
}
|
||||
|
||||
int BPH_cloth_solve(Object *ob, float frame, ClothModifierData *clmd, ListBase *effectors)
|
||||
{
|
||||
unsigned int i=0;
|
||||
@ -546,6 +586,10 @@ int BPH_cloth_solve(Object *ob, float frame, ClothModifierData *clmd, ListBase *
|
||||
|
||||
BKE_sim_debug_data_clear_category(clmd->debug_data, "collision");
|
||||
|
||||
if (!clmd->solver_result)
|
||||
clmd->solver_result = (ClothSolverResult *)MEM_callocN(sizeof(ClothSolverResult), "cloth solver result");
|
||||
cloth_clear_result(clmd);
|
||||
|
||||
if (clmd->sim_parms->flags & CLOTH_SIMSETTINGS_FLAG_GOAL) { /* do goal stuff */
|
||||
for (i = 0; i < numverts; i++) {
|
||||
// update velocities with constrained velocities from pinned verts
|
||||
@ -565,6 +609,7 @@ int BPH_cloth_solve(Object *ob, float frame, ClothModifierData *clmd, ListBase *
|
||||
}
|
||||
|
||||
while (step < tf) {
|
||||
ImplicitSolverResult result;
|
||||
|
||||
/* copy velocities for collision */
|
||||
for (i = 0; i < numverts; i++) {
|
||||
@ -597,7 +642,8 @@ int BPH_cloth_solve(Object *ob, float frame, ClothModifierData *clmd, ListBase *
|
||||
cloth_calc_force(clmd, frame, effectors, step);
|
||||
|
||||
// calculate new velocity and position
|
||||
BPH_mass_spring_solve(id, dt);
|
||||
BPH_mass_spring_solve(id, dt, &result);
|
||||
cloth_record_result(clmd, &result, clmd->sim_parms->stepsPerFrame);
|
||||
|
||||
BPH_mass_spring_apply_result(id);
|
||||
|
||||
|
@ -61,6 +61,13 @@ extern "C" {
|
||||
struct Implicit_Data;
|
||||
struct SimDebugData;
|
||||
|
||||
typedef struct ImplicitSolverResult {
|
||||
int status;
|
||||
|
||||
int iterations;
|
||||
float error;
|
||||
} ImplicitSolverResult;
|
||||
|
||||
BLI_INLINE void implicit_print_matrix_elem(float v)
|
||||
{
|
||||
printf("%-8.3f", v);
|
||||
@ -118,7 +125,7 @@ void BPH_mass_spring_add_constraint_ndof0(struct Implicit_Data *data, int index,
|
||||
void BPH_mass_spring_add_constraint_ndof1(struct Implicit_Data *data, int index, const float c1[3], const float c2[3], const float dV[3]);
|
||||
void BPH_mass_spring_add_constraint_ndof2(struct Implicit_Data *data, int index, const float c1[3], const float dV[3]);
|
||||
|
||||
bool BPH_mass_spring_solve(struct Implicit_Data *data, float dt);
|
||||
bool BPH_mass_spring_solve(struct Implicit_Data *data, float dt, struct ImplicitSolverResult *result);
|
||||
void BPH_mass_spring_apply_result(struct Implicit_Data *data);
|
||||
|
||||
/* Clear the force vector at the beginning of the time step */
|
||||
|
@ -834,7 +834,7 @@ static int cg_filtered(lfVector *ldV, fmatrix3x3 *lA, lfVector *lB, lfVector *z
|
||||
}
|
||||
#endif
|
||||
|
||||
static int cg_filtered(lfVector *ldV, fmatrix3x3 *lA, lfVector *lB, lfVector *z, fmatrix3x3 *S)
|
||||
static int cg_filtered(lfVector *ldV, fmatrix3x3 *lA, lfVector *lB, lfVector *z, fmatrix3x3 *S, ImplicitSolverResult *result)
|
||||
{
|
||||
// Solves for unknown X in equation AX=B
|
||||
unsigned int conjgrad_loopcount=0, conjgrad_looplimit=100;
|
||||
@ -847,14 +847,15 @@ static int cg_filtered(lfVector *ldV, fmatrix3x3 *lA, lfVector *lB, lfVector *z,
|
||||
lfVector *c = create_lfvector(numverts);
|
||||
lfVector *q = create_lfvector(numverts);
|
||||
lfVector *s = create_lfvector(numverts);
|
||||
float delta_new, delta_old, delta_target, alpha;
|
||||
float bnorm2, delta_new, delta_old, delta_target, alpha;
|
||||
|
||||
cp_lfvector(ldV, z, numverts);
|
||||
|
||||
/* d0 = filter(B)^T * P * filter(B) */
|
||||
cp_lfvector(fB, lB, numverts);
|
||||
filter(fB, S);
|
||||
delta_target = conjgrad_epsilon*conjgrad_epsilon * dot_lfvector(fB, fB, numverts);
|
||||
bnorm2 = dot_lfvector(fB, fB, numverts);
|
||||
delta_target = conjgrad_epsilon*conjgrad_epsilon * bnorm2;
|
||||
|
||||
/* r = filter(B - A * dV) */
|
||||
mul_bfmatrix_lfvector(AdV, lA, ldV);
|
||||
@ -914,6 +915,10 @@ static int cg_filtered(lfVector *ldV, fmatrix3x3 *lA, lfVector *lB, lfVector *z,
|
||||
del_lfvector(s);
|
||||
// printf("W/O conjgrad_loopcount: %d\n", conjgrad_loopcount);
|
||||
|
||||
result->status = conjgrad_loopcount < conjgrad_looplimit ? BPH_SOLVER_SUCCESS : BPH_SOLVER_NO_CONVERGENCE;
|
||||
result->iterations = conjgrad_loopcount;
|
||||
result->error = bnorm2 > 0.0f ? sqrt(delta_new / bnorm2) : 0.0f;
|
||||
|
||||
return conjgrad_loopcount < conjgrad_looplimit; // true means we reached desired accuracy in given time - ie stable
|
||||
}
|
||||
|
||||
@ -1105,10 +1110,9 @@ static int cg_filtered_pre(lfVector *dv, fmatrix3x3 *lA, lfVector *lB, lfVector
|
||||
}
|
||||
#endif
|
||||
|
||||
bool BPH_mass_spring_solve(Implicit_Data *data, float dt)
|
||||
bool BPH_mass_spring_solve(Implicit_Data *data, float dt, ImplicitSolverResult *result)
|
||||
{
|
||||
unsigned int numverts = data->dFdV[0].vcount;
|
||||
bool ok;
|
||||
|
||||
lfVector *dFdXmV = create_lfvector(numverts);
|
||||
zero_lfvector(data->dV, numverts);
|
||||
@ -1123,7 +1127,7 @@ bool BPH_mass_spring_solve(Implicit_Data *data, float dt)
|
||||
|
||||
// itstart();
|
||||
|
||||
ok = cg_filtered(data->dV, data->A, data->B, data->z, data->S); /* conjugate gradient algorithm to solve Ax=b */
|
||||
cg_filtered(data->dV, data->A, data->B, data->z, data->S, result); /* conjugate gradient algorithm to solve Ax=b */
|
||||
// cg_filtered_pre(id->dV, id->A, id->B, id->z, id->S, id->P, id->Pinv, id->bigI);
|
||||
|
||||
// itend();
|
||||
@ -1136,7 +1140,7 @@ bool BPH_mass_spring_solve(Implicit_Data *data, float dt)
|
||||
|
||||
del_lfvector(dFdXmV);
|
||||
|
||||
return ok;
|
||||
return result->status == BPH_SOLVER_SUCCESS;
|
||||
}
|
||||
|
||||
void BPH_mass_spring_apply_result(Implicit_Data *data)
|
||||
|
Loading…
Reference in New Issue
Block a user