reverted part of cloth.c and modifier.c back to old code before switch to use "deform_only" modifier (not tested, needs cleanup but compiles)
This commit is contained in:
@@ -120,13 +120,12 @@ static CM_SOLVER_DEF solvers [] =
|
||||
/* ********** cloth engine ******* */
|
||||
/* Prototypes for internal functions.
|
||||
*/
|
||||
static void cloth_to_object ( Object *ob, ClothModifierData *clmd, DerivedMesh *dm );
|
||||
static void cloth_from_mesh ( Object *ob, ClothModifierData *clmd, DerivedMesh *dm );
|
||||
static int cloth_from_object ( Object *ob, ClothModifierData *clmd, DerivedMesh *dm );
|
||||
static void cloth_to_object (Object *ob, DerivedMesh *dm, ClothModifierData *clmd);
|
||||
static void cloth_from_mesh (Object *ob, ClothModifierData *clmd, DerivedMesh *dm, float framenr);
|
||||
static int cloth_from_object(Object *ob, ClothModifierData *clmd, DerivedMesh *dm, DerivedMesh *olddm, float framenr);
|
||||
static int collobj_from_object(Object *ob, ClothModifierData *clmd, DerivedMesh *dm, DerivedMesh *olddm, float framenr);
|
||||
int cloth_build_springs ( Cloth *cloth, DerivedMesh *dm );
|
||||
static void cloth_apply_vgroup ( ClothModifierData *clmd, DerivedMesh *dm, short vgroup );
|
||||
|
||||
|
||||
static void cloth_apply_vgroup(ClothModifierData *clmd, DerivedMesh *dm, short vgroup);
|
||||
/******************************************************************************
|
||||
*
|
||||
* External interface called by modifier.c clothModifier functions.
|
||||
@@ -136,9 +135,9 @@ static void cloth_apply_vgroup ( ClothModifierData *clmd, DerivedMesh *dm, short
|
||||
* cloth_init - creates a new cloth simulation.
|
||||
*
|
||||
* 1. create object
|
||||
* 2. fill object with standard values or with the GUI settings if given
|
||||
* 2. fill object with standard values or with the GUI settings if given
|
||||
*/
|
||||
void cloth_init ( ClothModifierData *clmd )
|
||||
void cloth_init (ClothModifierData *clmd)
|
||||
{
|
||||
/* Initialize our new data structure to reasonable values. */
|
||||
clmd->sim_parms.gravity [0] = 0.0;
|
||||
@@ -153,17 +152,14 @@ void cloth_init ( ClothModifierData *clmd )
|
||||
clmd->sim_parms.stepsPerFrame = 5;
|
||||
clmd->sim_parms.sim_time = 1.0;
|
||||
clmd->sim_parms.flags = CLOTH_SIMSETTINGS_FLAG_RESET;
|
||||
clmd->sim_parms.solver_type = 0;
|
||||
clmd->sim_parms.solver_type = 0;
|
||||
clmd->sim_parms.preroll = 0;
|
||||
clmd->sim_parms.maxspringlen = 10;
|
||||
clmd->sim_parms.firstframe = 1;
|
||||
clmd->sim_parms.lastframe = 250;
|
||||
clmd->coll_parms.self_friction = 5.0;
|
||||
clmd->coll_parms.friction = 10.0;
|
||||
clmd->coll_parms.loop_count = 1;
|
||||
clmd->coll_parms.epsilon = 0.01f;
|
||||
clmd->coll_parms.flags = 0;
|
||||
|
||||
|
||||
/* These defaults are copied from softbody.c's
|
||||
* softbody_calc_forces() function.
|
||||
*/
|
||||
@@ -181,17 +177,17 @@ void cloth_init ( ClothModifierData *clmd )
|
||||
}
|
||||
|
||||
// unused in the moment, cloth needs quads from mesh
|
||||
DerivedMesh *CDDM_convert_to_triangle ( DerivedMesh *dm )
|
||||
DerivedMesh *CDDM_convert_to_triangle(DerivedMesh *dm)
|
||||
{
|
||||
DerivedMesh *result = NULL;
|
||||
int i;
|
||||
int numverts = dm->getNumVerts ( dm );
|
||||
int numedges = dm->getNumEdges ( dm );
|
||||
int numfaces = dm->getNumFaces ( dm );
|
||||
int numverts = dm->getNumVerts(dm);
|
||||
int numedges = dm->getNumEdges(dm);
|
||||
int numfaces = dm->getNumFaces(dm);
|
||||
|
||||
MVert *mvert = CDDM_get_verts ( dm );
|
||||
MEdge *medge = CDDM_get_edges ( dm );
|
||||
MFace *mface = CDDM_get_faces ( dm );
|
||||
MVert *mvert = CDDM_get_verts(dm);
|
||||
MEdge *medge = CDDM_get_edges(dm);
|
||||
MFace *mface = CDDM_get_faces(dm);
|
||||
|
||||
MVert *mvert2;
|
||||
MFace *mface2;
|
||||
@@ -203,36 +199,36 @@ DerivedMesh *CDDM_convert_to_triangle ( DerivedMesh *dm )
|
||||
float vec1[3], vec2[3], vec3[3], vec4[3], vec5[3];
|
||||
float mag1=0, mag2=0;
|
||||
|
||||
for ( i = 0; i < numfaces; i++ )
|
||||
for(i = 0; i < numfaces; i++)
|
||||
{
|
||||
if ( mface[i].v4 )
|
||||
if(mface[i].v4)
|
||||
numquads++;
|
||||
else
|
||||
numtris++;
|
||||
numtris++;
|
||||
}
|
||||
|
||||
result = CDDM_from_template ( dm, numverts, 0, numtris + 2*numquads );
|
||||
result = CDDM_from_template(dm, numverts, 0, numtris + 2*numquads);
|
||||
|
||||
if ( !result )
|
||||
if(!result)
|
||||
return NULL;
|
||||
|
||||
// do verts
|
||||
mvert2 = CDDM_get_verts ( result );
|
||||
for ( a=0; a<numverts; a++ )
|
||||
mvert2 = CDDM_get_verts(result);
|
||||
for(a=0; a<numverts; a++)
|
||||
{
|
||||
MVert *inMV;
|
||||
MVert *mv = &mvert2[a];
|
||||
|
||||
inMV = &mvert[a];
|
||||
|
||||
DM_copy_vert_data ( dm, result, a, a, 1 );
|
||||
DM_copy_vert_data(dm, result, a, a, 1);
|
||||
*mv = *inMV;
|
||||
}
|
||||
|
||||
|
||||
// do faces
|
||||
mface2 = CDDM_get_faces ( result );
|
||||
for ( a=0, i=0; a<numfaces; a++ )
|
||||
mface2 = CDDM_get_faces(result);
|
||||
for(a=0, i=0; a<numfaces; a++)
|
||||
{
|
||||
MFace *mf = &mface2[i];
|
||||
MFace *inMF;
|
||||
@@ -244,7 +240,7 @@ DerivedMesh *CDDM_convert_to_triangle ( DerivedMesh *dm )
|
||||
*mf = *inMF;
|
||||
*/
|
||||
|
||||
if ( mface[a].v4 && random==1 )
|
||||
if(mface[a].v4 && random==1)
|
||||
{
|
||||
mf->v1 = mface[a].v2;
|
||||
mf->v2 = mface[a].v3;
|
||||
@@ -260,9 +256,9 @@ DerivedMesh *CDDM_convert_to_triangle ( DerivedMesh *dm )
|
||||
mf->v4 = 0;
|
||||
mf->flag |= ME_SMOOTH;
|
||||
|
||||
test_index_face ( mf, NULL, 0, 3 );
|
||||
test_index_face(mf, NULL, 0, 3);
|
||||
|
||||
if ( mface[a].v4 )
|
||||
if(mface[a].v4)
|
||||
{
|
||||
MFace *mf2;
|
||||
|
||||
@@ -275,7 +271,7 @@ DerivedMesh *CDDM_convert_to_triangle ( DerivedMesh *dm )
|
||||
*mf2 = *inMF;
|
||||
*/
|
||||
|
||||
if ( random==1 )
|
||||
if(random==1)
|
||||
{
|
||||
mf2->v1 = mface[a].v1;
|
||||
mf2->v2 = mface[a].v2;
|
||||
@@ -290,31 +286,32 @@ DerivedMesh *CDDM_convert_to_triangle ( DerivedMesh *dm )
|
||||
mf2->v4 = 0;
|
||||
mf2->flag |= ME_SMOOTH;
|
||||
|
||||
test_index_face ( mf2, NULL, 0, 3 );
|
||||
test_index_face(mf2, NULL, 0, 3);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
CDDM_calc_edges ( result );
|
||||
CDDM_calc_normals ( result );
|
||||
CDDM_calc_edges(result);
|
||||
CDDM_calc_normals(result);
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
DerivedMesh *CDDM_create_tearing ( ClothModifierData *clmd, DerivedMesh *dm )
|
||||
DerivedMesh *CDDM_create_tearing(ClothModifierData *clmd, DerivedMesh *dm)
|
||||
{
|
||||
/*
|
||||
DerivedMesh *result = NULL;
|
||||
unsigned int i = 0, a = 0, j=0;
|
||||
int numverts = dm->getNumVerts ( dm );
|
||||
int numedges = dm->getNumEdges ( dm );
|
||||
int numfaces = dm->getNumFaces ( dm );
|
||||
int numverts = dm->getNumVerts(dm);
|
||||
int numedges = dm->getNumEdges(dm);
|
||||
int numfaces = dm->getNumFaces(dm);
|
||||
|
||||
MVert *mvert = CDDM_get_verts ( dm );
|
||||
MEdge *medge = CDDM_get_edges ( dm );
|
||||
MFace *mface = CDDM_get_faces ( dm );
|
||||
MVert *mvert = CDDM_get_verts(dm);
|
||||
MEdge *medge = CDDM_get_edges(dm);
|
||||
MFace *mface = CDDM_get_faces(dm);
|
||||
|
||||
MVert *mvert2;
|
||||
MFace *mface2;
|
||||
@@ -324,79 +321,80 @@ DerivedMesh *CDDM_create_tearing ( ClothModifierData *clmd, DerivedMesh *dm )
|
||||
Cloth *cloth = clmd->clothObject;
|
||||
ClothSpring *springs = cloth->springs;
|
||||
unsigned int numsprings = cloth->numsprings;
|
||||
|
||||
|
||||
// create spring tearing hash
|
||||
edgehash = BLI_edgehash_new();
|
||||
|
||||
for ( i = 0; i < numsprings; i++ )
|
||||
|
||||
for(i = 0; i < numsprings; i++)
|
||||
{
|
||||
if ( ( springs[i].flags & CLOTH_SPRING_FLAG_DEACTIVATE )
|
||||
&& ( !BLI_edgehash_haskey ( edgehash, springs[i].ij, springs[i].kl ) ) )
|
||||
if((springs[i].flags & CSPRING_FLAG_DEACTIVATE)
|
||||
&&(!BLI_edgehash_haskey(edgehash, springs[i].ij, springs[i].kl)))
|
||||
{
|
||||
BLI_edgehash_insert ( edgehash, springs[i].ij, springs[i].kl, NULL );
|
||||
BLI_edgehash_insert ( edgehash, springs[i].kl, springs[i].ij, NULL );
|
||||
BLI_edgehash_insert(edgehash, springs[i].ij, springs[i].kl, NULL);
|
||||
BLI_edgehash_insert(edgehash, springs[i].kl, springs[i].ij, NULL);
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// printf("found %d tears\n", j);
|
||||
|
||||
result = CDDM_from_template(dm, numverts, 0, numfaces);
|
||||
|
||||
result = CDDM_from_template ( dm, numverts, 0, numfaces );
|
||||
|
||||
if ( !result )
|
||||
if(!result)
|
||||
return NULL;
|
||||
|
||||
// do verts
|
||||
mvert2 = CDDM_get_verts ( result );
|
||||
for ( a=0; a<numverts; a++ )
|
||||
mvert2 = CDDM_get_verts(result);
|
||||
for(a=0; a<numverts; a++)
|
||||
{
|
||||
MVert *inMV;
|
||||
MVert *mv = &mvert2[a];
|
||||
|
||||
inMV = &mvert[a];
|
||||
|
||||
DM_copy_vert_data ( dm, result, a, a, 1 );
|
||||
DM_copy_vert_data(dm, result, a, a, 1);
|
||||
*mv = *inMV;
|
||||
}
|
||||
|
||||
|
||||
// do faces
|
||||
mface2 = CDDM_get_faces ( result );
|
||||
for ( a=0, i=0; a<numfaces; a++ )
|
||||
mface2 = CDDM_get_faces(result);
|
||||
for(a=0, i=0; a<numfaces; a++)
|
||||
{
|
||||
MFace *mf = &mface2[i];
|
||||
MFace *inMF;
|
||||
inMF = &mface[a];
|
||||
|
||||
/*
|
||||
DM_copy_face_data(dm, result, a, i, 1);
|
||||
|
||||
// DM_copy_face_data(dm, result, a, i, 1);
|
||||
|
||||
*mf = *inMF;
|
||||
*/
|
||||
|
||||
if ( ( !BLI_edgehash_haskey ( edgehash, mface[a].v1, mface[a].v2 ) )
|
||||
&& ( !BLI_edgehash_haskey ( edgehash, mface[a].v2, mface[a].v3 ) )
|
||||
&& ( !BLI_edgehash_haskey ( edgehash, mface[a].v3, mface[a].v4 ) )
|
||||
&& ( !BLI_edgehash_haskey ( edgehash, mface[a].v4, mface[a].v1 ) ) )
|
||||
// *mf = *inMF;
|
||||
|
||||
|
||||
if((!BLI_edgehash_haskey(edgehash, mface[a].v1, mface[a].v2))
|
||||
&&(!BLI_edgehash_haskey(edgehash, mface[a].v2, mface[a].v3))
|
||||
&&(!BLI_edgehash_haskey(edgehash, mface[a].v3, mface[a].v4))
|
||||
&&(!BLI_edgehash_haskey(edgehash, mface[a].v4, mface[a].v1)))
|
||||
{
|
||||
mf->v1 = mface[a].v1;
|
||||
mf->v2 = mface[a].v2;
|
||||
mf->v3 = mface[a].v3;
|
||||
mf->v4 = mface[a].v4;
|
||||
|
||||
test_index_face ( mf, NULL, 0, 4 );
|
||||
|
||||
|
||||
test_index_face(mf, NULL, 0, 4);
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
CDDM_lower_num_faces ( result, i );
|
||||
CDDM_calc_edges ( result );
|
||||
CDDM_calc_normals ( result );
|
||||
|
||||
BLI_edgehash_free ( edgehash, NULL );
|
||||
CDDM_lower_num_faces(result, i);
|
||||
CDDM_calc_edges(result);
|
||||
CDDM_calc_normals(result);
|
||||
|
||||
BLI_edgehash_free(edgehash, NULL);
|
||||
|
||||
return result;
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@@ -600,79 +598,54 @@ void cloth_cache_free ( ClothModifierData *clmd, float time )
|
||||
|
||||
/**
|
||||
* cloth_deform_verts - simulates one step, framenr is in frames.
|
||||
*
|
||||
*
|
||||
**/
|
||||
DerivedMesh *clothModifier_do(ClothModifierData *clmd,
|
||||
Object *ob, DerivedMesh *dm)
|
||||
DerivedMesh *clothModifier_do(ClothModifierData *clmd,Object *ob, DerivedMesh *dm, int useRenderParams, int isFinalCalc)
|
||||
{
|
||||
unsigned int i;
|
||||
DerivedMesh *result = NULL;
|
||||
unsigned int numverts = -1;
|
||||
unsigned int numedges = -1;
|
||||
unsigned int numfaces = -1;
|
||||
MVert *mvert = NULL;
|
||||
MEdge *medge = NULL;
|
||||
MFace *mface = NULL;
|
||||
DerivedMesh *result = NULL, *result2 = NULL;
|
||||
Cloth *cloth = clmd->clothObject;
|
||||
unsigned int framenr = ( float ) G.scene->r.cfra;
|
||||
float current_time = bsystem_time ( ob, ( float ) G.scene->r.cfra, 0.0 );
|
||||
ListBase *effectors = NULL;
|
||||
ClothVertex *verts = NULL;
|
||||
unsigned int framenr = (float)G.scene->r.cfra;
|
||||
float current_time = bsystem_time(ob, (float)G.scene->r.cfra, 0.0);
|
||||
ListBase *effectors = NULL;
|
||||
ClothVertex *newframe= NULL;
|
||||
Frame *frame = NULL;
|
||||
LinkNode *search = NULL;
|
||||
float deltaTime = current_time - clmd->sim_parms.sim_time;
|
||||
MVert *mverts = NULL;
|
||||
|
||||
result = CDDM_copy(dm);
|
||||
|
||||
// only be active during a specific period:
|
||||
// that's "first frame" and "last frame" on GUI
|
||||
if ( clmd->clothObject )
|
||||
{
|
||||
if ( clmd->sim_parms.cache )
|
||||
{
|
||||
if ( current_time < clmd->sim_parms.firstframe )
|
||||
{
|
||||
int frametime = cloth_cache_first_frame ( clmd );
|
||||
if ( cloth_cache_search_frame ( clmd, frametime ) )
|
||||
{
|
||||
cloth_cache_get_frame ( clmd, frametime );
|
||||
cloth_to_object ( ob, clmd, result );
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if ( current_time > clmd->sim_parms.lastframe )
|
||||
{
|
||||
int frametime = cloth_cache_last_frame ( clmd );
|
||||
if ( cloth_cache_search_frame ( clmd, frametime ) )
|
||||
{
|
||||
cloth_cache_get_frame ( clmd, frametime );
|
||||
cloth_to_object ( ob, clmd, result );
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if ( ABS ( deltaTime ) >= 2.0f ) // no timewarps allowed
|
||||
{
|
||||
if ( cloth_cache_search_frame ( clmd, framenr ) )
|
||||
{
|
||||
cloth_cache_get_frame ( clmd, framenr );
|
||||
cloth_to_object ( ob, clmd, result );
|
||||
}
|
||||
clmd->sim_parms.sim_time = current_time;
|
||||
return;
|
||||
}
|
||||
}
|
||||
float deltaTime = current_time - clmd->sim_parms.sim_time;
|
||||
|
||||
clmd->sim_parms.dt = 1.0f / (clmd->sim_parms.stepsPerFrame * G.scene->r.frs_sec);
|
||||
|
||||
result = CDDM_copy(dm);
|
||||
|
||||
if(!result)
|
||||
{
|
||||
return dm;
|
||||
}
|
||||
|
||||
// unused in the moment, calculated seperately in implicit.c
|
||||
clmd->sim_parms.dt = 1.0f / clmd->sim_parms.stepsPerFrame;
|
||||
|
||||
numverts = result->getNumVerts(result);
|
||||
numedges = result->getNumEdges(result);
|
||||
numfaces = result->getNumFaces(result);
|
||||
mvert = CDDM_get_verts(result);
|
||||
medge = CDDM_get_edges(result);
|
||||
mface = CDDM_get_faces(result);
|
||||
|
||||
clmd->sim_parms.sim_time = current_time;
|
||||
|
||||
if ( deltaTime == 1.0f )
|
||||
if(deltaTime == 1.0f)
|
||||
{
|
||||
if ( ( clmd->clothObject == NULL ) || ( dm->getNumVerts(dm) != clmd->clothObject->numverts ) )
|
||||
if ((clmd->clothObject == NULL) || (numverts != clmd->clothObject->numverts) )
|
||||
{
|
||||
if ( !cloth_from_object ( ob, clmd, dm ) )
|
||||
return;
|
||||
if(!cloth_from_object (ob, clmd, result, dm, framenr))
|
||||
return result;
|
||||
|
||||
if ( clmd->clothObject == NULL )
|
||||
return;
|
||||
if(clmd->clothObject == NULL)
|
||||
return result;
|
||||
|
||||
cloth = clmd->clothObject;
|
||||
}
|
||||
@@ -680,47 +653,56 @@ DerivedMesh *clothModifier_do(ClothModifierData *clmd,
|
||||
clmd->clothObject->old_solver_type = clmd->sim_parms.solver_type;
|
||||
|
||||
// Insure we have a clmd->clothObject, in case allocation failed.
|
||||
if ( clmd->clothObject != NULL )
|
||||
{
|
||||
if ( !cloth_cache_search_frame ( clmd, framenr ) )
|
||||
{
|
||||
verts = cloth->verts;
|
||||
mverts = dm->getVertArray(dm);
|
||||
if (clmd->clothObject != NULL)
|
||||
{
|
||||
|
||||
cloth->mfaces = mface; // update face pointer
|
||||
((BVH *)cloth->tree)->mfaces = mface;
|
||||
cloth->numfaces = numfaces;
|
||||
((BVH *)cloth->tree)->numfaces = numfaces;
|
||||
|
||||
// Force any pinned verts to their constrained location.
|
||||
for ( i = 0; i < clmd->clothObject->numverts; i++, verts++ )
|
||||
if(!cloth_cache_search_frame(clmd, framenr))
|
||||
{
|
||||
/* Force any pinned verts to their constrained location. */
|
||||
for (i = 0; i < clmd->clothObject->numverts; i++)
|
||||
{
|
||||
// Save the previous position.
|
||||
VECCOPY ( verts->xold, verts->xconst );
|
||||
VECCOPY ( verts->txold, verts->x );
|
||||
// Get the current position.
|
||||
VECCOPY ( verts->xconst, mverts[i].co );
|
||||
Mat4MulVecfl ( ob->obmat, verts->xconst );
|
||||
/* Save the previous position. */
|
||||
VECCOPY (cloth->verts [i].xold, cloth->verts [i].xconst);
|
||||
VECCOPY (cloth->verts [i].txold, cloth->verts [i].x);
|
||||
|
||||
/* Get the current position. */
|
||||
VECCOPY (cloth->verts [i].xconst, mvert[i].co);
|
||||
Mat4MulVecfl(ob->obmat, cloth->verts [i].xconst);
|
||||
|
||||
/* Compute the vertices velocity. */
|
||||
VECSUB (cloth->verts [i].v, cloth->verts [i].x, cloth->verts [i].xold);
|
||||
}
|
||||
|
||||
tstart();
|
||||
/*
|
||||
// Call the solver.
|
||||
if ( solvers [clmd->sim_parms.solver_type].solver )
|
||||
solvers [clmd->sim_parms.solver_type].solver ( ob, framenr, clmd, effectors );
|
||||
*/
|
||||
tend();
|
||||
printf ( "Cloth simulation time: %f\n", ( float ) tval() );
|
||||
|
||||
cloth_cache_set_frame ( clmd, framenr );
|
||||
/* Call the solver. */
|
||||
|
||||
if (solvers [clmd->sim_parms.solver_type].solver)
|
||||
solvers [clmd->sim_parms.solver_type].solver (ob, framenr, clmd, effectors);
|
||||
|
||||
tend();
|
||||
|
||||
// printf("Cloth simulation time: %f\n", (float)tval());
|
||||
|
||||
cloth_cache_set_frame(clmd, framenr);
|
||||
|
||||
}
|
||||
else // just retrieve the cached frame
|
||||
{
|
||||
cloth_cache_get_frame ( clmd, framenr );
|
||||
cloth_cache_get_frame(clmd, framenr);
|
||||
}
|
||||
|
||||
// Copy the result back to the object.
|
||||
cloth_to_object ( ob, clmd, result );
|
||||
|
||||
cloth_to_object (ob, result, clmd);
|
||||
|
||||
// bvh_free(clmd->clothObject->tree);
|
||||
// clmd->clothObject->tree = bvh_build(clmd, clmd->coll_parms.epsilon);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if ( ( deltaTime <= 0.0f ) || ( deltaTime > 1.0f ) )
|
||||
@@ -729,47 +711,44 @@ DerivedMesh *clothModifier_do(ClothModifierData *clmd,
|
||||
{
|
||||
if ( cloth_cache_search_frame ( clmd, framenr ) )
|
||||
{
|
||||
cloth_cache_get_frame ( clmd, framenr );
|
||||
cloth_to_object ( ob, clmd, result );
|
||||
cloth_cache_get_frame(clmd, framenr);
|
||||
cloth_to_object (ob, result, clmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(result)
|
||||
return result;
|
||||
else
|
||||
return dm;
|
||||
return result;
|
||||
}
|
||||
|
||||
/* frees all */
|
||||
void cloth_free_modifier ( ClothModifierData *clmd )
|
||||
void cloth_free_modifier (ClothModifierData *clmd)
|
||||
{
|
||||
Cloth *cloth = NULL;
|
||||
|
||||
if ( !clmd )
|
||||
if(!clmd)
|
||||
return;
|
||||
|
||||
cloth = clmd->clothObject;
|
||||
|
||||
if ( ! ( clmd->sim_parms.flags & CLOTH_SIMSETTINGS_FLAG_CCACHE_PROTECT ) )
|
||||
{
|
||||
// free our frame cache, TODO: but get to first position before
|
||||
cloth_cache_free ( clmd, 0 );
|
||||
// free our frame cache
|
||||
cloth_cache_free(clmd, 0);
|
||||
|
||||
if ( cloth )
|
||||
{
|
||||
/* Calls the solver and collision frees first as they
|
||||
* might depend on data in clmd->clothObject. */
|
||||
|
||||
// If our solver provides a free function, call it
|
||||
if ( cloth->old_solver_type < 255 && solvers [cloth->old_solver_type].free )
|
||||
{
|
||||
solvers [cloth->old_solver_type].free ( clmd );
|
||||
}
|
||||
if (cloth)
|
||||
{
|
||||
// If our solver provides a free function, call it
|
||||
if (cloth->old_solver_type < 255 && solvers [cloth->old_solver_type].free)
|
||||
{
|
||||
solvers [cloth->old_solver_type].free (clmd);
|
||||
}
|
||||
|
||||
// Free the verts.
|
||||
if (cloth->verts != NULL)
|
||||
MEM_freeN (cloth->verts);
|
||||
|
||||
// Free the verts.
|
||||
if ( cloth->verts != NULL )
|
||||
MEM_freeN ( cloth->verts );
|
||||
|
||||
// Free the verts.
|
||||
// Free the verts.
|
||||
if ( cloth->x != NULL )
|
||||
MEM_freeN ( cloth->x );
|
||||
|
||||
@@ -777,9 +756,9 @@ void cloth_free_modifier ( ClothModifierData *clmd )
|
||||
if ( cloth->xnew != NULL )
|
||||
MEM_freeN ( cloth->xnew );
|
||||
|
||||
cloth->verts = NULL;
|
||||
cloth->numverts = 0;
|
||||
|
||||
cloth->verts = NULL;
|
||||
cloth->numverts = -1;
|
||||
|
||||
// Free the springs.
|
||||
if ( cloth->springs != NULL )
|
||||
{
|
||||
@@ -796,27 +775,20 @@ void cloth_free_modifier ( ClothModifierData *clmd )
|
||||
cloth->springs = NULL;
|
||||
}
|
||||
|
||||
cloth->springs = NULL;
|
||||
cloth->numsprings = 0;
|
||||
/*
|
||||
// free BVH collision tree
|
||||
if ( cloth->tree )
|
||||
bvh_free ( ( BVH * ) cloth->tree );
|
||||
*/
|
||||
// we save our faces for collision objects
|
||||
if ( cloth->mfaces )
|
||||
MEM_freeN ( cloth->mfaces );
|
||||
/*
|
||||
if(clmd->clothObject->facemarks)
|
||||
MEM_freeN(clmd->clothObject->facemarks);
|
||||
*/
|
||||
MEM_freeN ( cloth );
|
||||
clmd->clothObject = NULL;
|
||||
}
|
||||
cloth->numsprings = -1;
|
||||
|
||||
// free BVH collision tree
|
||||
if(cloth->tree)
|
||||
bvh_free((BVH *)cloth->tree);
|
||||
|
||||
MEM_freeN (cloth);
|
||||
clmd->clothObject = NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/******************************************************************************
|
||||
*
|
||||
* Internal functions.
|
||||
@@ -828,24 +800,26 @@ void cloth_free_modifier ( ClothModifierData *clmd )
|
||||
*
|
||||
* This function is a modified version of the softbody.c:softbody_to_object() function.
|
||||
**/
|
||||
static void cloth_to_object ( Object *ob, ClothModifierData *clmd, DerivedMesh *dm )
|
||||
static void cloth_to_object (Object *ob, DerivedMesh *dm, ClothModifierData *clmd)
|
||||
{
|
||||
ClothVertex *verts = NULL;
|
||||
unsigned int i = 0;
|
||||
MVert *mvert = NULL;
|
||||
MVert *mvert = NULL;
|
||||
unsigned int numverts;
|
||||
|
||||
if ( clmd->clothObject )
|
||||
{
|
||||
if (clmd->clothObject) {
|
||||
verts = clmd->clothObject->verts;
|
||||
mvert = dm->getVertArray(dm);
|
||||
|
||||
/* inverse matrix is not uptodate... */
|
||||
Mat4Invert ( ob->imat, ob->obmat );
|
||||
Mat4Invert (ob->imat, ob->obmat);
|
||||
|
||||
for ( i = 0; i < dm->getNumVerts(dm); i++, verts++ )
|
||||
mvert = CDDM_get_verts(dm);
|
||||
numverts = dm->getNumVerts(dm);
|
||||
|
||||
for (i = 0; i < numverts; i++, verts++)
|
||||
{
|
||||
VECCOPY ( mvert[i].co, verts->x );
|
||||
Mat4MulVecfl ( ob->imat, mvert[i].co ); /* softbody is in global coords */
|
||||
VECCOPY (mvert[i].co, verts->x);
|
||||
Mat4MulVecfl (ob->imat, mvert[i].co); /* softbody is in global coords */
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -855,48 +829,43 @@ static void cloth_to_object ( Object *ob, ClothModifierData *clmd, DerivedMesh *
|
||||
* cloth_apply_vgroup - applies a vertex group as specified by type
|
||||
*
|
||||
**/
|
||||
static void cloth_apply_vgroup ( ClothModifierData *clmd, DerivedMesh *dm, short vgroup )
|
||||
static void cloth_apply_vgroup(ClothModifierData *clmd, DerivedMesh *dm, short vgroup)
|
||||
{
|
||||
unsigned int i = 0;
|
||||
unsigned int j = 0;
|
||||
MDeformVert *dvert = NULL;
|
||||
Cloth *clothObj = NULL;
|
||||
unsigned int numverts = dm->getNumVerts ( dm );
|
||||
float goalfac = 0;
|
||||
ClothVertex *verts = NULL;
|
||||
unsigned int i;
|
||||
int j;
|
||||
MDeformVert *dvert = NULL;
|
||||
Cloth *clothObj;
|
||||
unsigned int numverts = dm->getNumVerts(dm);
|
||||
float goalfac;
|
||||
|
||||
clothObj = clmd->clothObject;
|
||||
|
||||
if ( !dm )
|
||||
return;
|
||||
|
||||
numverts = dm->getNumVerts ( dm );
|
||||
|
||||
/* vgroup is 1 based, decrement so we can match the right group. */
|
||||
--vgroup;
|
||||
|
||||
verts = clothObj->verts;
|
||||
|
||||
for ( i = 0; i < numverts; i++, verts++ )
|
||||
{
|
||||
for (i = 0; i < numverts; ++i)
|
||||
{
|
||||
/* so this will definily be below SOFTGOALSNAP */
|
||||
clothObj->verts [i].goal= 0.0f;
|
||||
|
||||
// LATER ON, support also mass painting here
|
||||
if ( clmd->sim_parms.flags & CLOTH_SIMSETTINGS_FLAG_GOAL )
|
||||
{
|
||||
dvert = dm->getVertData ( dm, i, CD_MDEFORMVERT );
|
||||
if ( dvert )
|
||||
{
|
||||
for ( j = 0; j < dvert->totweight; j++ )
|
||||
if(clmd->sim_parms.flags & CLOTH_SIMSETTINGS_FLAG_GOAL)
|
||||
{
|
||||
dvert = dm->getVertData(dm, i, CD_MDEFORMVERT);
|
||||
if(dvert)
|
||||
{
|
||||
for(j = 0; j < dvert->totweight; j++)
|
||||
{
|
||||
if ( dvert->dw[j].def_nr == vgroup )
|
||||
if(dvert->dw[j].def_nr == vgroup)
|
||||
{
|
||||
verts->goal = dvert->dw [j].weight;
|
||||
clothObj->verts [i].goal = dvert->dw [j].weight;
|
||||
|
||||
goalfac= ABS ( clmd->sim_parms.maxgoal - clmd->sim_parms.mingoal );
|
||||
verts->goal = ( float ) pow ( verts->goal , 4.0f );
|
||||
goalfac= ABS(clmd->sim_parms.maxgoal - clmd->sim_parms.mingoal);
|
||||
clothObj->verts [i].goal = (float)pow(clothObj->verts [i].goal , 4.0f);
|
||||
|
||||
if ( dvert->dw [j].weight >=SOFTGOALSNAP )
|
||||
if(dvert->dw [j].weight >=SOFTGOALSNAP)
|
||||
{
|
||||
verts->flags |= CVERT_FLAG_PINNED;
|
||||
clothObj->verts[i].flags |= CVERT_FLAG_PINNED;
|
||||
}
|
||||
|
||||
// TODO enable mass painting here, for the moment i let "goals" go first
|
||||
@@ -910,158 +879,173 @@ static void cloth_apply_vgroup ( ClothModifierData *clmd, DerivedMesh *dm, short
|
||||
}
|
||||
|
||||
// only meshes supported at the moment
|
||||
static int cloth_from_object ( Object *ob, ClothModifierData *clmd, DerivedMesh *dm )
|
||||
/* collision objects */
|
||||
static int collobj_from_object(Object *ob, ClothModifierData *clmd, DerivedMesh *dm, DerivedMesh *olddm, float framenr)
|
||||
{
|
||||
unsigned int i = 0;
|
||||
// dm->getNumVerts(dm);
|
||||
MVert *mvert = NULL; // CDDM_get_verts(dm);
|
||||
ClothVertex *verts = NULL;
|
||||
float tnull[3] = {0,0,0};
|
||||
unsigned int i;
|
||||
unsigned int numverts = dm->getNumVerts(dm);
|
||||
MVert *mvert = CDDM_get_verts(dm);
|
||||
|
||||
switch (ob->type)
|
||||
{
|
||||
case OB_MESH:
|
||||
cloth_from_mesh (ob, clmd, dm, framenr);
|
||||
|
||||
if (clmd->clothObject != NULL)
|
||||
{
|
||||
|
||||
for (i = 0; i < numverts; ++i)
|
||||
{
|
||||
VECCOPY (clmd->clothObject->verts [i].x, mvert[i].co);
|
||||
Mat4MulVecfl(ob->obmat, clmd->clothObject->verts [i].x);
|
||||
clmd->clothObject->verts [i].flags = 0;
|
||||
VECCOPY(clmd->clothObject->verts [i].xold, clmd->clothObject->verts [i].x);
|
||||
VECCOPY(clmd->clothObject->verts [i].txold, clmd->clothObject->verts [i].x);
|
||||
VECCOPY(clmd->clothObject->verts [i].tx, clmd->clothObject->verts [i].x);
|
||||
VecMulf(clmd->clothObject->verts [i].v, 0.0f);
|
||||
}
|
||||
clmd->clothObject->tree = bvh_build(clmd, 0.001f);
|
||||
|
||||
}
|
||||
|
||||
return 1;
|
||||
default: return 0; // TODO - we do not support changing meshes
|
||||
}
|
||||
}
|
||||
|
||||
// only meshes supported at the moment
|
||||
static int cloth_from_object(Object *ob, ClothModifierData *clmd, DerivedMesh *dm, DerivedMesh *olddm, float framenr)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned int numverts = dm->getNumVerts(dm);
|
||||
MVert *mvert = CDDM_get_verts(dm);
|
||||
|
||||
/* If we have a clothObject, free it. */
|
||||
if ( clmd->clothObject != NULL )
|
||||
cloth_free_modifier ( clmd );
|
||||
if (clmd->clothObject != NULL)
|
||||
cloth_free_modifier (clmd);
|
||||
|
||||
/* Allocate a new cloth object. */
|
||||
clmd->clothObject = MEM_callocN ( sizeof ( Cloth ), "cloth" );
|
||||
if ( clmd->clothObject )
|
||||
clmd->clothObject = MEM_callocN (sizeof(Cloth), "cloth");
|
||||
if (clmd->clothObject)
|
||||
{
|
||||
clmd->clothObject->old_solver_type = 255;
|
||||
// clmd->clothObject->old_collision_type = 255;
|
||||
clmd->clothObject->old_solver_type = -1;
|
||||
}
|
||||
else if ( !clmd->clothObject )
|
||||
else if (clmd->clothObject == NULL)
|
||||
{
|
||||
modifier_setError ( & ( clmd->modifier ), "Out of memory on allocating clmd->clothObject." );
|
||||
modifier_setError (&(clmd->modifier), "Out of memory on allocating clmd->clothObject.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("cloth_from_object\n");
|
||||
|
||||
switch ( ob->type )
|
||||
switch (ob->type)
|
||||
{
|
||||
case OB_MESH:
|
||||
|
||||
cloth_from_mesh (ob, clmd, dm, framenr);
|
||||
|
||||
// mesh input objects need DerivedMesh
|
||||
if ( !dm )
|
||||
return 0;
|
||||
if (clmd->clothObject != NULL)
|
||||
{
|
||||
/* create springs */
|
||||
clmd->clothObject->springs = NULL;
|
||||
clmd->clothObject->numsprings = -1;
|
||||
|
||||
cloth_from_mesh ( ob, clmd, dm );
|
||||
|
||||
if ( clmd->clothObject != NULL )
|
||||
if (!cloth_build_springs (clmd->clothObject, dm) )
|
||||
{
|
||||
/* create springs */
|
||||
clmd->clothObject->springs = NULL;
|
||||
clmd->clothObject->numsprings = -1;
|
||||
modifier_setError (&(clmd->modifier), "Can't build springs.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( !cloth_build_springs ( clmd->clothObject, dm ) )
|
||||
{
|
||||
modifier_setError ( & ( clmd->modifier ), "Can't build springs." );
|
||||
return 0;
|
||||
}
|
||||
/* set initial values */
|
||||
for (i = 0; i < numverts; ++i)
|
||||
{
|
||||
VECCOPY (clmd->clothObject->verts [i].x, mvert[i].co);
|
||||
Mat4MulVecfl(ob->obmat, clmd->clothObject->verts [i].x);
|
||||
|
||||
mvert = dm->getVertArray ( dm ); // CDDM_get_verts ( dm );
|
||||
verts = clmd->clothObject->verts;
|
||||
|
||||
/* set initial values */
|
||||
for ( i = 0; i < dm->getNumVerts(dm); i++, verts++ )
|
||||
{
|
||||
VECCOPY ( verts->x, mvert[i].co );
|
||||
Mat4MulVecfl ( ob->obmat, verts->x );
|
||||
|
||||
verts->mass = clmd->sim_parms.mass;
|
||||
|
||||
if ( clmd->sim_parms.flags & CLOTH_SIMSETTINGS_FLAG_GOAL )
|
||||
verts->goal= clmd->sim_parms.defgoal;
|
||||
else
|
||||
verts->goal= 0.0f;
|
||||
|
||||
verts->flags = 0;
|
||||
VECCOPY ( verts->xold, verts->x );
|
||||
VECCOPY ( verts->xconst, verts->x );
|
||||
VECCOPY ( verts->txold, verts->x );
|
||||
VecMulf ( verts->v, 0.0f );
|
||||
|
||||
verts->impulse_count = 0;
|
||||
VECCOPY ( verts->impulse, tnull );
|
||||
}
|
||||
|
||||
// apply / set vertex groups
|
||||
if ( clmd->sim_parms.vgroup_mass > 0 )
|
||||
cloth_apply_vgroup ( clmd, dm, clmd->sim_parms.vgroup_mass );
|
||||
|
||||
// init our solver
|
||||
if ( solvers [clmd->sim_parms.solver_type].init )
|
||||
solvers [clmd->sim_parms.solver_type].init ( ob, clmd );
|
||||
|
||||
// clmd->clothObject->tree = bvh_build ( dm, clmd->coll_parms.epsilon );
|
||||
|
||||
cloth_cache_set_frame ( clmd, 1 );
|
||||
clmd->clothObject->verts [i].mass = clmd->sim_parms.mass;
|
||||
clmd->clothObject->verts [i].goal= clmd->sim_parms.defgoal;
|
||||
clmd->clothObject->verts [i].flags = 0;
|
||||
VECCOPY(clmd->clothObject->verts [i].xold, clmd->clothObject->verts [i].x);
|
||||
VECCOPY(clmd->clothObject->verts [i].xconst, clmd->clothObject->verts [i].x);
|
||||
VECCOPY(clmd->clothObject->verts [i].txold, clmd->clothObject->verts [i].x);
|
||||
VecMulf(clmd->clothObject->verts [i].v, 0.0f);
|
||||
}
|
||||
|
||||
return 1;
|
||||
/* apply / set vertex groups */
|
||||
if (clmd->sim_parms.vgroup_mass > 0)
|
||||
cloth_apply_vgroup (clmd, olddm, clmd->sim_parms.vgroup_mass);
|
||||
|
||||
/* init our solver */
|
||||
if (solvers [clmd->sim_parms.solver_type].init)
|
||||
solvers [clmd->sim_parms.solver_type].init (ob, clmd);
|
||||
|
||||
clmd->clothObject->tree = bvh_build(clmd, clmd->coll_parms.epsilon);
|
||||
|
||||
cloth_cache_set_frame(clmd, 1);
|
||||
}
|
||||
|
||||
return 1;
|
||||
case OB_LATTICE:
|
||||
printf ( "Not supported: OB_LATTICE\n" );
|
||||
// lattice_to_softbody(ob);
|
||||
return 1;
|
||||
printf("OB_LATTICE\n");
|
||||
// lattice_to_softbody(ob);
|
||||
return 1;
|
||||
case OB_CURVE:
|
||||
case OB_SURF:
|
||||
printf ( "Not supported: OB_SURF| OB_CURVE\n" );
|
||||
return 1;
|
||||
printf("OB_SURF| OB_CURVE\n");
|
||||
return 1;
|
||||
default: return 0; // TODO - we do not support changing meshes
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cloth_from_mesh ( Object *ob, ClothModifierData *clmd, DerivedMesh *dm )
|
||||
static void cloth_from_mesh (Object *ob, ClothModifierData *clmd, DerivedMesh *dm, float framenr)
|
||||
{
|
||||
unsigned int numverts = dm->getNumVerts ( dm );
|
||||
unsigned int numfaces = dm->getNumFaces ( dm );
|
||||
MFace *mface = dm->getFaceArray ( dm );
|
||||
unsigned int numverts = dm->getNumVerts(dm);
|
||||
unsigned int numfaces = dm->getNumFaces(dm);
|
||||
MFace *mface = CDDM_get_faces(dm);
|
||||
unsigned int i = 0;
|
||||
|
||||
/* Allocate our vertices.
|
||||
*/
|
||||
clmd->clothObject->numverts = numverts;
|
||||
clmd->clothObject->verts = MEM_callocN ( sizeof ( ClothVertex ) * clmd->clothObject->numverts, "clothVertex" );
|
||||
if ( clmd->clothObject->verts == NULL )
|
||||
clmd->clothObject->verts = MEM_callocN (sizeof (ClothVertex) * clmd->clothObject->numverts, "clothVertex");
|
||||
if (clmd->clothObject->verts == NULL)
|
||||
{
|
||||
cloth_free_modifier ( clmd );
|
||||
modifier_setError ( & ( clmd->modifier ), "Out of memory on allocating clmd->clothObject->verts." );
|
||||
cloth_free_modifier (clmd);
|
||||
modifier_setError (&(clmd->modifier), "Out of memory on allocating clmd->clothObject->verts.");
|
||||
return;
|
||||
}
|
||||
|
||||
clmd->clothObject->x = MEM_callocN ( sizeof ( MVert ) * clmd->clothObject->numverts, "Cloth MVert_x" );
|
||||
if ( clmd->clothObject->x == NULL )
|
||||
// collision objects need to cache face infos since they are needed during collision detection
|
||||
// TODO: maybe cache it for cloth objects, too
|
||||
if (clmd->sim_parms.flags & CLOTH_SIMSETTINGS_FLAG_COLLOBJ)
|
||||
{
|
||||
cloth_free_modifier ( clmd );
|
||||
modifier_setError ( & ( clmd->modifier ), "Out of memory on allocating clmd->clothObject->x." );
|
||||
return;
|
||||
clmd->clothObject->numfaces = numfaces;
|
||||
clmd->clothObject->mfaces = MEM_callocN (sizeof (MFace) * clmd->clothObject->numfaces, "clothMFaces");
|
||||
if (clmd->clothObject->mfaces == NULL)
|
||||
{
|
||||
cloth_free_modifier (clmd);
|
||||
modifier_setError (&(clmd->modifier), "Out of memory on allocating clmd->clothObject->mfaces.");
|
||||
return;
|
||||
}
|
||||
for(i = 0; i < numfaces; i++)
|
||||
memcpy(&clmd->clothObject->mfaces[i], &mface[i], sizeof(MFace));
|
||||
}
|
||||
else
|
||||
{
|
||||
clmd->clothObject->mfaces = mface; // update face pointer
|
||||
clmd->clothObject->numfaces = numfaces;
|
||||
}
|
||||
|
||||
clmd->clothObject->xnew = MEM_callocN ( sizeof ( MVert ) * clmd->clothObject->numverts, "Cloth MVert_xnew" );
|
||||
if ( clmd->clothObject->xnew == NULL )
|
||||
{
|
||||
cloth_free_modifier ( clmd );
|
||||
modifier_setError ( & ( clmd->modifier ), "Out of memory on allocating clmd->clothObject->xnew." );
|
||||
return;
|
||||
}
|
||||
|
||||
// save face information
|
||||
clmd->clothObject->numfaces = numfaces;
|
||||
clmd->clothObject->mfaces = MEM_callocN ( sizeof ( MFace ) * clmd->clothObject->numfaces, "clothMFaces" );
|
||||
if ( clmd->clothObject->mfaces == NULL )
|
||||
{
|
||||
cloth_free_modifier ( clmd );
|
||||
modifier_setError ( & ( clmd->modifier ), "Out of memory on allocating clmd->clothObject->mfaces." );
|
||||
return;
|
||||
}
|
||||
for ( i = 0; i < numfaces; i++ )
|
||||
memcpy ( &clmd->clothObject->mfaces[i], &mface[i], sizeof ( MFace ) );
|
||||
// for SIP code
|
||||
// clmd->clothObject->facemarks = MEM_callocN (sizeof (unsigned char) * clmd->clothObject->numfaces, "clothFaceMarks");
|
||||
|
||||
/* Free the springs since they can't be correct if the vertices
|
||||
* changed.
|
||||
*/
|
||||
if ( clmd->clothObject->springs != NULL )
|
||||
MEM_freeN ( clmd->clothObject->springs );
|
||||
if (clmd->clothObject->springs != NULL)
|
||||
MEM_freeN (clmd->clothObject->springs);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -4892,18 +4892,19 @@ static void clothModifier_deformVerts(
|
||||
*/
|
||||
|
||||
static DerivedMesh *clothModifier_applyModifier(
|
||||
ModifierData *md, Object *ob, DerivedMesh *derivedData,
|
||||
int useRenderParams, int isFinalCalc)
|
||||
ModifierData *md, Object *ob, DerivedMesh *derivedData,
|
||||
int useRenderParams, int isFinalCalc)
|
||||
{
|
||||
DerivedMesh *result = NULL;
|
||||
|
||||
ClothModifierData *clmd = (ClothModifierData*) md;
|
||||
DerivedMesh *result=NULL;
|
||||
|
||||
result = clothModifier_do(clmd, ob, derivedData);
|
||||
result = clothModifier_do(clmd, ob, derivedData, useRenderParams, isFinalCalc);
|
||||
|
||||
CDDM_calc_normals(result);
|
||||
|
||||
return result;
|
||||
if(result)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return derivedData;
|
||||
}
|
||||
|
||||
static void clothModifier_updateDepgraph(
|
||||
|
||||
Reference in New Issue
Block a user