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:
Lukas Tönne 2014-09-20 21:05:46 +02:00
parent 00bb836e17
commit 491e7493c7
12 changed files with 176 additions and 9 deletions

@ -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)