/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) Blender Foundation
* All rights reserved.
*/
/** \file
* \ingroup bke
*/
/**
* variables on the UI for now
*
* float mediafrict; friction to env
* float nodemass; softbody mass of *vertex*
* float grav; softbody amount of gravitation to apply
*
* float goalspring; softbody goal springs
* float goalfrict; softbody goal springs friction
* float mingoal; quick limits for goal
* float maxgoal;
*
* float inspring; softbody inner springs
* float infrict; softbody inner springs friction
*
*/
#include
#include
#include
#include "CLG_log.h"
#include "MEM_guardedalloc.h"
/* types */
#include "DNA_collection_types.h"
#include "DNA_curve_types.h"
#include "DNA_lattice_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_ghash.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_threads.h"
#include "BLI_utildefines.h"
#include "BKE_collection.h"
#include "BKE_collision.h"
#include "BKE_curve.h"
#include "BKE_deform.h"
#include "BKE_effect.h"
#include "BKE_global.h"
#include "BKE_layer.h"
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_pointcache.h"
#include "BKE_scene.h"
#include "BKE_softbody.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_query.h"
#include "PIL_time.h"
static CLG_LogRef LOG = {"bke.softbody"};
/* callbacks for errors and interrupts and some goo */
static int (*SB_localInterruptCallBack)(void) = NULL;
/* ********** soft body engine ******* */
typedef enum { SB_EDGE = 1, SB_BEND = 2, SB_STIFFQUAD = 3, SB_HANDLE = 4 } type_spring;
typedef struct BodySpring {
int v1, v2;
float len, cf, load;
float ext_force[3]; /* edges colliding and sailing */
type_spring springtype;
short flag;
} BodySpring;
typedef struct BodyFace {
int v1, v2, v3;
float ext_force[3]; /* faces colliding */
short flag;
} BodyFace;
typedef struct ReferenceVert {
float pos[3]; /* position relative to com */
float mass; /* node mass */
} ReferenceVert;
typedef struct ReferenceState {
float com[3]; /* center of mass*/
ReferenceVert *ivert; /* list of initial values */
} ReferenceState;
/*private scratch pad for caching and other data only needed when alive*/
typedef struct SBScratch {
GHash *colliderhash;
short needstobuildcollider;
short flag;
BodyFace *bodyface;
int totface;
float aabbmin[3], aabbmax[3];
ReferenceState Ref;
} SBScratch;
typedef struct SB_thread_context {
Scene *scene;
Object *ob;
float forcetime;
float timenow;
int ifirst;
int ilast;
ListBase *effectors;
int do_deflector;
float fieldfactor;
float windfactor;
int nr;
int tot;
} SB_thread_context;
#define MID_PRESERVE 1
#define SOFTGOALSNAP 0.999f
/* if bp-> goal is above make it a *forced follow original* and skip all ODE stuff for this bp
* removes *unnecessary* stiffness from ODE system
*/
#define HEUNWARNLIMIT 1 /* 500 would be fine i think for detecting severe *stiff* stuff */
#define BSF_INTERSECT 1 /* edge intersects collider face */
/* private definitions for bodypoint states */
#define SBF_DOFUZZY 1 /* Bodypoint do fuzzy */
#define SBF_OUTOFCOLLISION 2 /* Bodypoint does not collide */
#define BFF_INTERSECT 1 /* collider edge intrudes face */
#define BFF_CLOSEVERT 2 /* collider vertex repulses face */
/* humm .. this should be calculated from sb parameters and sizes. */
static float SoftHeunTol = 1.0f;
/* local prototypes */
static void free_softbody_intern(SoftBody *sb);
/*+++ frame based timing +++*/
/*physical unit of force is [kg * m / sec^2]*/
/**
* Since unit of g is [m/sec^2] and F = mass * g we re-scale unit mass of node to 1 gram
* put it to a function here, so we can add user options later without touching simulation code.
*/
static float sb_grav_force_scale(Object *UNUSED(ob))
{
return (0.001f);
}
/**
* Re-scaling unit of drag [1 / sec] to somehow reasonable
* put it to a function here, so we can add user options later without touching simulation code.
*/
static float sb_fric_force_scale(Object *UNUSED(ob))
{
return (0.01f);
}
/**
* Defining the frames to *real* time relation.
*/
static float sb_time_scale(Object *ob)
{
SoftBody *sb = ob->soft; /* is supposed to be there */
if (sb) {
return (sb->physics_speed);
/* hrms .. this could be IPO as well :)
* estimated range [0.001 sluggish slug - 100.0 very fast (i hope ODE solver can handle that)]
* 1 approx = a unit 1 pendulum at g = 9.8 [earth conditions] has period 65 frames
* theory would give a 50 frames period .. so there must be something inaccurate ..
* looking for that (BM). */
}
return (1.0f);
/*
* this would be frames/sec independent timing assuming 25 fps is default
* but does not work very well with NLA
* return (25.0f/scene->r.frs_sec)
*/
}
/*--- frame based timing ---*/
/* helper functions for everything is animatable jow_go_for2_5 +++++++*/
/* introducing them here, because i know: steps in properties ( at frame timing )
* will cause unwanted responses of the softbody system (which does inter frame calculations )
* so first 'cure' would be: interpolate linear in time ..
* Q: why do i write this?
* A: because it happened once, that some eger coder 'streamlined' code to fail.
* We DO linear interpolation for goals .. and i think we should do on animated properties as well
*/
/* animate sb->maxgoal, sb->mingoal */
static float _final_goal(Object *ob, BodyPoint *bp) /*jow_go_for2_5 */
{
float f = -1999.99f;
if (ob) {
SoftBody *sb = ob->soft; /* is supposed to be there */
if (!(ob->softflag & OB_SB_GOAL)) {
return (0.0f);
}
if (sb && bp) {
if (bp->goal < 0.0f) {
return (0.0f);
}
f = sb->mingoal + bp->goal * fabsf(sb->maxgoal - sb->mingoal);
f = pow(f, 4.0f);
return f;
}
}
CLOG_ERROR(&LOG, "sb or bp == NULL");
return f; /*using crude but spot able values some times helps debuggin */
}
static float _final_mass(Object *ob, BodyPoint *bp)
{
if (ob) {
SoftBody *sb = ob->soft; /* is supposed to be there */
if (sb && bp) {
return (bp->mass * sb->nodemass);
}
}
CLOG_ERROR(&LOG, "sb or bp == NULL");
return 1.0f;
}
/* helper functions for everything is animateble jow_go_for2_5 ------*/
/*+++ collider caching and dicing +++*/
/*
* for each target object/face the axis aligned bounding box (AABB) is stored
* faces parallel to global axes
* so only simple "value" in [min, max] checks are used
* float operations still
*/
/* just an ID here to reduce the prob for killing objects
* ob->sumohandle points to we should not kill :)
*/
static const int CCD_SAFETY = 190561;
typedef struct ccdf_minmax {
float minx, miny, minz, maxx, maxy, maxz;
} ccdf_minmax;
typedef struct ccd_Mesh {
int mvert_num, tri_num;
const MVert *mvert;
const MVert *mprevvert;
const MVertTri *tri;
int safety;
ccdf_minmax *mima;
/* Axis Aligned Bounding Box AABB */
float bbmin[3];
float bbmax[3];
} ccd_Mesh;
static ccd_Mesh *ccd_mesh_make(Object *ob)
{
CollisionModifierData *cmd;
ccd_Mesh *pccd_M = NULL;
ccdf_minmax *mima;
const MVertTri *vt;
float hull;
int i;
cmd = (CollisionModifierData *)BKE_modifiers_findby_type(ob, eModifierType_Collision);
/* first some paranoia checks */
if (!cmd) {
return NULL;
}
if (!cmd->mvert_num || !cmd->tri_num) {
return NULL;
}
pccd_M = MEM_mallocN(sizeof(ccd_Mesh), "ccd_Mesh");
pccd_M->mvert_num = cmd->mvert_num;
pccd_M->tri_num = cmd->tri_num;
pccd_M->safety = CCD_SAFETY;
pccd_M->bbmin[0] = pccd_M->bbmin[1] = pccd_M->bbmin[2] = 1e30f;
pccd_M->bbmax[0] = pccd_M->bbmax[1] = pccd_M->bbmax[2] = -1e30f;
pccd_M->mprevvert = NULL;
/* blow it up with forcefield ranges */
hull = max_ff(ob->pd->pdef_sbift, ob->pd->pdef_sboft);
/* alloc and copy verts*/
pccd_M->mvert = MEM_dupallocN(cmd->xnew);
/* note that xnew coords are already in global space, */
/* determine the ortho BB */
for (i = 0; i < pccd_M->mvert_num; i++) {
const float *v;
/* evaluate limits */
v = pccd_M->mvert[i].co;
pccd_M->bbmin[0] = min_ff(pccd_M->bbmin[0], v[0] - hull);
pccd_M->bbmin[1] = min_ff(pccd_M->bbmin[1], v[1] - hull);
pccd_M->bbmin[2] = min_ff(pccd_M->bbmin[2], v[2] - hull);
pccd_M->bbmax[0] = max_ff(pccd_M->bbmax[0], v[0] + hull);
pccd_M->bbmax[1] = max_ff(pccd_M->bbmax[1], v[1] + hull);
pccd_M->bbmax[2] = max_ff(pccd_M->bbmax[2], v[2] + hull);
}
/* alloc and copy faces*/
pccd_M->tri = MEM_dupallocN(cmd->tri);
/* OBBs for idea1 */
pccd_M->mima = MEM_mallocN(sizeof(ccdf_minmax) * pccd_M->tri_num, "ccd_Mesh_Faces_mima");
/* anyhoo we need to walk the list of faces and find OBB they live in */
for (i = 0, mima = pccd_M->mima, vt = pccd_M->tri; i < pccd_M->tri_num; i++, mima++, vt++) {
const float *v;
mima->minx = mima->miny = mima->minz = 1e30f;
mima->maxx = mima->maxy = mima->maxz = -1e30f;
v = pccd_M->mvert[vt->tri[0]].co;
mima->minx = min_ff(mima->minx, v[0] - hull);
mima->miny = min_ff(mima->miny, v[1] - hull);
mima->minz = min_ff(mima->minz, v[2] - hull);
mima->maxx = max_ff(mima->maxx, v[0] + hull);
mima->maxy = max_ff(mima->maxy, v[1] + hull);
mima->maxz = max_ff(mima->maxz, v[2] + hull);
v = pccd_M->mvert[vt->tri[1]].co;
mima->minx = min_ff(mima->minx, v[0] - hull);
mima->miny = min_ff(mima->miny, v[1] - hull);
mima->minz = min_ff(mima->minz, v[2] - hull);
mima->maxx = max_ff(mima->maxx, v[0] + hull);
mima->maxy = max_ff(mima->maxy, v[1] + hull);
mima->maxz = max_ff(mima->maxz, v[2] + hull);
v = pccd_M->mvert[vt->tri[2]].co;
mima->minx = min_ff(mima->minx, v[0] - hull);
mima->miny = min_ff(mima->miny, v[1] - hull);
mima->minz = min_ff(mima->minz, v[2] - hull);
mima->maxx = max_ff(mima->maxx, v[0] + hull);
mima->maxy = max_ff(mima->maxy, v[1] + hull);
mima->maxz = max_ff(mima->maxz, v[2] + hull);
}
return pccd_M;
}
static void ccd_mesh_update(Object *ob, ccd_Mesh *pccd_M)
{
CollisionModifierData *cmd;
ccdf_minmax *mima;
const MVertTri *vt;
float hull;
int i;
cmd = (CollisionModifierData *)BKE_modifiers_findby_type(ob, eModifierType_Collision);
/* first some paranoia checks */
if (!cmd) {
return;
}
if (!cmd->mvert_num || !cmd->tri_num) {
return;
}
if ((pccd_M->mvert_num != cmd->mvert_num) || (pccd_M->tri_num != cmd->tri_num)) {
return;
}
pccd_M->bbmin[0] = pccd_M->bbmin[1] = pccd_M->bbmin[2] = 1e30f;
pccd_M->bbmax[0] = pccd_M->bbmax[1] = pccd_M->bbmax[2] = -1e30f;
/* blow it up with forcefield ranges */
hull = max_ff(ob->pd->pdef_sbift, ob->pd->pdef_sboft);
/* rotate current to previous */
if (pccd_M->mprevvert) {
MEM_freeN((void *)pccd_M->mprevvert);
}
pccd_M->mprevvert = pccd_M->mvert;
/* alloc and copy verts*/
pccd_M->mvert = MEM_dupallocN(cmd->xnew);
/* note that xnew coords are already in global space, */
/* determine the ortho BB */
for (i = 0; i < pccd_M->mvert_num; i++) {
const float *v;
/* evaluate limits */
v = pccd_M->mvert[i].co;
pccd_M->bbmin[0] = min_ff(pccd_M->bbmin[0], v[0] - hull);
pccd_M->bbmin[1] = min_ff(pccd_M->bbmin[1], v[1] - hull);
pccd_M->bbmin[2] = min_ff(pccd_M->bbmin[2], v[2] - hull);
pccd_M->bbmax[0] = max_ff(pccd_M->bbmax[0], v[0] + hull);
pccd_M->bbmax[1] = max_ff(pccd_M->bbmax[1], v[1] + hull);
pccd_M->bbmax[2] = max_ff(pccd_M->bbmax[2], v[2] + hull);
/* evaluate limits */
v = pccd_M->mprevvert[i].co;
pccd_M->bbmin[0] = min_ff(pccd_M->bbmin[0], v[0] - hull);
pccd_M->bbmin[1] = min_ff(pccd_M->bbmin[1], v[1] - hull);
pccd_M->bbmin[2] = min_ff(pccd_M->bbmin[2], v[2] - hull);
pccd_M->bbmax[0] = max_ff(pccd_M->bbmax[0], v[0] + hull);
pccd_M->bbmax[1] = max_ff(pccd_M->bbmax[1], v[1] + hull);
pccd_M->bbmax[2] = max_ff(pccd_M->bbmax[2], v[2] + hull);
}
/* anyhoo we need to walk the list of faces and find OBB they live in */
for (i = 0, mima = pccd_M->mima, vt = pccd_M->tri; i < pccd_M->tri_num; i++, mima++, vt++) {
const float *v;
mima->minx = mima->miny = mima->minz = 1e30f;
mima->maxx = mima->maxy = mima->maxz = -1e30f;
/* mvert */
v = pccd_M->mvert[vt->tri[0]].co;
mima->minx = min_ff(mima->minx, v[0] - hull);
mima->miny = min_ff(mima->miny, v[1] - hull);
mima->minz = min_ff(mima->minz, v[2] - hull);
mima->maxx = max_ff(mima->maxx, v[0] + hull);
mima->maxy = max_ff(mima->maxy, v[1] + hull);
mima->maxz = max_ff(mima->maxz, v[2] + hull);
v = pccd_M->mvert[vt->tri[1]].co;
mima->minx = min_ff(mima->minx, v[0] - hull);
mima->miny = min_ff(mima->miny, v[1] - hull);
mima->minz = min_ff(mima->minz, v[2] - hull);
mima->maxx = max_ff(mima->maxx, v[0] + hull);
mima->maxy = max_ff(mima->maxy, v[1] + hull);
mima->maxz = max_ff(mima->maxz, v[2] + hull);
v = pccd_M->mvert[vt->tri[2]].co;
mima->minx = min_ff(mima->minx, v[0] - hull);
mima->miny = min_ff(mima->miny, v[1] - hull);
mima->minz = min_ff(mima->minz, v[2] - hull);
mima->maxx = max_ff(mima->maxx, v[0] + hull);
mima->maxy = max_ff(mima->maxy, v[1] + hull);
mima->maxz = max_ff(mima->maxz, v[2] + hull);
/* mprevvert */
v = pccd_M->mprevvert[vt->tri[0]].co;
mima->minx = min_ff(mima->minx, v[0] - hull);
mima->miny = min_ff(mima->miny, v[1] - hull);
mima->minz = min_ff(mima->minz, v[2] - hull);
mima->maxx = max_ff(mima->maxx, v[0] + hull);
mima->maxy = max_ff(mima->maxy, v[1] + hull);
mima->maxz = max_ff(mima->maxz, v[2] + hull);
v = pccd_M->mprevvert[vt->tri[1]].co;
mima->minx = min_ff(mima->minx, v[0] - hull);
mima->miny = min_ff(mima->miny, v[1] - hull);
mima->minz = min_ff(mima->minz, v[2] - hull);
mima->maxx = max_ff(mima->maxx, v[0] + hull);
mima->maxy = max_ff(mima->maxy, v[1] + hull);
mima->maxz = max_ff(mima->maxz, v[2] + hull);
v = pccd_M->mprevvert[vt->tri[2]].co;
mima->minx = min_ff(mima->minx, v[0] - hull);
mima->miny = min_ff(mima->miny, v[1] - hull);
mima->minz = min_ff(mima->minz, v[2] - hull);
mima->maxx = max_ff(mima->maxx, v[0] + hull);
mima->maxy = max_ff(mima->maxy, v[1] + hull);
mima->maxz = max_ff(mima->maxz, v[2] + hull);
}
}
static void ccd_mesh_free(ccd_Mesh *ccdm)
{
/* Make sure we're not nuking objects we don't know. */
if (ccdm && (ccdm->safety == CCD_SAFETY)) {
MEM_freeN((void *)ccdm->mvert);
MEM_freeN((void *)ccdm->tri);
if (ccdm->mprevvert) {
MEM_freeN((void *)ccdm->mprevvert);
}
MEM_freeN(ccdm->mima);
MEM_freeN(ccdm);
ccdm = NULL;
}
}
static void ccd_build_deflector_hash_single(GHash *hash, Object *ob)
{
/* only with deflecting set */
if (ob->pd && ob->pd->deflect) {
void **val_p;
if (!BLI_ghash_ensure_p(hash, ob, &val_p)) {
ccd_Mesh *ccdmesh = ccd_mesh_make(ob);
*val_p = ccdmesh;
}
}
}
/**
* \note collection overrides scene when not NULL.
*/
static void ccd_build_deflector_hash(Depsgraph *depsgraph,
Collection *collection,
Object *vertexowner,
GHash *hash)
{
if (!hash) {
return;
}
unsigned int numobjects;
Object **objects = BKE_collision_objects_create(
depsgraph, vertexowner, collection, &numobjects, eModifierType_Collision);
for (int i = 0; i < numobjects; i++) {
Object *ob = objects[i];
if (ob->type == OB_MESH) {
ccd_build_deflector_hash_single(hash, ob);
}
}
BKE_collision_objects_free(objects);
}
static void ccd_update_deflector_hash_single(GHash *hash, Object *ob)
{
if (ob->pd && ob->pd->deflect) {
ccd_Mesh *ccdmesh = BLI_ghash_lookup(hash, ob);
if (ccdmesh) {
ccd_mesh_update(ob, ccdmesh);
}
}
}
/**
* \note collection overrides scene when not NULL.
*/
static void ccd_update_deflector_hash(Depsgraph *depsgraph,
Collection *collection,
Object *vertexowner,
GHash *hash)
{
if ((!hash) || (!vertexowner)) {
return;
}
unsigned int numobjects;
Object **objects = BKE_collision_objects_create(
depsgraph, vertexowner, collection, &numobjects, eModifierType_Collision);
for (int i = 0; i < numobjects; i++) {
Object *ob = objects[i];
if (ob->type == OB_MESH) {
ccd_update_deflector_hash_single(hash, ob);
}
}
BKE_collision_objects_free(objects);
}
/*--- collider caching and dicing ---*/
static int count_mesh_quads(Mesh *me)
{
int a, result = 0;
const MPoly *mp = me->mpoly;
if (mp) {
for (a = me->totpoly; a > 0; a--, mp++) {
if (mp->totloop == 4) {
result++;
}
}
}
return result;
}
static void add_mesh_quad_diag_springs(Object *ob)
{
Mesh *me = ob->data;
/*BodyPoint *bp;*/ /*UNUSED*/
int a;
if (ob->soft) {
int nofquads;
// float s_shear = ob->soft->shearstiff*ob->soft->shearstiff;
nofquads = count_mesh_quads(me);
if (nofquads) {
const MLoop *mloop = me->mloop;
const MPoly *mp = me->mpoly;
BodySpring *bs;
/* resize spring-array to hold additional quad springs */
ob->soft->bspring = MEM_recallocN(ob->soft->bspring,
sizeof(BodySpring) * (ob->soft->totspring + nofquads * 2));
/* fill the tail */
a = 0;
bs = &ob->soft->bspring[ob->soft->totspring];
/*bp= ob->soft->bpoint; */ /*UNUSED*/
for (a = me->totpoly; a > 0; a--, mp++) {
if (mp->totloop == 4) {
bs->v1 = mloop[mp->loopstart + 0].v;
bs->v2 = mloop[mp->loopstart + 2].v;
bs->springtype = SB_STIFFQUAD;
bs++;
bs->v1 = mloop[mp->loopstart + 1].v;
bs->v2 = mloop[mp->loopstart + 3].v;
bs->springtype = SB_STIFFQUAD;
bs++;
}
}
/* now we can announce new springs */
ob->soft->totspring += nofquads * 2;
}
}
}
static void add_2nd_order_roller(Object *ob, float UNUSED(stiffness), int *counter, int addsprings)
{
/*assume we have a softbody*/
SoftBody *sb = ob->soft; /* is supposed to be there */
BodyPoint *bp, *bpo;
BodySpring *bs, *bs2, *bs3 = NULL;
int a, b, c, notthis = 0, v0;
if (!sb->bspring) {
return;
} /* we are 2nd order here so 1rst should have been build :) */
/* first run counting second run adding */
*counter = 0;
if (addsprings) {
bs3 = ob->soft->bspring + ob->soft->totspring;
}
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
/*scan for neighborhood*/
bpo = NULL;
v0 = (sb->totpoint - a);
for (b = bp->nofsprings; b > 0; b--) {
bs = sb->bspring + bp->springs[b - 1];
/* Nasty thing here that springs have two ends
* so here we have to make sure we examine the other */
if (v0 == bs->v1) {
bpo = sb->bpoint + bs->v2;
notthis = bs->v2;
}
else {
if (v0 == bs->v2) {
bpo = sb->bpoint + bs->v1;
notthis = bs->v1;
}
else {
CLOG_ERROR(&LOG, "oops we should not get here");
}
}
if (bpo) { /* so now we have a 2nd order humpdidump */
for (c = bpo->nofsprings; c > 0; c--) {
bs2 = sb->bspring + bpo->springs[c - 1];
if ((bs2->v1 != notthis) && (bs2->v1 > v0)) {
(*counter)++; /*hit */
if (addsprings) {
bs3->v1 = v0;
bs3->v2 = bs2->v1;
bs3->springtype = SB_BEND;
bs3++;
}
}
if ((bs2->v2 != notthis) && (bs2->v2 > v0)) {
(*counter)++; /* hit */
if (addsprings) {
bs3->v1 = v0;
bs3->v2 = bs2->v2;
bs3->springtype = SB_BEND;
bs3++;
}
}
}
}
}
/*scan for neighborhood done*/
}
}
static void add_2nd_order_springs(Object *ob, float stiffness)
{
int counter = 0;
BodySpring *bs_new;
stiffness *= stiffness;
add_2nd_order_roller(ob, stiffness, &counter, 0); /* counting */
if (counter) {
/* resize spring-array to hold additional springs */
bs_new = MEM_callocN((ob->soft->totspring + counter) * sizeof(BodySpring), "bodyspring");
memcpy(bs_new, ob->soft->bspring, (ob->soft->totspring) * sizeof(BodySpring));
if (ob->soft->bspring) {
MEM_freeN(ob->soft->bspring);
}
ob->soft->bspring = bs_new;
add_2nd_order_roller(ob, stiffness, &counter, 1); /* adding */
ob->soft->totspring += counter;
}
}
static void add_bp_springlist(BodyPoint *bp, int springID)
{
int *newlist;
if (bp->springs == NULL) {
bp->springs = MEM_callocN(sizeof(int), "bpsprings");
bp->springs[0] = springID;
bp->nofsprings = 1;
}
else {
bp->nofsprings++;
newlist = MEM_callocN(bp->nofsprings * sizeof(int), "bpsprings");
memcpy(newlist, bp->springs, (bp->nofsprings - 1) * sizeof(int));
MEM_freeN(bp->springs);
bp->springs = newlist;
bp->springs[bp->nofsprings - 1] = springID;
}
}
/**
* Do this once when sb is build it is `O(N^2)`
* so scanning for springs every iteration is too expensive.
*/
static void build_bps_springlist(Object *ob)
{
SoftBody *sb = ob->soft; /* is supposed to be there */
BodyPoint *bp;
BodySpring *bs;
int a, b;
if (sb == NULL) {
return; /* paranoia check */
}
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
/* throw away old list */
if (bp->springs) {
MEM_freeN(bp->springs);
bp->springs = NULL;
}
/* scan for attached inner springs */
for (b = sb->totspring, bs = sb->bspring; b > 0; b--, bs++) {
if (((sb->totpoint - a) == bs->v1)) {
add_bp_springlist(bp, sb->totspring - b);
}
if (((sb->totpoint - a) == bs->v2)) {
add_bp_springlist(bp, sb->totspring - b);
}
} /*for springs*/
} /*for bp*/
}
static void calculate_collision_balls(Object *ob)
{
SoftBody *sb = ob->soft; /* is supposed to be there */
BodyPoint *bp;
BodySpring *bs;
int a, b, akku_count;
float min, max, akku;
if (sb == NULL) {
return; /* paranoia check */
}
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
bp->colball = 0;
akku = 0.0f;
akku_count = 0;
min = 1e22f;
max = -1e22f;
/* first estimation based on attached */
for (b = bp->nofsprings; b > 0; b--) {
bs = sb->bspring + bp->springs[b - 1];
if (bs->springtype == SB_EDGE) {
akku += bs->len;
akku_count++;
min = min_ff(bs->len, min);
max = max_ff(bs->len, max);
}
}
if (akku_count > 0) {
if (sb->sbc_mode == SBC_MODE_MANUAL) {
bp->colball = sb->colball;
}
if (sb->sbc_mode == SBC_MODE_AVG) {
bp->colball = akku / (float)akku_count * sb->colball;
}
if (sb->sbc_mode == SBC_MODE_MIN) {
bp->colball = min * sb->colball;
}
if (sb->sbc_mode == SBC_MODE_MAX) {
bp->colball = max * sb->colball;
}
if (sb->sbc_mode == SBC_MODE_AVGMINMAX) {
bp->colball = (min + max) / 2.0f * sb->colball;
}
}
else {
bp->colball = 0;
}
} /*for bp*/
}
/* creates new softbody if didn't exist yet, makes new points and springs arrays */
static void renew_softbody(Scene *scene, Object *ob, int totpoint, int totspring)
{
SoftBody *sb;
int i;
short softflag;
if (ob->soft == NULL) {
ob->soft = sbNew(scene);
}
else {
free_softbody_intern(ob->soft);
}
sb = ob->soft;
softflag = ob->softflag;
if (totpoint) {
sb->totpoint = totpoint;
sb->totspring = totspring;
sb->bpoint = MEM_mallocN(totpoint * sizeof(BodyPoint), "bodypoint");
if (totspring) {
sb->bspring = MEM_mallocN(totspring * sizeof(BodySpring), "bodyspring");
}
/* initialize BodyPoint array */
for (i = 0; i < totpoint; i++) {
BodyPoint *bp = &sb->bpoint[i];
/* hum as far as i see this is overridden by _final_goal() now jow_go_for2_5 */
/* sadly breaks compatibility with older versions */
/* but makes goals behave the same for meshes, lattices and curves */
if (softflag & OB_SB_GOAL) {
bp->goal = sb->defgoal;
}
else {
bp->goal = 0.0f;
/* so this will definily be below SOFTGOALSNAP */
}
bp->nofsprings = 0;
bp->springs = NULL;
bp->choke = 0.0f;
bp->choke2 = 0.0f;
bp->frozen = 1.0f;
bp->colball = 0.0f;
bp->loc_flag = 0;
bp->springweight = 1.0f;
bp->mass = 1.0f;
}
}
}
static void free_softbody_baked(SoftBody *sb)
{
SBVertex *key;
int k;
for (k = 0; k < sb->totkey; k++) {
key = *(sb->keys + k);
if (key) {
MEM_freeN(key);
}
}
if (sb->keys) {
MEM_freeN(sb->keys);
}
sb->keys = NULL;
sb->totkey = 0;
}
static void free_scratch(SoftBody *sb)
{
if (sb->scratch) {
/* todo make sure everything is cleaned up nicly */
if (sb->scratch->colliderhash) {
BLI_ghash_free(sb->scratch->colliderhash,
NULL,
(GHashValFreeFP)ccd_mesh_free); /*this hoepfully will free all caches*/
sb->scratch->colliderhash = NULL;
}
if (sb->scratch->bodyface) {
MEM_freeN(sb->scratch->bodyface);
}
if (sb->scratch->Ref.ivert) {
MEM_freeN(sb->scratch->Ref.ivert);
}
MEM_freeN(sb->scratch);
sb->scratch = NULL;
}
}
/* only frees internal data */
static void free_softbody_intern(SoftBody *sb)
{
if (sb) {
int a;
BodyPoint *bp;
if (sb->bpoint) {
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
/* free spring list */
if (bp->springs != NULL) {
MEM_freeN(bp->springs);
}
}
MEM_freeN(sb->bpoint);
}
if (sb->bspring) {
MEM_freeN(sb->bspring);
}
sb->totpoint = sb->totspring = 0;
sb->bpoint = NULL;
sb->bspring = NULL;
free_scratch(sb);
free_softbody_baked(sb);
}
}
/* ************ dynamics ********** */
/* the most general (micro physics correct) way to do collision
* (only needs the current particle position)
*
* it actually checks if the particle intrudes a short range force field generated
* by the faces of the target object and returns a force to drive the particel out
* the strength of the field grows exponentially if the particle is on the 'wrong' side of the face
* 'wrong' side : projection to the face normal is negative (all referred to a vertex in the face)
*
* flaw of this: 'fast' particles as well as 'fast' colliding faces
* give a 'tunnel' effect such that the particle passes through the force field
* without ever 'seeing' it
* this is fully compliant to heisenberg: h >= fuzzy(location) * fuzzy(time)
* besides our h is way larger than in QM because forces propagate way slower here
* we have to deal with fuzzy(time) in the range of 1/25 seconds (typical frame rate)
* yup collision targets are not known here any better
* and 1/25 second is very long compared to real collision events
* Q: why not use 'simple' collision here like bouncing back a particle
* --> reverting is velocity on the face normal
* A: because our particles are not alone here
* and need to tell their neighbors exactly what happens via spring forces
* unless sbObjectStep( .. ) is called on sub frame timing level
* BTW that also questions the use of a 'implicit' solvers on softbodies
* since that would only valid for 'slow' moving collision targets and dito particles
*/
/* +++ dependency information functions*/
/**
* \note collection overrides scene when not NULL.
*/
static int query_external_colliders(Depsgraph *depsgraph, Collection *collection)
{
unsigned int numobjects;
Object **objects = BKE_collision_objects_create(
depsgraph, NULL, collection, &numobjects, eModifierType_Collision);
BKE_collision_objects_free(objects);
return (numobjects != 0);
}
/* --- dependency information functions*/
/* +++ the aabb "force" section*/
static int sb_detect_aabb_collisionCached(float UNUSED(force[3]),
struct Object *vertexowner,
float UNUSED(time))
{
Object *ob;
SoftBody *sb = vertexowner->soft;
GHash *hash;
GHashIterator *ihash;
float aabbmin[3], aabbmax[3];
int deflected = 0;
#if 0
int a;
#endif
if ((sb == NULL) || (sb->scratch == NULL)) {
return 0;
}
copy_v3_v3(aabbmin, sb->scratch->aabbmin);
copy_v3_v3(aabbmax, sb->scratch->aabbmax);
hash = vertexowner->soft->scratch->colliderhash;
ihash = BLI_ghashIterator_new(hash);
while (!BLI_ghashIterator_done(ihash)) {
ccd_Mesh *ccdm = BLI_ghashIterator_getValue(ihash);
ob = BLI_ghashIterator_getKey(ihash);
{
/* only with deflecting set */
if (ob->pd && ob->pd->deflect) {
if (ccdm) {
if ((aabbmax[0] < ccdm->bbmin[0]) || (aabbmax[1] < ccdm->bbmin[1]) ||
(aabbmax[2] < ccdm->bbmin[2]) || (aabbmin[0] > ccdm->bbmax[0]) ||
(aabbmin[1] > ccdm->bbmax[1]) || (aabbmin[2] > ccdm->bbmax[2])) {
/* boxes don't intersect */
BLI_ghashIterator_step(ihash);
continue;
}
/* so now we have the 2 boxes overlapping */
/* forces actually not used */
deflected = 2;
}
else {
/*aye that should be cached*/
CLOG_ERROR(&LOG, "missing cache error");
BLI_ghashIterator_step(ihash);
continue;
}
} /* if (ob->pd && ob->pd->deflect) */
BLI_ghashIterator_step(ihash);
}
} /* while () */
BLI_ghashIterator_free(ihash);
return deflected;
}
/* --- the aabb section*/
/* +++ the face external section*/
static int sb_detect_face_pointCached(const float face_v1[3],
const float face_v2[3],
const float face_v3[3],
float *damp,
float force[3],
struct Object *vertexowner,
float time)
{
Object *ob;
GHash *hash;
GHashIterator *ihash;
float nv1[3], edge1[3], edge2[3], d_nvect[3], aabbmin[3], aabbmax[3];
float facedist, outerfacethickness, tune = 10.f;
int a, deflected = 0;
aabbmin[0] = min_fff(face_v1[0], face_v2[0], face_v3[0]);
aabbmin[1] = min_fff(face_v1[1], face_v2[1], face_v3[1]);
aabbmin[2] = min_fff(face_v1[2], face_v2[2], face_v3[2]);
aabbmax[0] = max_fff(face_v1[0], face_v2[0], face_v3[0]);
aabbmax[1] = max_fff(face_v1[1], face_v2[1], face_v3[1]);
aabbmax[2] = max_fff(face_v1[2], face_v2[2], face_v3[2]);
/* calculate face normal once again SIGH */
sub_v3_v3v3(edge1, face_v1, face_v2);
sub_v3_v3v3(edge2, face_v3, face_v2);
cross_v3_v3v3(d_nvect, edge2, edge1);
normalize_v3(d_nvect);
hash = vertexowner->soft->scratch->colliderhash;
ihash = BLI_ghashIterator_new(hash);
while (!BLI_ghashIterator_done(ihash)) {
ccd_Mesh *ccdm = BLI_ghashIterator_getValue(ihash);
ob = BLI_ghashIterator_getKey(ihash);
{
/* only with deflecting set */
if (ob->pd && ob->pd->deflect) {
const MVert *mvert = NULL;
const MVert *mprevvert = NULL;
if (ccdm) {
mvert = ccdm->mvert;
a = ccdm->mvert_num;
mprevvert = ccdm->mprevvert;
outerfacethickness = ob->pd->pdef_sboft;
if ((aabbmax[0] < ccdm->bbmin[0]) || (aabbmax[1] < ccdm->bbmin[1]) ||
(aabbmax[2] < ccdm->bbmin[2]) || (aabbmin[0] > ccdm->bbmax[0]) ||
(aabbmin[1] > ccdm->bbmax[1]) || (aabbmin[2] > ccdm->bbmax[2])) {
/* boxes don't intersect */
BLI_ghashIterator_step(ihash);
continue;
}
}
else {
/*aye that should be cached*/
CLOG_ERROR(&LOG, "missing cache error");
BLI_ghashIterator_step(ihash);
continue;
}
/* use mesh*/
if (mvert) {
while (a) {
copy_v3_v3(nv1, mvert[a - 1].co);
if (mprevvert) {
mul_v3_fl(nv1, time);
madd_v3_v3fl(nv1, mprevvert[a - 1].co, 1.0f - time);
}
/* origin to face_v2*/
sub_v3_v3(nv1, face_v2);
facedist = dot_v3v3(nv1, d_nvect);
if (fabsf(facedist) < outerfacethickness) {
if (isect_point_tri_prism_v3(nv1, face_v1, face_v2, face_v3)) {
float df;
if (facedist > 0) {
df = (outerfacethickness - facedist) / outerfacethickness;
}
else {
df = (outerfacethickness + facedist) / outerfacethickness;
}
*damp = df * tune * ob->pd->pdef_sbdamp;
df = 0.01f * expf(-100.0f * df);
madd_v3_v3fl(force, d_nvect, -df);
deflected = 3;
}
}
a--;
} /* while (a)*/
} /* if (mvert) */
} /* if (ob->pd && ob->pd->deflect) */
BLI_ghashIterator_step(ihash);
}
} /* while () */
BLI_ghashIterator_free(ihash);
return deflected;
}
static int sb_detect_face_collisionCached(const float face_v1[3],
const float face_v2[3],
const float face_v3[3],
float *damp,
float force[3],
struct Object *vertexowner,
float time)
{
Object *ob;
GHash *hash;
GHashIterator *ihash;
float nv1[3], nv2[3], nv3[3], edge1[3], edge2[3], d_nvect[3], aabbmin[3], aabbmax[3];
float t, tune = 10.0f;
int a, deflected = 0;
aabbmin[0] = min_fff(face_v1[0], face_v2[0], face_v3[0]);
aabbmin[1] = min_fff(face_v1[1], face_v2[1], face_v3[1]);
aabbmin[2] = min_fff(face_v1[2], face_v2[2], face_v3[2]);
aabbmax[0] = max_fff(face_v1[0], face_v2[0], face_v3[0]);
aabbmax[1] = max_fff(face_v1[1], face_v2[1], face_v3[1]);
aabbmax[2] = max_fff(face_v1[2], face_v2[2], face_v3[2]);
hash = vertexowner->soft->scratch->colliderhash;
ihash = BLI_ghashIterator_new(hash);
while (!BLI_ghashIterator_done(ihash)) {
ccd_Mesh *ccdm = BLI_ghashIterator_getValue(ihash);
ob = BLI_ghashIterator_getKey(ihash);
{
/* only with deflecting set */
if (ob->pd && ob->pd->deflect) {
const MVert *mvert = NULL;
const MVert *mprevvert = NULL;
const MVertTri *vt = NULL;
const ccdf_minmax *mima = NULL;
if (ccdm) {
mvert = ccdm->mvert;
vt = ccdm->tri;
mprevvert = ccdm->mprevvert;
mima = ccdm->mima;
a = ccdm->tri_num;
if ((aabbmax[0] < ccdm->bbmin[0]) || (aabbmax[1] < ccdm->bbmin[1]) ||
(aabbmax[2] < ccdm->bbmin[2]) || (aabbmin[0] > ccdm->bbmax[0]) ||
(aabbmin[1] > ccdm->bbmax[1]) || (aabbmin[2] > ccdm->bbmax[2])) {
/* boxes don't intersect */
BLI_ghashIterator_step(ihash);
continue;
}
}
else {
/*aye that should be cached*/
CLOG_ERROR(&LOG, "missing cache error");
BLI_ghashIterator_step(ihash);
continue;
}
/* use mesh*/
while (a--) {
if ((aabbmax[0] < mima->minx) || (aabbmin[0] > mima->maxx) ||
(aabbmax[1] < mima->miny) || (aabbmin[1] > mima->maxy) ||
(aabbmax[2] < mima->minz) || (aabbmin[2] > mima->maxz)) {
mima++;
vt++;
continue;
}
if (mvert) {
copy_v3_v3(nv1, mvert[vt->tri[0]].co);
copy_v3_v3(nv2, mvert[vt->tri[1]].co);
copy_v3_v3(nv3, mvert[vt->tri[2]].co);
if (mprevvert) {
mul_v3_fl(nv1, time);
madd_v3_v3fl(nv1, mprevvert[vt->tri[0]].co, 1.0f - time);
mul_v3_fl(nv2, time);
madd_v3_v3fl(nv2, mprevvert[vt->tri[1]].co, 1.0f - time);
mul_v3_fl(nv3, time);
madd_v3_v3fl(nv3, mprevvert[vt->tri[2]].co, 1.0f - time);
}
}
/* switch origin to be nv2*/
sub_v3_v3v3(edge1, nv1, nv2);
sub_v3_v3v3(edge2, nv3, nv2);
cross_v3_v3v3(d_nvect, edge2, edge1);
normalize_v3(d_nvect);
if (isect_line_segment_tri_v3(nv1, nv2, face_v1, face_v2, face_v3, &t, NULL) ||
isect_line_segment_tri_v3(nv2, nv3, face_v1, face_v2, face_v3, &t, NULL) ||
isect_line_segment_tri_v3(nv3, nv1, face_v1, face_v2, face_v3, &t, NULL)) {
madd_v3_v3fl(force, d_nvect, -0.5f);
*damp = tune * ob->pd->pdef_sbdamp;
deflected = 2;
}
mima++;
vt++;
} /* while a */
} /* if (ob->pd && ob->pd->deflect) */
BLI_ghashIterator_step(ihash);
}
} /* while () */
BLI_ghashIterator_free(ihash);
return deflected;
}
static void scan_for_ext_face_forces(Object *ob, float timenow)
{
SoftBody *sb = ob->soft;
BodyFace *bf;
int a;
float damp = 0.0f, choke = 1.0f;
float tune = -10.0f;
float feedback[3];
if (sb && sb->scratch->totface) {
bf = sb->scratch->bodyface;
for (a = 0; a < sb->scratch->totface; a++, bf++) {
bf->ext_force[0] = bf->ext_force[1] = bf->ext_force[2] = 0.0f;
/*+++edges intruding*/
bf->flag &= ~BFF_INTERSECT;
zero_v3(feedback);
if (sb_detect_face_collisionCached(sb->bpoint[bf->v1].pos,
sb->bpoint[bf->v2].pos,
sb->bpoint[bf->v3].pos,
&damp,
feedback,
ob,
timenow)) {
madd_v3_v3fl(sb->bpoint[bf->v1].force, feedback, tune);
madd_v3_v3fl(sb->bpoint[bf->v2].force, feedback, tune);
madd_v3_v3fl(sb->bpoint[bf->v3].force, feedback, tune);
// madd_v3_v3fl(bf->ext_force, feedback, tune);
bf->flag |= BFF_INTERSECT;
choke = min_ff(max_ff(damp, choke), 1.0f);
}
/*---edges intruding*/
/*+++ close vertices*/
if ((bf->flag & BFF_INTERSECT) == 0) {
bf->flag &= ~BFF_CLOSEVERT;
tune = -1.0f;
zero_v3(feedback);
if (sb_detect_face_pointCached(sb->bpoint[bf->v1].pos,
sb->bpoint[bf->v2].pos,
sb->bpoint[bf->v3].pos,
&damp,
feedback,
ob,
timenow)) {
madd_v3_v3fl(sb->bpoint[bf->v1].force, feedback, tune);
madd_v3_v3fl(sb->bpoint[bf->v2].force, feedback, tune);
madd_v3_v3fl(sb->bpoint[bf->v3].force, feedback, tune);
// madd_v3_v3fl(bf->ext_force, feedback, tune);
bf->flag |= BFF_CLOSEVERT;
choke = min_ff(max_ff(damp, choke), 1.0f);
}
}
/*--- close vertices*/
}
bf = sb->scratch->bodyface;
for (a = 0; a < sb->scratch->totface; a++, bf++) {
if ((bf->flag & BFF_INTERSECT) || (bf->flag & BFF_CLOSEVERT)) {
sb->bpoint[bf->v1].choke2 = max_ff(sb->bpoint[bf->v1].choke2, choke);
sb->bpoint[bf->v2].choke2 = max_ff(sb->bpoint[bf->v2].choke2, choke);
sb->bpoint[bf->v3].choke2 = max_ff(sb->bpoint[bf->v3].choke2, choke);
}
}
}
}
/* --- the face external section*/
/* +++ the spring external section*/
static int sb_detect_edge_collisionCached(const float edge_v1[3],
const float edge_v2[3],
float *damp,
float force[3],
struct Object *vertexowner,
float time)
{
Object *ob;
GHash *hash;
GHashIterator *ihash;
float nv1[3], nv2[3], nv3[3], edge1[3], edge2[3], d_nvect[3], aabbmin[3], aabbmax[3];
float t, el;
int a, deflected = 0;
minmax_v3v3_v3(aabbmin, aabbmax, edge_v1);
minmax_v3v3_v3(aabbmin, aabbmax, edge_v2);
el = len_v3v3(edge_v1, edge_v2);
hash = vertexowner->soft->scratch->colliderhash;
ihash = BLI_ghashIterator_new(hash);
while (!BLI_ghashIterator_done(ihash)) {
ccd_Mesh *ccdm = BLI_ghashIterator_getValue(ihash);
ob = BLI_ghashIterator_getKey(ihash);
{
/* only with deflecting set */
if (ob->pd && ob->pd->deflect) {
const MVert *mvert = NULL;
const MVert *mprevvert = NULL;
const MVertTri *vt = NULL;
const ccdf_minmax *mima = NULL;
if (ccdm) {
mvert = ccdm->mvert;
mprevvert = ccdm->mprevvert;
vt = ccdm->tri;
mima = ccdm->mima;
a = ccdm->tri_num;
if ((aabbmax[0] < ccdm->bbmin[0]) || (aabbmax[1] < ccdm->bbmin[1]) ||
(aabbmax[2] < ccdm->bbmin[2]) || (aabbmin[0] > ccdm->bbmax[0]) ||
(aabbmin[1] > ccdm->bbmax[1]) || (aabbmin[2] > ccdm->bbmax[2])) {
/* boxes don't intersect */
BLI_ghashIterator_step(ihash);
continue;
}
}
else {
/*aye that should be cached*/
CLOG_ERROR(&LOG, "missing cache error");
BLI_ghashIterator_step(ihash);
continue;
}
/* use mesh*/
while (a--) {
if ((aabbmax[0] < mima->minx) || (aabbmin[0] > mima->maxx) ||
(aabbmax[1] < mima->miny) || (aabbmin[1] > mima->maxy) ||
(aabbmax[2] < mima->minz) || (aabbmin[2] > mima->maxz)) {
mima++;
vt++;
continue;
}
if (mvert) {
copy_v3_v3(nv1, mvert[vt->tri[0]].co);
copy_v3_v3(nv2, mvert[vt->tri[1]].co);
copy_v3_v3(nv3, mvert[vt->tri[2]].co);
if (mprevvert) {
mul_v3_fl(nv1, time);
madd_v3_v3fl(nv1, mprevvert[vt->tri[0]].co, 1.0f - time);
mul_v3_fl(nv2, time);
madd_v3_v3fl(nv2, mprevvert[vt->tri[1]].co, 1.0f - time);
mul_v3_fl(nv3, time);
madd_v3_v3fl(nv3, mprevvert[vt->tri[2]].co, 1.0f - time);
}
}
/* switch origin to be nv2*/
sub_v3_v3v3(edge1, nv1, nv2);
sub_v3_v3v3(edge2, nv3, nv2);
cross_v3_v3v3(d_nvect, edge2, edge1);
normalize_v3(d_nvect);
if (isect_line_segment_tri_v3(edge_v1, edge_v2, nv1, nv2, nv3, &t, NULL)) {
float v1[3], v2[3];
float intrusiondepth, i1, i2;
sub_v3_v3v3(v1, edge_v1, nv2);
sub_v3_v3v3(v2, edge_v2, nv2);
i1 = dot_v3v3(v1, d_nvect);
i2 = dot_v3v3(v2, d_nvect);
intrusiondepth = -min_ff(i1, i2) / el;
madd_v3_v3fl(force, d_nvect, intrusiondepth);
*damp = ob->pd->pdef_sbdamp;
deflected = 2;
}
mima++;
vt++;
} /* while a */
} /* if (ob->pd && ob->pd->deflect) */
BLI_ghashIterator_step(ihash);
}
} /* while () */
BLI_ghashIterator_free(ihash);
return deflected;
}
static void _scan_for_ext_spring_forces(
Scene *scene, Object *ob, float timenow, int ifirst, int ilast, struct ListBase *effectors)
{
SoftBody *sb = ob->soft;
int a;
float damp;
float feedback[3];
if (sb && sb->totspring) {
for (a = ifirst; a < ilast; a++) {
BodySpring *bs = &sb->bspring[a];
bs->ext_force[0] = bs->ext_force[1] = bs->ext_force[2] = 0.0f;
feedback[0] = feedback[1] = feedback[2] = 0.0f;
bs->flag &= ~BSF_INTERSECT;
if (bs->springtype == SB_EDGE) {
/* +++ springs colliding */
if (ob->softflag & OB_SB_EDGECOLL) {
if (sb_detect_edge_collisionCached(
sb->bpoint[bs->v1].pos, sb->bpoint[bs->v2].pos, &damp, feedback, ob, timenow)) {
add_v3_v3(bs->ext_force, feedback);
bs->flag |= BSF_INTERSECT;
// bs->cf=damp;
bs->cf = sb->choke * 0.01f;
}
}
/* ---- springs colliding */
/* +++ springs seeing wind ... n stuff depending on their orientation*/
/* note we don't use sb->mediafrict but use sb->aeroedge for magnitude of effect*/
if (sb->aeroedge) {
float vel[3], sp[3], pr[3], force[3];
float f, windfactor = 0.25f;
/*see if we have wind*/
if (effectors) {
EffectedPoint epoint;
float speed[3] = {0.0f, 0.0f, 0.0f};
float pos[3];
mid_v3_v3v3(pos, sb->bpoint[bs->v1].pos, sb->bpoint[bs->v2].pos);
mid_v3_v3v3(vel, sb->bpoint[bs->v1].vec, sb->bpoint[bs->v2].vec);
pd_point_from_soft(scene, pos, vel, -1, &epoint);
BKE_effectors_apply(
effectors, NULL, sb->effector_weights, &epoint, force, NULL, speed);
mul_v3_fl(speed, windfactor);
add_v3_v3(vel, speed);
}
/* media in rest */
else {
add_v3_v3v3(vel, sb->bpoint[bs->v1].vec, sb->bpoint[bs->v2].vec);
}
f = normalize_v3(vel);
f = -0.0001f * f * f * sb->aeroedge;
/* (todo) add a nice angle dependent function done for now BUT */
/* still there could be some nice drag/lift function, but who needs it */
sub_v3_v3v3(sp, sb->bpoint[bs->v1].pos, sb->bpoint[bs->v2].pos);
project_v3_v3v3(pr, vel, sp);
sub_v3_v3(vel, pr);
normalize_v3(vel);
if (ob->softflag & OB_SB_AERO_ANGLE) {
normalize_v3(sp);
madd_v3_v3fl(bs->ext_force, vel, f * (1.0f - fabsf(dot_v3v3(vel, sp))));
}
else {
madd_v3_v3fl(bs->ext_force, vel, f); // to keep compatible with 2.45 release files
}
}
/* --- springs seeing wind */
}
}
}
}
static void *exec_scan_for_ext_spring_forces(void *data)
{
SB_thread_context *pctx = (SB_thread_context *)data;
_scan_for_ext_spring_forces(
pctx->scene, pctx->ob, pctx->timenow, pctx->ifirst, pctx->ilast, pctx->effectors);
return NULL;
}
static void sb_sfesf_threads_run(struct Depsgraph *depsgraph,
Scene *scene,
struct Object *ob,
float timenow,
int totsprings,
int *UNUSED(ptr_to_break_func(void)))
{
ListBase threads;
SB_thread_context *sb_threads;
int i, totthread, left, dec;
/* wild guess .. may increase with better thread management 'above'
* or even be UI option sb->spawn_cf_threads_nopts */
int lowsprings = 100;
ListBase *effectors = BKE_effectors_create(depsgraph, ob, NULL, ob->soft->effector_weights);
/* figure the number of threads while preventing pretty pointless threading overhead */
totthread = BKE_scene_num_threads(scene);
/* what if we got zillions of CPUs running but less to spread*/
while ((totsprings / totthread < lowsprings) && (totthread > 1)) {
totthread--;
}
sb_threads = MEM_callocN(sizeof(SB_thread_context) * totthread, "SBSpringsThread");
memset(sb_threads, 0, sizeof(SB_thread_context) * totthread);
left = totsprings;
dec = totsprings / totthread + 1;
for (i = 0; i < totthread; i++) {
sb_threads[i].scene = scene;
sb_threads[i].ob = ob;
sb_threads[i].forcetime = 0.0; // not used here
sb_threads[i].timenow = timenow;
sb_threads[i].ilast = left;
left = left - dec;
if (left > 0) {
sb_threads[i].ifirst = left;
}
else {
sb_threads[i].ifirst = 0;
}
sb_threads[i].effectors = effectors;
sb_threads[i].do_deflector = false; // not used here
sb_threads[i].fieldfactor = 0.0f; // not used here
sb_threads[i].windfactor = 0.0f; // not used here
sb_threads[i].nr = i;
sb_threads[i].tot = totthread;
}
if (totthread > 1) {
BLI_threadpool_init(&threads, exec_scan_for_ext_spring_forces, totthread);
for (i = 0; i < totthread; i++) {
BLI_threadpool_insert(&threads, &sb_threads[i]);
}
BLI_threadpool_end(&threads);
}
else {
exec_scan_for_ext_spring_forces(&sb_threads[0]);
}
/* clean up */
MEM_freeN(sb_threads);
BKE_effectors_free(effectors);
}
/* --- the spring external section*/
static int choose_winner(
float *w, float *pos, float *a, float *b, float *c, float *ca, float *cb, float *cc)
{
float mindist, cp;
int winner = 1;
mindist = fabsf(dot_v3v3(pos, a));
cp = fabsf(dot_v3v3(pos, b));
if (mindist < cp) {
mindist = cp;
winner = 2;
}
cp = fabsf(dot_v3v3(pos, c));
if (mindist < cp) {
mindist = cp;
winner = 3;
}
switch (winner) {
case 1:
copy_v3_v3(w, ca);
break;
case 2:
copy_v3_v3(w, cb);
break;
case 3:
copy_v3_v3(w, cc);
}
return winner;
}
static int sb_detect_vertex_collisionCached(float opco[3],
float facenormal[3],
float *damp,
float force[3],
struct Object *vertexowner,
float time,
float vel[3],
float *intrusion)
{
Object *ob = NULL;
GHash *hash;
GHashIterator *ihash;
float nv1[3], nv2[3], nv3[3], edge1[3], edge2[3], d_nvect[3], dv1[3], ve[3],
avel[3] = {0.0, 0.0, 0.0}, vv1[3], vv2[3], vv3[3], coledge[3] = {0.0f, 0.0f, 0.0f},
mindistedge = 1000.0f, outerforceaccu[3], innerforceaccu[3], facedist,
/* n_mag, */ /* UNUSED */ force_mag_norm, minx, miny, minz, maxx, maxy, maxz,
innerfacethickness = -0.5f, outerfacethickness = 0.2f, ee = 5.0f, ff = 0.1f, fa = 1;
int a, deflected = 0, cavel = 0, ci = 0;
/* init */
*intrusion = 0.0f;
hash = vertexowner->soft->scratch->colliderhash;
ihash = BLI_ghashIterator_new(hash);
outerforceaccu[0] = outerforceaccu[1] = outerforceaccu[2] = 0.0f;
innerforceaccu[0] = innerforceaccu[1] = innerforceaccu[2] = 0.0f;
/* go */
while (!BLI_ghashIterator_done(ihash)) {
ccd_Mesh *ccdm = BLI_ghashIterator_getValue(ihash);
ob = BLI_ghashIterator_getKey(ihash);
{
/* only with deflecting set */
if (ob->pd && ob->pd->deflect) {
const MVert *mvert = NULL;
const MVert *mprevvert = NULL;
const MVertTri *vt = NULL;
const ccdf_minmax *mima = NULL;
if (ccdm) {
mvert = ccdm->mvert;
mprevvert = ccdm->mprevvert;
vt = ccdm->tri;
mima = ccdm->mima;
a = ccdm->tri_num;
minx = ccdm->bbmin[0];
miny = ccdm->bbmin[1];
minz = ccdm->bbmin[2];
maxx = ccdm->bbmax[0];
maxy = ccdm->bbmax[1];
maxz = ccdm->bbmax[2];
if ((opco[0] < minx) || (opco[1] < miny) || (opco[2] < minz) || (opco[0] > maxx) ||
(opco[1] > maxy) || (opco[2] > maxz)) {
/* outside the padded boundbox --> collision object is too far away */
BLI_ghashIterator_step(ihash);
continue;
}
}
else {
/*aye that should be cached*/
CLOG_ERROR(&LOG, "missing cache error");
BLI_ghashIterator_step(ihash);
continue;
}
/* do object level stuff */
/* need to have user control for that since it depends on model scale */
innerfacethickness = -ob->pd->pdef_sbift;
outerfacethickness = ob->pd->pdef_sboft;
fa = (ff * outerfacethickness - outerfacethickness);
fa *= fa;
fa = 1.0f / fa;
avel[0] = avel[1] = avel[2] = 0.0f;
/* use mesh*/
while (a--) {
if ((opco[0] < mima->minx) || (opco[0] > mima->maxx) || (opco[1] < mima->miny) ||
(opco[1] > mima->maxy) || (opco[2] < mima->minz) || (opco[2] > mima->maxz)) {
mima++;
vt++;
continue;
}
if (mvert) {
copy_v3_v3(nv1, mvert[vt->tri[0]].co);
copy_v3_v3(nv2, mvert[vt->tri[1]].co);
copy_v3_v3(nv3, mvert[vt->tri[2]].co);
if (mprevvert) {
/* Grab the average speed of the collider vertices before we spoil nvX
* humm could be done once a SB steps but then we' need to store that too
* since the AABB reduced probability to get here drastically
* it might be a nice tradeoff CPU <--> memory.
*/
sub_v3_v3v3(vv1, nv1, mprevvert[vt->tri[0]].co);
sub_v3_v3v3(vv2, nv2, mprevvert[vt->tri[1]].co);
sub_v3_v3v3(vv3, nv3, mprevvert[vt->tri[2]].co);
mul_v3_fl(nv1, time);
madd_v3_v3fl(nv1, mprevvert[vt->tri[0]].co, 1.0f - time);
mul_v3_fl(nv2, time);
madd_v3_v3fl(nv2, mprevvert[vt->tri[1]].co, 1.0f - time);
mul_v3_fl(nv3, time);
madd_v3_v3fl(nv3, mprevvert[vt->tri[2]].co, 1.0f - time);
}
}
/* switch origin to be nv2*/
sub_v3_v3v3(edge1, nv1, nv2);
sub_v3_v3v3(edge2, nv3, nv2);
/* Abuse dv1 to have vertex in question at *origin* of triangle. */
sub_v3_v3v3(dv1, opco, nv2);
cross_v3_v3v3(d_nvect, edge2, edge1);
/* n_mag = */ /* UNUSED */ normalize_v3(d_nvect);
facedist = dot_v3v3(dv1, d_nvect);
// so rules are
//
if ((facedist > innerfacethickness) && (facedist < outerfacethickness)) {
if (isect_point_tri_prism_v3(opco, nv1, nv2, nv3)) {
force_mag_norm = (float)exp(-ee * facedist);
if (facedist > outerfacethickness * ff) {
force_mag_norm = (float)force_mag_norm * fa * (facedist - outerfacethickness) *
(facedist - outerfacethickness);
}
*damp = ob->pd->pdef_sbdamp;
if (facedist > 0.0f) {
*damp *= (1.0f - facedist / outerfacethickness);
madd_v3_v3fl(outerforceaccu, d_nvect, force_mag_norm);
deflected = 3;
}
else {
madd_v3_v3fl(innerforceaccu, d_nvect, force_mag_norm);
if (deflected < 2) {
deflected = 2;
}
}
if ((mprevvert) && (*damp > 0.0f)) {
choose_winner(ve, opco, nv1, nv2, nv3, vv1, vv2, vv3);
add_v3_v3(avel, ve);
cavel++;
}
*intrusion += facedist;
ci++;
}
}
mima++;
vt++;
} /* while a */
} /* if (ob->pd && ob->pd->deflect) */
BLI_ghashIterator_step(ihash);
}
} /* while () */
if (deflected == 1) { // no face but 'outer' edge cylinder sees vert
force_mag_norm = (float)exp(-ee * mindistedge);
if (mindistedge > outerfacethickness * ff) {
force_mag_norm = (float)force_mag_norm * fa * (mindistedge - outerfacethickness) *
(mindistedge - outerfacethickness);
}
madd_v3_v3fl(force, coledge, force_mag_norm);
*damp = ob->pd->pdef_sbdamp;
if (mindistedge > 0.0f) {
*damp *= (1.0f - mindistedge / outerfacethickness);
}
}
if (deflected == 2) { // face inner detected
add_v3_v3(force, innerforceaccu);
}
if (deflected == 3) { // face outer detected
add_v3_v3(force, outerforceaccu);
}
BLI_ghashIterator_free(ihash);
if (cavel) {
mul_v3_fl(avel, 1.0f / (float)cavel);
}
copy_v3_v3(vel, avel);
if (ci) {
*intrusion /= ci;
}
if (deflected) {
normalize_v3_v3(facenormal, force);
}
return deflected;
}
/* sandbox to plug in various deflection algos */
static int sb_deflect_face(Object *ob,
float *actpos,
float *facenormal,
float *force,
float *cf,
float time,
float *vel,
float *intrusion)
{
float s_actpos[3];
int deflected;
copy_v3_v3(s_actpos, actpos);
deflected = sb_detect_vertex_collisionCached(
s_actpos, facenormal, cf, force, ob, time, vel, intrusion);
#if 0
deflected = sb_detect_vertex_collisionCachedEx(
s_actpos, facenormal, cf, force, ob, time, vel, intrusion);
#endif
return deflected;
}
/* hiding this for now .. but the jacobian may pop up on other tasks .. so i'd like to keep it */
#if 0
static void dfdx_spring(int ia, int ic, int op, float dir[3], float L, float len, float factor)
{
float m, delta_ij;
int i, j;
if (L < len) {
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
delta_ij = (i == j ? (1.0f) : (0.0f));
m = factor * (dir[i] * dir[j] + (1 - L / len) * (delta_ij - dir[i] * dir[j]));
EIG_linear_solver_matrix_add(ia + i, op + ic + j, m);
}
}
}
else {
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
m = factor * dir[i] * dir[j];
EIG_linear_solver_matrix_add(ia + i, op + ic + j, m);
}
}
}
}
static void dfdx_goal(int ia, int ic, int op, float factor)
{
int i;
for (i = 0; i < 3; i++) {
EIG_linear_solver_matrix_add(ia + i, op + ic + i, factor);
}
}
static void dfdv_goal(int ia, int ic, float factor)
{
int i;
for (i = 0; i < 3; i++) {
EIG_linear_solver_matrix_add(ia + i, ic + i, factor);
}
}
#endif /* if 0 */
static void sb_spring_force(
Object *ob, int bpi, BodySpring *bs, float iks, float UNUSED(forcetime))
{
SoftBody *sb = ob->soft; /* is supposed to be there */
BodyPoint *bp1, *bp2;
float dir[3], dvel[3];
float distance, forcefactor, kd, absvel, projvel, kw;
#if 0 /* UNUSED */
int ia, ic;
#endif
/* prepare depending on which side of the spring we are on */
if (bpi == bs->v1) {
bp1 = &sb->bpoint[bs->v1];
bp2 = &sb->bpoint[bs->v2];
#if 0 /* UNUSED */
ia = 3 * bs->v1;
ic = 3 * bs->v2;
#endif
}
else if (bpi == bs->v2) {
bp1 = &sb->bpoint[bs->v2];
bp2 = &sb->bpoint[bs->v1];
#if 0 /* UNUSED */
ia = 3 * bs->v2;
ic = 3 * bs->v1;
#endif
}
else {
/* TODO make this debug option */
CLOG_WARN(&LOG, "bodypoint is not attached to spring <*bs>");
return;
}
/* do bp1 <--> bp2 elastic */
sub_v3_v3v3(dir, bp1->pos, bp2->pos);
distance = normalize_v3(dir);
if (bs->len < distance) {
iks = 1.0f / (1.0f - sb->inspring) - 1.0f; /* inner spring constants function */
}
else {
iks = 1.0f / (1.0f - sb->inpush) - 1.0f; /* inner spring constants function */
}
if (bs->len > 0.0f) { /* check for degenerated springs */
forcefactor = iks / bs->len;
}
else {
forcefactor = iks;
}
kw = (bp1->springweight + bp2->springweight) / 2.0f;
kw = kw * kw;
kw = kw * kw;
switch (bs->springtype) {
case SB_EDGE:
case SB_HANDLE:
forcefactor *= kw;
break;
case SB_BEND:
forcefactor *= sb->secondspring * kw;
break;
case SB_STIFFQUAD:
forcefactor *= sb->shearstiff * sb->shearstiff * kw;
break;
default:
break;
}
madd_v3_v3fl(bp1->force, dir, (bs->len - distance) * forcefactor);
/* do bp1 <--> bp2 viscous */
sub_v3_v3v3(dvel, bp1->vec, bp2->vec);
kd = sb->infrict * sb_fric_force_scale(ob);
absvel = normalize_v3(dvel);
projvel = dot_v3v3(dir, dvel);
kd *= absvel * projvel;
madd_v3_v3fl(bp1->force, dir, -kd);
}
/* since this is definitely the most CPU consuming task here .. try to spread it */
/* core function _softbody_calc_forces_slice_in_a_thread */
/* result is int to be able to flag user break */
static int _softbody_calc_forces_slice_in_a_thread(Scene *scene,
Object *ob,
float forcetime,
float timenow,
int ifirst,
int ilast,
int *UNUSED(ptr_to_break_func(void)),
ListBase *effectors,
int do_deflector,
float fieldfactor,
float windfactor)
{
float iks;
int bb, do_selfcollision, do_springcollision, do_aero;
int number_of_points_here = ilast - ifirst;
SoftBody *sb = ob->soft; /* is supposed to be there */
BodyPoint *bp;
/* initialize */
if (sb) {
/* check conditions for various options */
/* +++ could be done on object level to squeeze out the last bits of it */
do_selfcollision = ((ob->softflag & OB_SB_EDGES) && (sb->bspring) &&
(ob->softflag & OB_SB_SELF));
do_springcollision = do_deflector && (ob->softflag & OB_SB_EDGES) &&
(ob->softflag & OB_SB_EDGECOLL);
do_aero = ((sb->aeroedge) && (ob->softflag & OB_SB_EDGES));
/* --- could be done on object level to squeeze out the last bits of it */
}
else {
CLOG_ERROR(&LOG, "expected a SB here");
return 999;
}
/* debugerin */
if (sb->totpoint < ifirst) {
printf("Aye 998");
return 998;
}
/* debugerin */
bp = &sb->bpoint[ifirst];
for (bb = number_of_points_here; bb > 0; bb--, bp++) {
/* clear forces accumulator */
bp->force[0] = bp->force[1] = bp->force[2] = 0.0;
/* naive ball self collision */
/* needs to be done if goal snaps or not */
if (do_selfcollision) {
int attached;
BodyPoint *obp;
BodySpring *bs;
int c, b;
float velcenter[3], dvel[3], def[3];
float distance;
float compare;
float bstune = sb->ballstiff;
/* Running in a slice we must not assume anything done with obp
* neither alter the data of obp. */
for (c = sb->totpoint, obp = sb->bpoint; c > 0; c--, obp++) {
compare = (obp->colball + bp->colball);
sub_v3_v3v3(def, bp->pos, obp->pos);
/* rather check the AABBoxes before ever calculating the real distance */
/* mathematically it is completely nuts, but performance is pretty much (3) times faster */
if ((fabsf(def[0]) > compare) || (fabsf(def[1]) > compare) || (fabsf(def[2]) > compare)) {
continue;
}
distance = normalize_v3(def);
if (distance < compare) {
/* exclude body points attached with a spring */
attached = 0;
for (b = obp->nofsprings; b > 0; b--) {
bs = sb->bspring + obp->springs[b - 1];
if ((ilast - bb == bs->v2) || (ilast - bb == bs->v1)) {
attached = 1;
continue;
}
}
if (!attached) {
float f = bstune / (distance) + bstune / (compare * compare) * distance -
2.0f * bstune / compare;
mid_v3_v3v3(velcenter, bp->vec, obp->vec);
sub_v3_v3v3(dvel, velcenter, bp->vec);
mul_v3_fl(dvel, _final_mass(ob, bp));
madd_v3_v3fl(bp->force, def, f * (1.0f - sb->balldamp));
madd_v3_v3fl(bp->force, dvel, sb->balldamp);
}
}
}
}
/* naive ball self collision done */
if (_final_goal(ob, bp) < SOFTGOALSNAP) { /* omit this bp when it snaps */
float auxvect[3];
float velgoal[3];
/* do goal stuff */
if (ob->softflag & OB_SB_GOAL) {
/* true elastic goal */
float ks, kd;
sub_v3_v3v3(auxvect, bp->pos, bp->origT);
ks = 1.0f / (1.0f - _final_goal(ob, bp) * sb->goalspring) - 1.0f;
bp->force[0] += -ks * (auxvect[0]);
bp->force[1] += -ks * (auxvect[1]);
bp->force[2] += -ks * (auxvect[2]);
/* calculate damping forces generated by goals*/
sub_v3_v3v3(velgoal, bp->origS, bp->origE);
kd = sb->goalfrict * sb_fric_force_scale(ob);
add_v3_v3v3(auxvect, velgoal, bp->vec);
if (forcetime >
0.0f) { /* make sure friction does not become rocket motor on time reversal */
bp->force[0] -= kd * (auxvect[0]);
bp->force[1] -= kd * (auxvect[1]);
bp->force[2] -= kd * (auxvect[2]);
}
else {
bp->force[0] -= kd * (velgoal[0] - bp->vec[0]);
bp->force[1] -= kd * (velgoal[1] - bp->vec[1]);
bp->force[2] -= kd * (velgoal[2] - bp->vec[2]);
}
}
/* done goal stuff */
/* gravitation */
if (scene->physics_settings.flag & PHYS_GLOBAL_GRAVITY) {
float gravity[3];
copy_v3_v3(gravity, scene->physics_settings.gravity);
/* Individual mass of node here. */
mul_v3_fl(gravity,
sb_grav_force_scale(ob) * _final_mass(ob, bp) *
sb->effector_weights->global_gravity);
add_v3_v3(bp->force, gravity);
}
/* particle field & vortex */
if (effectors) {
EffectedPoint epoint;
float kd;
float force[3] = {0.0f, 0.0f, 0.0f};
float speed[3] = {0.0f, 0.0f, 0.0f};
/* just for calling function once */
float eval_sb_fric_force_scale = sb_fric_force_scale(ob);
pd_point_from_soft(scene, bp->pos, bp->vec, sb->bpoint - bp, &epoint);
BKE_effectors_apply(effectors, NULL, sb->effector_weights, &epoint, force, NULL, speed);
/* apply forcefield*/
mul_v3_fl(force, fieldfactor * eval_sb_fric_force_scale);
add_v3_v3(bp->force, force);
/* BP friction in moving media */
kd = sb->mediafrict * eval_sb_fric_force_scale;
bp->force[0] -= kd * (bp->vec[0] + windfactor * speed[0] / eval_sb_fric_force_scale);
bp->force[1] -= kd * (bp->vec[1] + windfactor * speed[1] / eval_sb_fric_force_scale);
bp->force[2] -= kd * (bp->vec[2] + windfactor * speed[2] / eval_sb_fric_force_scale);
/* now we'll have nice centrifugal effect for vortex */
}
else {
/* BP friction in media (not) moving*/
float kd = sb->mediafrict * sb_fric_force_scale(ob);
/* assume it to be proportional to actual velocity */
bp->force[0] -= bp->vec[0] * kd;
bp->force[1] -= bp->vec[1] * kd;
bp->force[2] -= bp->vec[2] * kd;
/* friction in media done */
}
/* +++cached collision targets */
bp->choke = 0.0f;
bp->choke2 = 0.0f;
bp->loc_flag &= ~SBF_DOFUZZY;
if (do_deflector && !(bp->loc_flag & SBF_OUTOFCOLLISION)) {
float cfforce[3], defforce[3] = {0.0f, 0.0f, 0.0f}, vel[3] = {0.0f, 0.0f, 0.0f},
facenormal[3], cf = 1.0f, intrusion;
float kd = 1.0f;
if (sb_deflect_face(ob, bp->pos, facenormal, defforce, &cf, timenow, vel, &intrusion)) {
if (intrusion < 0.0f) {
sb->scratch->flag |= SBF_DOFUZZY;
bp->loc_flag |= SBF_DOFUZZY;
bp->choke = sb->choke * 0.01f;
}
sub_v3_v3v3(cfforce, bp->vec, vel);
madd_v3_v3fl(bp->force, cfforce, -cf * 50.0f);
madd_v3_v3fl(bp->force, defforce, kd);
}
}
/* ---cached collision targets */
/* +++springs */
iks = 1.0f / (1.0f - sb->inspring) - 1.0f; /* inner spring constants function */
if (ob->softflag & OB_SB_EDGES) {
if (sb->bspring) { /* spring list exists at all ? */
int b;
BodySpring *bs;
for (b = bp->nofsprings; b > 0; b--) {
bs = sb->bspring + bp->springs[b - 1];
if (do_springcollision || do_aero) {
add_v3_v3(bp->force, bs->ext_force);
if (bs->flag & BSF_INTERSECT) {
bp->choke = bs->cf;
}
}
// sb_spring_force(Object *ob, int bpi, BodySpring *bs, float iks, float forcetime)
sb_spring_force(ob, ilast - bb, bs, iks, forcetime);
} /* loop springs */
} /* existing spring list */
} /*any edges*/
/* ---springs */
} /*omit on snap */
} /*loop all bp's*/
return 0; /*done fine*/
}
static void *exec_softbody_calc_forces(void *data)
{
SB_thread_context *pctx = (SB_thread_context *)data;
_softbody_calc_forces_slice_in_a_thread(pctx->scene,
pctx->ob,
pctx->forcetime,
pctx->timenow,
pctx->ifirst,
pctx->ilast,
NULL,
pctx->effectors,
pctx->do_deflector,
pctx->fieldfactor,
pctx->windfactor);
return NULL;
}
static void sb_cf_threads_run(Scene *scene,
Object *ob,
float forcetime,
float timenow,
int totpoint,
int *UNUSED(ptr_to_break_func(void)),
struct ListBase *effectors,
int do_deflector,
float fieldfactor,
float windfactor)
{
ListBase threads;
SB_thread_context *sb_threads;
int i, totthread, left, dec;
/* wild guess .. may increase with better thread management 'above'
* or even be UI option sb->spawn_cf_threads_nopts. */
int lowpoints = 100;
/* figure the number of threads while preventing pretty pointless threading overhead */
totthread = BKE_scene_num_threads(scene);
/* what if we got zillions of CPUs running but less to spread*/
while ((totpoint / totthread < lowpoints) && (totthread > 1)) {
totthread--;
}
/* printf("sb_cf_threads_run spawning %d threads\n", totthread); */
sb_threads = MEM_callocN(sizeof(SB_thread_context) * totthread, "SBThread");
memset(sb_threads, 0, sizeof(SB_thread_context) * totthread);
left = totpoint;
dec = totpoint / totthread + 1;
for (i = 0; i < totthread; i++) {
sb_threads[i].scene = scene;
sb_threads[i].ob = ob;
sb_threads[i].forcetime = forcetime;
sb_threads[i].timenow = timenow;
sb_threads[i].ilast = left;
left = left - dec;
if (left > 0) {
sb_threads[i].ifirst = left;
}
else {
sb_threads[i].ifirst = 0;
}
sb_threads[i].effectors = effectors;
sb_threads[i].do_deflector = do_deflector;
sb_threads[i].fieldfactor = fieldfactor;
sb_threads[i].windfactor = windfactor;
sb_threads[i].nr = i;
sb_threads[i].tot = totthread;
}
if (totthread > 1) {
BLI_threadpool_init(&threads, exec_softbody_calc_forces, totthread);
for (i = 0; i < totthread; i++) {
BLI_threadpool_insert(&threads, &sb_threads[i]);
}
BLI_threadpool_end(&threads);
}
else {
exec_softbody_calc_forces(&sb_threads[0]);
}
/* clean up */
MEM_freeN(sb_threads);
}
static void softbody_calc_forces(
struct Depsgraph *depsgraph, Scene *scene, Object *ob, float forcetime, float timenow)
{
/* rule we never alter free variables :bp->vec bp->pos in here !
* this will ruin adaptive stepsize AKA heun! (BM)
*/
SoftBody *sb = ob->soft; /* is supposed to be there */
/*BodyPoint *bproot;*/ /* UNUSED */
/* float gravity; */ /* UNUSED */
/* float iks; */
float fieldfactor = -1.0f, windfactor = 0.25;
int do_deflector /*, do_selfcollision*/, do_springcollision, do_aero;
/* gravity = sb->grav * sb_grav_force_scale(ob); */ /* UNUSED */
/* check conditions for various options */
do_deflector = query_external_colliders(depsgraph, sb->collision_group);
#if 0
do_selfcollision=((ob->softflag & OB_SB_EDGES) && (sb->bspring)&& (ob->softflag & OB_SB_SELF));
#endif
do_springcollision = do_deflector && (ob->softflag & OB_SB_EDGES) &&
(ob->softflag & OB_SB_EDGECOLL);
do_aero = ((sb->aeroedge) && (ob->softflag & OB_SB_EDGES));
/* iks = 1.0f/(1.0f-sb->inspring)-1.0f; */ /* inner spring constants function */ /* UNUSED */
/* bproot= sb->bpoint; */ /* need this for proper spring addressing */ /* UNUSED */
if (do_springcollision || do_aero) {
sb_sfesf_threads_run(depsgraph, scene, ob, timenow, sb->totspring, NULL);
}
/* after spring scan because it uses Effoctors too */
ListBase *effectors = BKE_effectors_create(depsgraph, ob, NULL, sb->effector_weights);
if (do_deflector) {
float defforce[3];
do_deflector = sb_detect_aabb_collisionCached(defforce, ob, timenow);
}
sb_cf_threads_run(scene,
ob,
forcetime,
timenow,
sb->totpoint,
NULL,
effectors,
do_deflector,
fieldfactor,
windfactor);
/* finally add forces caused by face collision */
if (ob->softflag & OB_SB_FACECOLL) {
scan_for_ext_face_forces(ob, timenow);
}
/* finish matrix and solve */
BKE_effectors_free(effectors);
}
static void softbody_apply_forces(Object *ob, float forcetime, int mode, float *err, int mid_flags)
{
/* time evolution */
/* actually does an explicit euler step mode == 0 */
/* or heun ~ 2nd order runge-kutta steps, mode 1, 2 */
SoftBody *sb = ob->soft; /* is supposed to be there */
BodyPoint *bp;
float dx[3] = {0}, dv[3], aabbmin[3], aabbmax[3], cm[3] = {0.0f, 0.0f, 0.0f};
float timeovermass /*, freezeloc=0.00001f, freezeforce=0.00000000001f*/;
float maxerrpos = 0.0f, maxerrvel = 0.0f;
int a, fuzzy = 0;
forcetime *= sb_time_scale(ob);
aabbmin[0] = aabbmin[1] = aabbmin[2] = 1e20f;
aabbmax[0] = aabbmax[1] = aabbmax[2] = -1e20f;
/* old one with homogeneous masses */
/* claim a minimum mass for vertex */
#if 0
if (sb->nodemass > 0.009999f) {
timeovermass = forcetime / sb->nodemass;
}
else {
timeovermass = forcetime / 0.009999f;
}
#endif
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
/* Now we have individual masses. */
/* claim a minimum mass for vertex */
if (_final_mass(ob, bp) > 0.009999f) {
timeovermass = forcetime / _final_mass(ob, bp);
}
else {
timeovermass = forcetime / 0.009999f;
}
if (_final_goal(ob, bp) < SOFTGOALSNAP) {
/* this makes t~ = t */
if (mid_flags & MID_PRESERVE) {
copy_v3_v3(dx, bp->vec);
}
/**
* So here is:
*
* (v)' = a(cceleration) =
* sum(F_springs)/m + gravitation + some friction forces + more forces.
*
*
* The ( ... )' operator denotes derivate respective time.
*
* The euler step for velocity then becomes:
*
* v(t + dt) = v(t) + a(t) * dt
*
*/
mul_v3_fl(bp->force, timeovermass); /* individual mass of node here */
/* some nasty if's to have heun in here too */
copy_v3_v3(dv, bp->force);
if (mode == 1) {
copy_v3_v3(bp->prevvec, bp->vec);
copy_v3_v3(bp->prevdv, dv);
}
if (mode == 2) {
/* be optimistic and execute step */
bp->vec[0] = bp->prevvec[0] + 0.5f * (dv[0] + bp->prevdv[0]);
bp->vec[1] = bp->prevvec[1] + 0.5f * (dv[1] + bp->prevdv[1]);
bp->vec[2] = bp->prevvec[2] + 0.5f * (dv[2] + bp->prevdv[2]);
/* compare euler to heun to estimate error for step sizing */
maxerrvel = max_ff(maxerrvel, fabsf(dv[0] - bp->prevdv[0]));
maxerrvel = max_ff(maxerrvel, fabsf(dv[1] - bp->prevdv[1]));
maxerrvel = max_ff(maxerrvel, fabsf(dv[2] - bp->prevdv[2]));
}
else {
add_v3_v3(bp->vec, bp->force);
}
/* this makes t~ = t+dt */
if (!(mid_flags & MID_PRESERVE)) {
copy_v3_v3(dx, bp->vec);
}
/* so here is (x)'= v(elocity) */
/* the euler step for location then becomes */
/* x(t + dt) = x(t) + v(t~) * dt */
mul_v3_fl(dx, forcetime);
/* the freezer coming sooner or later */
#if 0
if ((dot_v3v3(dx, dx) < freezeloc) && (dot_v3v3(bp->force, bp->force) < freezeforce)) {
bp->frozen /= 2;
}
else {
bp->frozen = min_ff(bp->frozen * 1.05f, 1.0f);
}
mul_v3_fl(dx, bp->frozen);
#endif
/* again some nasty if's to have heun in here too */
if (mode == 1) {
copy_v3_v3(bp->prevpos, bp->pos);
copy_v3_v3(bp->prevdx, dx);
}
if (mode == 2) {
bp->pos[0] = bp->prevpos[0] + 0.5f * (dx[0] + bp->prevdx[0]);
bp->pos[1] = bp->prevpos[1] + 0.5f * (dx[1] + bp->prevdx[1]);
bp->pos[2] = bp->prevpos[2] + 0.5f * (dx[2] + bp->prevdx[2]);
maxerrpos = max_ff(maxerrpos, fabsf(dx[0] - bp->prevdx[0]));
maxerrpos = max_ff(maxerrpos, fabsf(dx[1] - bp->prevdx[1]));
maxerrpos = max_ff(maxerrpos, fabsf(dx[2] - bp->prevdx[2]));
/* bp->choke is set when we need to pull a vertex or edge out of the collider.
* the collider object signals to get out by pushing hard. on the other hand
* we don't want to end up in deep space so we add some
* to balance that out */
if (bp->choke2 > 0.0f) {
mul_v3_fl(bp->vec, (1.0f - bp->choke2));
}
if (bp->choke > 0.0f) {
mul_v3_fl(bp->vec, (1.0f - bp->choke));
}
}
else {
add_v3_v3(bp->pos, dx);
}
} /*snap*/
/* so while we are looping BPs anyway do statistics on the fly */
minmax_v3v3_v3(aabbmin, aabbmax, bp->pos);
if (bp->loc_flag & SBF_DOFUZZY) {
fuzzy = 1;
}
} /*for*/
if (sb->totpoint) {
mul_v3_fl(cm, 1.0f / sb->totpoint);
}
if (sb->scratch) {
copy_v3_v3(sb->scratch->aabbmin, aabbmin);
copy_v3_v3(sb->scratch->aabbmax, aabbmax);
}
if (err) { /* so step size will be controlled by biggest difference in slope */
if (sb->solverflags & SBSO_OLDERR) {
*err = max_ff(maxerrpos, maxerrvel);
}
else {
*err = maxerrpos;
}
// printf("EP %f EV %f\n", maxerrpos, maxerrvel);
if (fuzzy) {
*err /= sb->fuzzyness;
}
}
}
/* used by heun when it overshoots */
static void softbody_restore_prev_step(Object *ob)
{
SoftBody *sb = ob->soft; /* is supposed to be there*/
BodyPoint *bp;
int a;
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
copy_v3_v3(bp->vec, bp->prevvec);
copy_v3_v3(bp->pos, bp->prevpos);
}
}
#if 0
static void softbody_store_step(Object *ob)
{
SoftBody *sb = ob->soft; /* is supposed to be there*/
BodyPoint *bp;
int a;
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
copy_v3_v3(bp->prevvec, bp->vec);
copy_v3_v3(bp->prevpos, bp->pos);
}
}
/* used by predictors and correctors */
static void softbody_store_state(Object *ob, float *ppos, float *pvel)
{
SoftBody *sb = ob->soft; /* is supposed to be there*/
BodyPoint *bp;
int a;
float *pp = ppos, *pv = pvel;
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
copy_v3_v3(pv, bp->vec);
pv += 3;
copy_v3_v3(pp, bp->pos);
pp += 3;
}
}
/* used by predictors and correctors */
static void softbody_retrieve_state(Object *ob, float *ppos, float *pvel)
{
SoftBody *sb = ob->soft; /* is supposed to be there*/
BodyPoint *bp;
int a;
float *pp = ppos, *pv = pvel;
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
copy_v3_v3(bp->vec, pv);
pv += 3;
copy_v3_v3(bp->pos, pp);
pp += 3;
}
}
/* used by predictors and correctors */
static void softbody_swap_state(Object *ob, float *ppos, float *pvel)
{
SoftBody *sb = ob->soft; /* is supposed to be there*/
BodyPoint *bp;
int a;
float *pp = ppos, *pv = pvel;
float temp[3];
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
copy_v3_v3(temp, bp->vec);
copy_v3_v3(bp->vec, pv);
copy_v3_v3(pv, temp);
pv += 3;
copy_v3_v3(temp, bp->pos);
copy_v3_v3(bp->pos, pp);
copy_v3_v3(pp, temp);
pp += 3;
}
}
#endif
/* care for bodypoints taken out of the 'ordinary' solver step
* because they are screwed to goal by bolts
* they just need to move along with the goal in time
* we need to adjust them on sub frame timing in solver
* so now when frame is done .. put 'em to the position at the end of frame
*/
static void softbody_apply_goalsnap(Object *ob)
{
SoftBody *sb = ob->soft; /* is supposed to be there */
BodyPoint *bp;
int a;
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
if (_final_goal(ob, bp) >= SOFTGOALSNAP) {
copy_v3_v3(bp->prevpos, bp->pos);
copy_v3_v3(bp->pos, bp->origT);
}
}
}
static void apply_spring_memory(Object *ob)
{
SoftBody *sb = ob->soft;
BodySpring *bs;
BodyPoint *bp1, *bp2;
int a;
float b, l, r;
if (sb && sb->totspring) {
b = sb->plastic;
for (a = 0; a < sb->totspring; a++) {
bs = &sb->bspring[a];
bp1 = &sb->bpoint[bs->v1];
bp2 = &sb->bpoint[bs->v2];
l = len_v3v3(bp1->pos, bp2->pos);
r = bs->len / l;
if ((r > 1.05f) || (r < 0.95f)) {
bs->len = ((100.0f - b) * bs->len + b * l) / 100.0f;
}
}
}
}
/* expects full initialized softbody */
static void interpolate_exciter(Object *ob, int timescale, int time)
{
SoftBody *sb = ob->soft;
BodyPoint *bp;
float f;
int a;
f = (float)time / (float)timescale;
for (a = sb->totpoint, bp = sb->bpoint; a > 0; a--, bp++) {
bp->origT[0] = bp->origS[0] + f * (bp->origE[0] - bp->origS[0]);
bp->origT[1] = bp->origS[1] + f * (bp->origE[1] - bp->origS[1]);
bp->origT[2] = bp->origS[2] + f * (bp->origE[2] - bp->origS[2]);
if (_final_goal(ob, bp) >= SOFTGOALSNAP) {
bp->vec[0] = bp->origE[0] - bp->origS[0];
bp->vec[1] = bp->origE[1] - bp->origS[1];
bp->vec[2] = bp->origE[2] - bp->origS[2];
}
}
}
/* ************ convertors ********** */
/* for each object type we need;
* - xxxx_to_softbody(Object *ob) : a full (new) copy, creates SB geometry
*/
/* Resetting a Mesh SB object's springs */
/* Spring length are caculted from'raw' mesh vertices that are NOT altered by modifier stack. */
static void springs_from_mesh(Object *ob)
{
SoftBody *sb;
Mesh *me = ob->data;
BodyPoint *bp;
int a;
float scale = 1.0f;
sb = ob->soft;
if (me && sb) {
/* using bp->origS as a container for spring calculations here
* will be overwritten sbObjectStep() to receive
* actual modifier stack positions
*/
if (me->totvert) {
bp = ob->soft->bpoint;
for (a = 0; a < me->totvert; a++, bp++) {
copy_v3_v3(bp->origS, me->mvert[a].co);
mul_m4_v3(ob->obmat, bp->origS);
}
}
/* recalculate spring length for meshes here */
/* public version shrink to fit */
if (sb->springpreload != 0) {
scale = sb->springpreload / 100.0f;
}
for (a = 0; a < sb->totspring; a++) {
BodySpring *bs = &sb->bspring[a];
bs->len = scale * len_v3v3(sb->bpoint[bs->v1].origS, sb->bpoint[bs->v2].origS);
}
}
}
/* makes totally fresh start situation */
static void mesh_to_softbody(Scene *scene, Object *ob)
{
SoftBody *sb;
Mesh *me = ob->data;
MEdge *medge = me->medge;
BodyPoint *bp;
BodySpring *bs;
int a, totedge;
int defgroup_index, defgroup_index_mass, defgroup_index_spring;
if (ob->softflag & OB_SB_EDGES) {
totedge = me->totedge;
}
else {
totedge = 0;
}
/* renew ends with ob->soft with points and edges, also checks & makes ob->soft */
renew_softbody(scene, ob, me->totvert, totedge);
/* we always make body points */
sb = ob->soft;
bp = sb->bpoint;
defgroup_index = me->dvert ? (sb->vertgroup - 1) : -1;
defgroup_index_mass = me->dvert ? BKE_object_defgroup_name_index(ob, sb->namedVG_Mass) : -1;
defgroup_index_spring = me->dvert ? BKE_object_defgroup_name_index(ob, sb->namedVG_Spring_K) :
-1;
for (a = 0; a < me->totvert; a++, bp++) {
/* get scalar values needed *per vertex* from vertex group functions,
* so we can *paint* them nicely ..
* they are normalized [0.0..1.0] so may be we need amplitude for scale
* which can be done by caller but still .. i'd like it to go this way
*/
if (ob->softflag & OB_SB_GOAL) {
BLI_assert(bp->goal == sb->defgoal);
}
if ((ob->softflag & OB_SB_GOAL) && (defgroup_index != -1)) {
bp->goal *= BKE_defvert_find_weight(&me->dvert[a], defgroup_index);
}
/* to proof the concept
* this enables per vertex *mass painting*
*/
if (defgroup_index_mass != -1) {
bp->mass *= BKE_defvert_find_weight(&me->dvert[a], defgroup_index_mass);
}
if (defgroup_index_spring != -1) {
bp->springweight *= BKE_defvert_find_weight(&me->dvert[a], defgroup_index_spring);
}
}
/* but we only optionally add body edge springs */
if (ob->softflag & OB_SB_EDGES) {
if (medge) {
bs = sb->bspring;
for (a = me->totedge; a > 0; a--, medge++, bs++) {
bs->v1 = medge->v1;
bs->v2 = medge->v2;
bs->springtype = SB_EDGE;
}
/* insert *diagonal* springs in quads if desired */
if (ob->softflag & OB_SB_QUADS) {
add_mesh_quad_diag_springs(ob);
}
build_bps_springlist(ob); /* scan for springs attached to bodypoints ONCE */
/* insert *other second order* springs if desired */
if (sb->secondspring > 0.0000001f) {
/* exploits the first run of build_bps_springlist(ob); */
add_2nd_order_springs(ob, sb->secondspring);
/* yes we need to do it again. */
build_bps_springlist(ob);
}
springs_from_mesh(ob); /* write the 'rest'-length of the springs */
if (ob->softflag & OB_SB_SELF) {
calculate_collision_balls(ob);
}
}
}
}
static void mesh_faces_to_scratch(Object *ob)
{
SoftBody *sb = ob->soft;
const Mesh *me = ob->data;
MLoopTri *looptri, *lt;
BodyFace *bodyface;
int a;
/* alloc and copy faces*/
sb->scratch->totface = poly_to_tri_count(me->totpoly, me->totloop);
looptri = lt = MEM_mallocN(sizeof(*looptri) * sb->scratch->totface, __func__);
BKE_mesh_recalc_looptri(me->mloop, me->mpoly, me->mvert, me->totloop, me->totpoly, looptri);
bodyface = sb->scratch->bodyface = MEM_mallocN(sizeof(BodyFace) * sb->scratch->totface,
"SB_body_Faces");
for (a = 0; a < sb->scratch->totface; a++, lt++, bodyface++) {
bodyface->v1 = me->mloop[lt->tri[0]].v;
bodyface->v2 = me->mloop[lt->tri[1]].v;
bodyface->v3 = me->mloop[lt->tri[2]].v;
zero_v3(bodyface->ext_force);
bodyface->ext_force[0] = bodyface->ext_force[1] = bodyface->ext_force[2] = 0.0f;
bodyface->flag = 0;
}
MEM_freeN(looptri);
}
static void reference_to_scratch(Object *ob)
{
SoftBody *sb = ob->soft;
ReferenceVert *rp;
BodyPoint *bp;
float accu_pos[3] = {0.f, 0.f, 0.f};
float accu_mass = 0.f;
int a;
sb->scratch->Ref.ivert = MEM_mallocN(sizeof(ReferenceVert) * sb->totpoint, "SB_Reference");
bp = ob->soft->bpoint;
rp = sb->scratch->Ref.ivert;
for (a = 0; a < sb->totpoint; a++, rp++, bp++) {
copy_v3_v3(rp->pos, bp->pos);
add_v3_v3(accu_pos, bp->pos);
accu_mass += _final_mass(ob, bp);
}
mul_v3_fl(accu_pos, 1.0f / accu_mass);
copy_v3_v3(sb->scratch->Ref.com, accu_pos);
/* printf("reference_to_scratch\n"); */
}
/*
* helper function to get proper spring length
* when object is rescaled
*/
static float globallen(float *v1, float *v2, Object *ob)
{
float p1[3], p2[3];
copy_v3_v3(p1, v1);
mul_m4_v3(ob->obmat, p1);
copy_v3_v3(p2, v2);
mul_m4_v3(ob->obmat, p2);
return len_v3v3(p1, p2);
}
static void makelatticesprings(Lattice *lt, BodySpring *bs, int dostiff, Object *ob)
{
BPoint *bp = lt->def, *bpu;
int u, v, w, dv, dw, bpc = 0, bpuc;
dv = lt->pntsu;
dw = dv * lt->pntsv;
for (w = 0; w < lt->pntsw; w++) {
for (v = 0; v < lt->pntsv; v++) {
for (u = 0, bpuc = 0, bpu = NULL; u < lt->pntsu; u++, bp++, bpc++) {
if (w) {
bs->v1 = bpc;
bs->v2 = bpc - dw;
bs->springtype = SB_EDGE;
bs->len = globallen((bp - dw)->vec, bp->vec, ob);
bs++;
}
if (v) {
bs->v1 = bpc;
bs->v2 = bpc - dv;
bs->springtype = SB_EDGE;
bs->len = globallen((bp - dv)->vec, bp->vec, ob);
bs++;
}
if (u) {
bs->v1 = bpuc;
bs->v2 = bpc;
bs->springtype = SB_EDGE;
bs->len = globallen((bpu)->vec, bp->vec, ob);
bs++;
}
if (dostiff) {
if (w) {
if (v && u) {
bs->v1 = bpc;
bs->v2 = bpc - dw - dv - 1;
bs->springtype = SB_BEND;
bs->len = globallen((bp - dw - dv - 1)->vec, bp->vec, ob);
bs++;
}
if ((v < lt->pntsv - 1) && (u != 0)) {
bs->v1 = bpc;
bs->v2 = bpc - dw + dv - 1;
bs->springtype = SB_BEND;
bs->len = globallen((bp - dw + dv - 1)->vec, bp->vec, ob);
bs++;
}
}
if (w < lt->pntsw - 1) {
if (v && u) {
bs->v1 = bpc;
bs->v2 = bpc + dw - dv - 1;
bs->springtype = SB_BEND;
bs->len = globallen((bp + dw - dv - 1)->vec, bp->vec, ob);
bs++;
}
if ((v < lt->pntsv - 1) && (u != 0)) {
bs->v1 = bpc;
bs->v2 = bpc + dw + dv - 1;
bs->springtype = SB_BEND;
bs->len = globallen((bp + dw + dv - 1)->vec, bp->vec, ob);
bs++;
}
}
}
bpu = bp;
bpuc = bpc;
}
}
}
}
/* makes totally fresh start situation */
static void lattice_to_softbody(Scene *scene, Object *ob)
{
Lattice *lt = ob->data;
SoftBody *sb;
int totvert, totspring = 0, a;
BodyPoint *bp;
BPoint *bpnt = lt->def;
int defgroup_index, defgroup_index_mass, defgroup_index_spring;
totvert = lt->pntsu * lt->pntsv * lt->pntsw;
if (ob->softflag & OB_SB_EDGES) {
totspring = ((lt->pntsu - 1) * lt->pntsv + (lt->pntsv - 1) * lt->pntsu) * lt->pntsw +
lt->pntsu * lt->pntsv * (lt->pntsw - 1);
if (ob->softflag & OB_SB_QUADS) {
totspring += 4 * (lt->pntsu - 1) * (lt->pntsv - 1) * (lt->pntsw - 1);
}
}
/* renew ends with ob->soft with points and edges, also checks & makes ob->soft */
renew_softbody(scene, ob, totvert, totspring);
sb = ob->soft; /* can be created in renew_softbody() */
bp = sb->bpoint;
defgroup_index = lt->dvert ? (sb->vertgroup - 1) : -1;
defgroup_index_mass = lt->dvert ? BKE_object_defgroup_name_index(ob, sb->namedVG_Mass) : -1;
defgroup_index_spring = lt->dvert ? BKE_object_defgroup_name_index(ob, sb->namedVG_Spring_K) :
-1;
/* same code used as for mesh vertices */
for (a = 0; a < totvert; a++, bp++, bpnt++) {
if (ob->softflag & OB_SB_GOAL) {
BLI_assert(bp->goal == sb->defgoal);
}
if ((ob->softflag & OB_SB_GOAL) && (defgroup_index != -1)) {
bp->goal *= BKE_defvert_find_weight(<->dvert[a], defgroup_index);
}
else {
bp->goal *= bpnt->weight;
}
if (defgroup_index_mass != -1) {
bp->mass *= BKE_defvert_find_weight(<->dvert[a], defgroup_index_mass);
}
if (defgroup_index_spring != -1) {
bp->springweight *= BKE_defvert_find_weight(<->dvert[a], defgroup_index_spring);
}
}
/* create some helper edges to enable SB lattice to be useful at all */
if (ob->softflag & OB_SB_EDGES) {
makelatticesprings(lt, ob->soft->bspring, ob->softflag & OB_SB_QUADS, ob);
build_bps_springlist(ob); /* link bps to springs */
if (ob->softflag & OB_SB_SELF) {
calculate_collision_balls(ob);
}
}
}
/* makes totally fresh start situation */
static void curve_surf_to_softbody(Scene *scene, Object *ob)
{
Curve *cu = ob->data;
SoftBody *sb;
BodyPoint *bp;
BodySpring *bs;
Nurb *nu;
BezTriple *bezt;
BPoint *bpnt;
int a, curindex = 0;
int totvert, totspring = 0, setgoal = 0;
totvert = BKE_nurbList_verts_count(&cu->nurb);
if (ob->softflag & OB_SB_EDGES) {
if (ob->type == OB_CURVE) {
totspring = totvert - BLI_listbase_count(&cu->nurb);
}
}
/* renew ends with ob->soft with points and edges, also checks & makes ob->soft */
renew_softbody(scene, ob, totvert, totspring);
sb = ob->soft; /* can be created in renew_softbody() */
/* set vars now */
bp = sb->bpoint;
bs = sb->bspring;
/* weights from bpoints, same code used as for mesh vertices */
/* if ((ob->softflag & OB_SB_GOAL) && sb->vertgroup) 2.4x hack*/
/* new! take the weights from curve vertex anyhow */
if (ob->softflag & OB_SB_GOAL) {
setgoal = 1;
}
for (nu = cu->nurb.first; nu; nu = nu->next) {
if (nu->bezt) {
/* Bezier case; this is nicly said naive; who ever wrote this part,
* it was not me (JOW) :).
*
* a: never ever make tangent handles (sub) and or (ob)ject to collision.
* b: rather calculate them using some C2
* (C2= continuous in second derivate -> no jump in bending ) condition.
*
* Not too hard to do, but needs some more code to care for;
* some one may want look at it (JOW 2010/06/12). */
for (bezt = nu->bezt, a = 0; a < nu->pntsu; a++, bezt++, bp += 3, curindex += 3) {
if (setgoal) {
bp->goal *= bezt->weight;
/* all three triples */
(bp + 1)->goal = bp->goal;
(bp + 2)->goal = bp->goal;
/*do not collide handles */
(bp + 1)->loc_flag |= SBF_OUTOFCOLLISION;
(bp + 2)->loc_flag |= SBF_OUTOFCOLLISION;
}
if (totspring) {
if (a > 0) {
bs->v1 = curindex - 3;
bs->v2 = curindex;
bs->springtype = SB_HANDLE;
bs->len = globallen((bezt - 1)->vec[0], bezt->vec[0], ob);
bs++;
}
bs->v1 = curindex;
bs->v2 = curindex + 1;
bs->springtype = SB_HANDLE;
bs->len = globallen(bezt->vec[0], bezt->vec[1], ob);
bs++;
bs->v1 = curindex + 1;
bs->v2 = curindex + 2;
bs->springtype = SB_HANDLE;
bs->len = globallen(bezt->vec[1], bezt->vec[2], ob);
bs++;
}
}
}
else {
for (bpnt = nu->bp, a = 0; a < nu->pntsu * nu->pntsv; a++, bpnt++, bp++, curindex++) {
if (setgoal) {
bp->goal *= bpnt->weight;
}
if (totspring && a > 0) {
bs->v1 = curindex - 1;
bs->v2 = curindex;
bs->springtype = SB_EDGE;
bs->len = globallen((bpnt - 1)->vec, bpnt->vec, ob);
bs++;
}
}
}
}
if (totspring) {
build_bps_springlist(ob); /* link bps to springs */
if (ob->softflag & OB_SB_SELF) {
calculate_collision_balls(ob);
}
}
}
/* copies softbody result back in object */
static void softbody_to_object(Object *ob, float (*vertexCos)[3], int numVerts, int local)
{
SoftBody *sb = ob->soft;
if (sb) {
BodyPoint *bp = sb->bpoint;
int a;
if (sb->solverflags & SBSO_ESTIMATEIPO) {
SB_estimate_transform(ob, sb->lcom, sb->lrot, sb->lscale);
}
/* inverse matrix is not uptodate... */
invert_m4_m4(ob->imat, ob->obmat);
for (a = 0; a < numVerts; a++, bp++) {
copy_v3_v3(vertexCos[a], bp->pos);
if (local == 0) {
mul_m4_v3(ob->imat, vertexCos[a]); /* softbody is in global coords, baked optionally not */
}
}
}
}
/* +++ ************ maintaining scratch *************** */
static void sb_new_scratch(SoftBody *sb)
{
if (!sb) {
return;
}
sb->scratch = MEM_callocN(sizeof(SBScratch), "SBScratch");
sb->scratch->colliderhash = BLI_ghash_ptr_new("sb_new_scratch gh");
sb->scratch->bodyface = NULL;
sb->scratch->totface = 0;
sb->scratch->aabbmax[0] = sb->scratch->aabbmax[1] = sb->scratch->aabbmax[2] = 1.0e30f;
sb->scratch->aabbmin[0] = sb->scratch->aabbmin[1] = sb->scratch->aabbmin[2] = -1.0e30f;
sb->scratch->Ref.ivert = NULL;
}
/* --- ************ maintaining scratch *************** */
/* ************ Object level, exported functions *************** */
/* allocates and initializes general main data */
SoftBody *sbNew(Scene *scene)
{
SoftBody *sb;
sb = MEM_callocN(sizeof(SoftBody), "softbody");
sb->mediafrict = 0.5f;
sb->nodemass = 1.0f;
sb->grav = 9.8f;
sb->physics_speed = 1.0f;
sb->rklimit = 0.1f;
sb->goalspring = 0.5f;
sb->goalfrict = 0.0f;
sb->mingoal = 0.0f;
sb->maxgoal = 1.0f;
sb->defgoal = 0.7f;
sb->inspring = 0.5f;
sb->infrict = 0.5f;
/*todo backward file compat should copy inspring to inpush while reading old files*/
sb->inpush = 0.5f;
sb->interval = 10;
if (scene != NULL) {
sb->sfra = scene->r.sfra;
sb->efra = scene->r.efra;
}
sb->colball = 0.49f;
sb->balldamp = 0.50f;
sb->ballstiff = 1.0f;
sb->sbc_mode = 1;
sb->minloops = 10;
sb->maxloops = 300;
sb->choke = 3;
sb_new_scratch(sb);
/*todo backward file compat should set sb->shearstiff = 1.0f while reading old files*/
sb->shearstiff = 1.0f;
sb->solverflags |= SBSO_OLDERR;
sb->shared = MEM_callocN(sizeof(*sb->shared), "SoftBody_Shared");
sb->shared->pointcache = BKE_ptcache_add(&sb->shared->ptcaches);
if (!sb->effector_weights) {
sb->effector_weights = BKE_effector_add_weights(NULL);
}
sb->last_frame = MINFRAME - 1;
return sb;
}
/* frees all */
void sbFree(Object *ob)
{
SoftBody *sb = ob->soft;
if (sb == NULL) {
return;
}
free_softbody_intern(sb);
if ((ob->id.tag & LIB_TAG_NO_MAIN) == 0) {
/* Only free shared data on non-CoW copies */
BKE_ptcache_free_list(&sb->shared->ptcaches);
sb->shared->pointcache = NULL;
MEM_freeN(sb->shared);
}
if (sb->effector_weights) {
MEM_freeN(sb->effector_weights);
}
MEM_freeN(sb);
ob->soft = NULL;
}
void sbFreeSimulation(SoftBody *sb)
{
free_softbody_intern(sb);
}
/* makes totally fresh start situation */
void sbObjectToSoftbody(Object *ob)
{
// ob->softflag |= OB_SB_REDO;
free_softbody_intern(ob->soft);
}
static int object_has_edges(Object *ob)
{
if (ob->type == OB_MESH) {
return ((Mesh *)ob->data)->totedge;
}
if (ob->type == OB_LATTICE) {
return 1;
}
return 0;
}
/* SB global visible functions */
void sbSetInterruptCallBack(int (*f)(void))
{
SB_localInterruptCallBack = f;
}
static void softbody_update_positions(Object *ob,
SoftBody *sb,
float (*vertexCos)[3],
int numVerts)
{
BodyPoint *bp;
int a;
if (!sb || !sb->bpoint) {
return;
}
for (a = 0, bp = sb->bpoint; a < numVerts; a++, bp++) {
/* store where goals are now */
copy_v3_v3(bp->origS, bp->origE);
/* copy the position of the goals at desired end time */
copy_v3_v3(bp->origE, vertexCos[a]);
/* vertexCos came from local world, go global */
mul_m4_v3(ob->obmat, bp->origE);
/* just to be save give bp->origT a defined value
* will be calculated in interpolate_exciter() */
copy_v3_v3(bp->origT, bp->origE);
}
}
/* void SB_estimate_transform */
/* input Object *ob out (says any object that can do SB like mesh, lattice, curve )
* output float lloc[3], float lrot[3][3], float lscale[3][3]
* that is:
* a precise position vector denoting the motion of the center of mass
* give a rotation/scale matrix using averaging method, that's why estimate and not calculate
* see: this is kind of reverse engineering: having to states of a point cloud and recover what
* happened our advantage here we know the identity of the vertex there are others methods giving
* other results. lloc, lrot, lscale are allowed to be NULL, just in case you don't need it.
* should be pretty useful for pythoneers :)
* not! velocity .. 2nd order stuff
* vcloud_estimate_transform_v3 see
*/
void SB_estimate_transform(Object *ob, float lloc[3], float lrot[3][3], float lscale[3][3])
{
BodyPoint *bp;
ReferenceVert *rp;
SoftBody *sb = NULL;
float(*opos)[3];
float(*rpos)[3];
float com[3], rcom[3];
int a;
if (!ob || !ob->soft) {
return; /* why did we get here ? */
}
sb = ob->soft;
if (!sb || !sb->bpoint) {
return;
}
opos = MEM_callocN(sizeof(float[3]) * sb->totpoint, "SB_OPOS");
rpos = MEM_callocN(sizeof(float[3]) * sb->totpoint, "SB_RPOS");
/* might filter vertex selection with a vertex group */
for (a = 0, bp = sb->bpoint, rp = sb->scratch->Ref.ivert; a < sb->totpoint; a++, bp++, rp++) {
copy_v3_v3(rpos[a], rp->pos);
copy_v3_v3(opos[a], bp->pos);
}
vcloud_estimate_transform_v3(sb->totpoint, opos, NULL, rpos, NULL, com, rcom, lrot, lscale);
// sub_v3_v3(com, rcom);
if (lloc) {
copy_v3_v3(lloc, com);
}
copy_v3_v3(sb->lcom, com);
if (lscale) {
copy_m3_m3(sb->lscale, lscale);
}
if (lrot) {
copy_m3_m3(sb->lrot, lrot);
}
MEM_freeN(opos);
MEM_freeN(rpos);
}
static void softbody_reset(Object *ob, SoftBody *sb, float (*vertexCos)[3], int numVerts)
{
BodyPoint *bp;
int a;
for (a = 0, bp = sb->bpoint; a < numVerts; a++, bp++) {
copy_v3_v3(bp->pos, vertexCos[a]);
mul_m4_v3(ob->obmat, bp->pos); /* yep, sofbody is global coords*/
copy_v3_v3(bp->origS, bp->pos);
copy_v3_v3(bp->origE, bp->pos);
copy_v3_v3(bp->origT, bp->pos);
bp->vec[0] = bp->vec[1] = bp->vec[2] = 0.0f;
/* the bp->prev*'s are for rolling back from a canceled try to propagate in time
* adaptive step size algo in a nutshell:
* 1. set scheduled time step to new dtime
* 2. try to advance the scheduled time step, being optimistic execute it
* 3. check for success
* 3.a we 're fine continue, may be we can increase scheduled time again ?? if so, do so!
* 3.b we did exceed error limit --> roll back, shorten the scheduled time and try again at 2.
* 4. check if we did reach dtime
* 4.a nope we need to do some more at 2.
* 4.b yup we're done
*/
copy_v3_v3(bp->prevpos, bp->pos);
copy_v3_v3(bp->prevvec, bp->vec);
copy_v3_v3(bp->prevdx, bp->vec);
copy_v3_v3(bp->prevdv, bp->vec);
}
/* make a nice clean scratch struct */
free_scratch(sb); /* clear if any */
sb_new_scratch(sb); /* make a new */
sb->scratch->needstobuildcollider = 1;
zero_v3(sb->lcom);
unit_m3(sb->lrot);
unit_m3(sb->lscale);
/* copy some info to scratch */
/* we only need that if we want to reconstruct IPO */
if (1) {
reference_to_scratch(ob);
SB_estimate_transform(ob, NULL, NULL, NULL);
SB_estimate_transform(ob, NULL, NULL, NULL);
}
switch (ob->type) {
case OB_MESH:
if (ob->softflag & OB_SB_FACECOLL) {
mesh_faces_to_scratch(ob);
}
break;
case OB_LATTICE:
break;
case OB_CURVE:
case OB_SURF:
break;
default:
break;
}
}
static void softbody_step(
struct Depsgraph *depsgraph, Scene *scene, Object *ob, SoftBody *sb, float dtime)
{
/* the simulator */
float forcetime;
double sct, sst;
sst = PIL_check_seconds_timer();
/* Integration back in time is possible in theory, but pretty useless here.
* So we refuse to do so. Since we do not know anything about 'outside' changes
* especially colliders we refuse to go more than 10 frames.
*/
if (dtime < 0 || dtime > 10.5f) {
return;
}
ccd_update_deflector_hash(depsgraph, sb->collision_group, ob, sb->scratch->colliderhash);
if (sb->scratch->needstobuildcollider) {
ccd_build_deflector_hash(depsgraph, sb->collision_group, ob, sb->scratch->colliderhash);
sb->scratch->needstobuildcollider = 0;
}
if (sb->solver_ID < 2) {
/* special case of 2nd order Runge-Kutta type AKA Heun */
int mid_flags = 0;
float err = 0;
/* Set defaults guess we shall do one frame */
float forcetimemax = 1.0f;
/* Set defaults guess 1/100 is tight enough */
float forcetimemin = 0.01f;
/* How far did we get without violating error condition. */
float timedone = 0.0;
/* Loops = counter for emergency brake we don't want to lock up the system if physics fail. */
int loops = 0;
SoftHeunTol = sb->rklimit; /* humm .. this should be calculated from sb parameters and sizes */
/* adjust loop limits */
if (sb->minloops > 0) {
forcetimemax = dtime / sb->minloops;
}
if (sb->maxloops > 0) {
forcetimemin = dtime / sb->maxloops;
}
if (sb->solver_ID > 0) {
mid_flags |= MID_PRESERVE;
}
forcetime = forcetimemax; /* hope for integrating in one step */
while ((fabsf(timedone) < fabsf(dtime)) && (loops < 2000)) {
/* set goals in time */
interpolate_exciter(ob, 200, (int)(200.0f * (timedone / dtime)));
sb->scratch->flag &= ~SBF_DOFUZZY;
/* do predictive euler step */
softbody_calc_forces(depsgraph, scene, ob, forcetime, timedone / dtime);
softbody_apply_forces(ob, forcetime, 1, NULL, mid_flags);
/* crop new slope values to do averaged slope step */
softbody_calc_forces(depsgraph, scene, ob, forcetime, timedone / dtime);
softbody_apply_forces(ob, forcetime, 2, &err, mid_flags);
softbody_apply_goalsnap(ob);
if (err > SoftHeunTol) { /* error needs to be scaled to some quantity */
if (forcetime > forcetimemin) {
forcetime = max_ff(forcetime / 2.0f, forcetimemin);
softbody_restore_prev_step(ob);
// printf("down, ");
}
else {
timedone += forcetime;
}
}
else {
float newtime = forcetime * 1.1f; /* hope for 1.1 times better conditions in next step */
if (sb->scratch->flag & SBF_DOFUZZY) {
///* stay with this stepsize unless err really small */
// if (err > SoftHeunTol/(2.0f*sb->fuzzyness)) {
newtime = forcetime;
//}
}
else {
if (err > SoftHeunTol / 2.0f) { /* stay with this stepsize unless err really small */
newtime = forcetime;
}
}
timedone += forcetime;
newtime = min_ff(forcetimemax, max_ff(newtime, forcetimemin));
// if (newtime > forcetime) printf("up, ");
if (forcetime > 0.0f) {
forcetime = min_ff(dtime - timedone, newtime);
}
else {
forcetime = max_ff(dtime - timedone, newtime);
}
}
loops++;
if (sb->solverflags & SBSO_MONITOR) {
sct = PIL_check_seconds_timer();
if (sct - sst > 0.5) {
printf("%3.0f%% \r", 100.0f * timedone / dtime);
}
}
/* ask for user break */
if (SB_localInterruptCallBack && SB_localInterruptCallBack()) {
break;
}
}
/* move snapped to final position */
interpolate_exciter(ob, 2, 2);
softbody_apply_goalsnap(ob);
// if (G.debug & G_DEBUG) {
if (sb->solverflags & SBSO_MONITOR) {
if (loops > HEUNWARNLIMIT) { /* monitor high loop counts */
printf("\r needed %d steps/frame", loops);
}
}
}
else if (sb->solver_ID == 2) {
/* do semi "fake" implicit euler */
// removed
} /*SOLVER SELECT*/
else if (sb->solver_ID == 4) {
/* do semi "fake" implicit euler */
} /*SOLVER SELECT*/
else if (sb->solver_ID == 3) {
/* do "stupid" semi "fake" implicit euler */
// removed
} /*SOLVER SELECT*/
else {
CLOG_ERROR(&LOG, "softbody no valid solver ID!");
} /*SOLVER SELECT*/
if (sb->plastic) {
apply_spring_memory(ob);
}
if (sb->solverflags & SBSO_MONITOR) {
sct = PIL_check_seconds_timer();
if ((sct - sst > 0.5) || (G.debug & G_DEBUG)) {
printf(" solver time %f sec %s\n", sct - sst, ob->id.name);
}
}
}
static void sbStoreLastFrame(struct Depsgraph *depsgraph, Object *object, float framenr)
{
if (!DEG_is_active(depsgraph)) {
return;
}
Object *object_orig = DEG_get_original_object(object);
object->soft->last_frame = framenr;
object_orig->soft->last_frame = framenr;
}
/* simulates one step. framenr is in frames */
void sbObjectStep(struct Depsgraph *depsgraph,
Scene *scene,
Object *ob,
float cfra,
float (*vertexCos)[3],
int numVerts)
{
SoftBody *sb = ob->soft;
PointCache *cache;
PTCacheID pid;
float dtime, timescale;
int framedelta, framenr, startframe, endframe;
int cache_result;
cache = sb->shared->pointcache;
framenr = (int)cfra;
framedelta = framenr - cache->simframe;
BKE_ptcache_id_from_softbody(&pid, ob, sb);
BKE_ptcache_id_time(&pid, scene, framenr, &startframe, &endframe, ×cale);
/* check for changes in mesh, should only happen in case the mesh
* structure changes during an animation */
if (sb->bpoint && numVerts != sb->totpoint) {
BKE_ptcache_invalidate(cache);
return;
}
/* clamp frame ranges */
if (framenr < startframe) {
BKE_ptcache_invalidate(cache);
return;
}
if (framenr > endframe) {
framenr = endframe;
}
/* verify if we need to create the softbody data */
if (sb->bpoint == NULL ||
((ob->softflag & OB_SB_EDGES) && !ob->soft->bspring && object_has_edges(ob))) {
switch (ob->type) {
case OB_MESH:
mesh_to_softbody(scene, ob);
break;
case OB_LATTICE:
lattice_to_softbody(scene, ob);
break;
case OB_CURVE:
case OB_SURF:
curve_surf_to_softbody(scene, ob);
break;
default:
renew_softbody(scene, ob, numVerts, 0);
break;
}
softbody_update_positions(ob, sb, vertexCos, numVerts);
softbody_reset(ob, sb, vertexCos, numVerts);
}
/* still no points? go away */
if (sb->totpoint == 0) {
return;
}
if (framenr == startframe) {
BKE_ptcache_id_reset(scene, &pid, PTCACHE_RESET_OUTDATED);
/* first frame, no simulation to do, just set the positions */
softbody_update_positions(ob, sb, vertexCos, numVerts);
BKE_ptcache_validate(cache, framenr);
cache->flag &= ~PTCACHE_REDO_NEEDED;
sbStoreLastFrame(depsgraph, ob, framenr);
return;
}
/* try to read from cache */
bool can_write_cache = DEG_is_active(depsgraph);
bool can_simulate = (framenr == sb->last_frame + 1) && !(cache->flag & PTCACHE_BAKED) &&
can_write_cache;
cache_result = BKE_ptcache_read(&pid, (float)framenr + scene->r.subframe, can_simulate);
if (cache_result == PTCACHE_READ_EXACT || cache_result == PTCACHE_READ_INTERPOLATED ||
(!can_simulate && cache_result == PTCACHE_READ_OLD)) {
softbody_to_object(ob, vertexCos, numVerts, sb->local);
BKE_ptcache_validate(cache, framenr);
if (cache_result == PTCACHE_READ_INTERPOLATED && cache->flag & PTCACHE_REDO_NEEDED &&
can_write_cache) {
BKE_ptcache_write(&pid, framenr);
}
sbStoreLastFrame(depsgraph, ob, framenr);
return;
}
if (cache_result == PTCACHE_READ_OLD) {
/* pass */
}
else if (/*ob->id.lib || */
/* "library linking & pointcaches" has to be solved properly at some point */
(cache->flag & PTCACHE_BAKED)) {
/* if baked and nothing in cache, do nothing */
if (can_write_cache) {
BKE_ptcache_invalidate(cache);
}
return;
}
if (!can_simulate) {
return;
}
/* if on second frame, write cache for first frame */
if (cache->simframe == startframe &&
(cache->flag & PTCACHE_OUTDATED || cache->last_exact == 0)) {
BKE_ptcache_write(&pid, startframe);
}
softbody_update_positions(ob, sb, vertexCos, numVerts);
/* checking time: */
dtime = framedelta * timescale;
/* do simulation */
softbody_step(depsgraph, scene, ob, sb, dtime);
softbody_to_object(ob, vertexCos, numVerts, 0);
BKE_ptcache_validate(cache, framenr);
BKE_ptcache_write(&pid, framenr);
sbStoreLastFrame(depsgraph, ob, framenr);
}