1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434
|
#include <lib/high-precision/Constants.hpp>
#include <pkg/common/Facet.hpp>
#include <pkg/common/Sphere.hpp>
#include <pkg/common/Wall.hpp>
#include <pkg/dem/L3Geom.hpp>
#ifdef YADE_OPENGL
#include <lib/opengl/GLUtils.hpp>
#include <lib/opengl/OpenGLWrapper.hpp>
#endif
namespace yade { // Cannot have #include directive inside.
using math::max;
using math::min; // using inside .cpp file is ok.
YADE_PLUGIN((L3Geom)(L6Geom)(Ig2_Sphere_Sphere_L3Geom)(Ig2_Wall_Sphere_L3Geom)(Ig2_Facet_Sphere_L3Geom)(Ig2_Sphere_Sphere_L6Geom)(
Law2_L3Geom_FrictPhys_ElPerfPl)(Law2_L6Geom_FrictPhys_Linear)
#ifdef YADE_OPENGL
(Gl1_L3Geom)(Gl1_L6Geom)
#endif
);
L3Geom::~L3Geom() { }
void L3Geom::applyLocalForceTorque(const Vector3r& localF, const Vector3r& localT, const Interaction* I, Scene* scene, NormShearPhys* nsp) const
{
Vector3r globF = trsf.transpose() * localF; // trsf is orthonormal, therefore inverse==transpose
Vector3r x1c(normal * (refR1 + .5 * u[0])), x2c(-normal * (refR2 + .5 * u[0]));
if (nsp) {
nsp->normalForce = normal * globF.dot(normal);
nsp->shearForce = globF - nsp->normalForce;
}
Vector3r globT = Vector3r::Zero();
if (localT != Vector3r::Zero()) { globT = trsf.transpose() * localT; }
// apply force and torque
scene->forces.addForce(I->getId1(), globF);
scene->forces.addTorque(I->getId1(), x1c.cross(globF) + globT);
scene->forces.addForce(I->getId2(), -globF);
scene->forces.addTorque(I->getId2(), x2c.cross(-globF) - globT);
}
void L3Geom::applyLocalForce(const Vector3r& localF, const Interaction* I, Scene* scene, NormShearPhys* nsp) const
{
applyLocalForceTorque(localF, Vector3r::Zero(), I, scene, nsp);
}
L6Geom::~L6Geom() { }
bool Ig2_Sphere_Sphere_L3Geom::go(
const shared_ptr<Shape>& s1,
const shared_ptr<Shape>& s2,
const State& state1,
const State& state2,
const Vector3r& shift2,
const bool& force,
const shared_ptr<Interaction>& I)
{
return genericGo(/*is6Dof*/ false, s1, s2, state1, state2, shift2, force, I);
};
bool Ig2_Sphere_Sphere_L6Geom::go(
const shared_ptr<Shape>& s1,
const shared_ptr<Shape>& s2,
const State& state1,
const State& state2,
const Vector3r& shift2,
const bool& force,
const shared_ptr<Interaction>& I)
{
return genericGo(/*is6Dof*/ true, s1, s2, state1, state2, shift2, force, I);
};
CREATE_LOGGER(Ig2_Sphere_Sphere_L3Geom);
bool Ig2_Sphere_Sphere_L3Geom::genericGo(
bool is6Dof,
const shared_ptr<Shape>& s1,
const shared_ptr<Shape>& s2,
const State& state1,
const State& state2,
const Vector3r& shift2,
const bool& force,
const shared_ptr<Interaction>& I)
{
// temporary hack only, to not have elastic potential energy in rigid packings with overlapping spheres
//if(state1.blockedDOFs==State::DOF_ALL && state2.blockedDOFs==State::DOF_ALL) return false;
const Real& r1 = s1->cast<Sphere>().radius;
const Real& r2 = s2->cast<Sphere>().radius;
Vector3r relPos = state2.pos + shift2 - state1.pos;
Real unDistSq = relPos.squaredNorm() - pow(math::abs(distFactor) * (r1 + r2), 2);
if (unDistSq > 0 && !I->isReal() && !force) return false;
// contact exists, go ahead
Real dist = relPos.norm();
Real uN = dist - (r1 + r2);
Vector3r normal = relPos / dist;
Vector3r contPt = state1.pos + (r1 + 0.5 * uN) * normal;
handleSpheresLikeContact(I, state1, state2, shift2, is6Dof, normal, contPt, uN, r1, r2);
return true;
};
/*
Generic function to compute L3Geom (with colinear points), used for {sphere,facet,wall}+sphere contacts now
*/
void Ig2_Sphere_Sphere_L3Geom::handleSpheresLikeContact(
const shared_ptr<Interaction>& I,
const State& state1,
const State& state2,
const Vector3r& shift2,
bool is6Dof,
const Vector3r& normal,
const Vector3r& contPt,
Real uN,
Real r1,
Real r2)
{
// create geometry
if (!I->geom) {
if (is6Dof) I->geom = shared_ptr<L6Geom>(new L6Geom);
else
I->geom = shared_ptr<L3Geom>(new L3Geom);
L3Geom& g(I->geom->cast<L3Geom>());
g.contactPoint = contPt;
g.refR1 = r1;
g.refR2 = r2;
g.normal = normal;
// g.trsf.setFromTwoVectors(Vector3r::UnitX(),g.normal); // quaternion just from the X-axis; does not seem to work for some reason?!
const Vector3r& locX(g.normal);
// initial local y-axis orientation, in the xz or xy plane, depending on which component is larger to avoid singularities
Vector3r locY = normal.cross(math::abs(normal[1]) < math::abs(normal[2]) ? Vector3r::UnitY() : Vector3r::UnitZ());
locY -= locX * locY.dot(locX);
locY.normalize();
Vector3r locZ = normal.cross(locY);
g.trsf.row(0) = locX;
g.trsf.row(1) = locY;
g.trsf.row(2) = locZ;
g.u = Vector3r(uN, 0, 0); // zero shear displacement
if (distFactor < 0) g.u0[0] = uN;
// L6Geom::phi is initialized to Vector3r::Zero() automatically
//cerr<<"Init trsf=\n"<<g.trsf<<endl<<"locX="<<locX<<", locY="<<locY<<", locZ="<<locZ<<endl;
return;
}
// update geometry
/* motion of the conctact consists in rigid motion (normRotVec, normTwistVec) and mutual motion (relShearDu);
they are used to update trsf and u
*/
L3Geom& g(I->geom->cast<L3Geom>());
const Vector3r& currNormal(normal);
const Vector3r& prevNormal(g.normal);
// normal rotation vector, between last steps
Vector3r normRotVec = prevNormal.cross(currNormal);
// contrary to what ScGeom::precompute does now (r2486), we take average normal, i.e. .5*(prevNormal+currNormal),
// so that all terms in the equation are in the previous mid-step
// the re-normalization might not be necessary for very small increments, but better do it
Vector3r avgNormal = (approxMask & APPROX_NO_MID_NORMAL) ? prevNormal : .5 * (prevNormal + currNormal);
if (!(approxMask & APPROX_NO_RENORM_MID_NORMAL) && !(approxMask & APPROX_NO_MID_NORMAL))
avgNormal.normalize(); // normalize only if used and if requested via approxMask
// twist vector of the normal from the last step
Vector3r normTwistVec = avgNormal * scene->dt * .5 * avgNormal.dot(state1.angVel + state2.angVel);
// compute relative velocity
// noRatch: take radius or current distance as the branch vector; see discussion in ScGeom::precompute (avoidGranularRatcheting)
Vector3r c1x = ((noRatch && !(r1 > 0)) ? (r1 * normal).eval() : (contPt - state1.pos).eval()); // used only for sphere-sphere
Vector3r c2x = (noRatch ? (-r2 * normal).eval() : (contPt - state2.pos + shift2).eval());
//Vector3r state2velCorrected=state2.vel+(scene->isPeriodic?scene->cell->intrShiftVel(I->cellDist):Vector3r::Zero()); // velocity of the second particle, corrected with meanfield velocity if necessary
//cerr<<"correction "<<(scene->isPeriodic?scene->cell->intrShiftVel(I->cellDist):Vector3r::Zero())<<endl;
Vector3r relShearVel = (state2.vel + state2.angVel.cross(c2x)) - (state1.vel + state1.angVel.cross(c1x));
// account for relative velocity of particles in different cell periods
if (scene->isPeriodic) relShearVel += scene->cell->intrShiftVel(I->cellDist);
relShearVel -= avgNormal.dot(relShearVel) * avgNormal;
Vector3r relShearDu = relShearVel * scene->dt;
/* Update of quantities in global coords consists in adding 3 increments we have computed; in global coords (a is any vector)
1. +relShearVel*scene->dt; // mutual motion of the contact
2. -a.cross(normRotVec); // rigid rotation perpendicular to the normal
3. -a.cross(normTwistVec); // rigid rotation parallel to the normal
*/
// compute current transformation, by updating previous axes
// the X axis can be prescribed directly (copy of normal)
// the mutual motion on the contact does not change transformation
const Matrix3r prevTrsf(g.trsf); // could be reference perhaps, but we need it to compute midTrsf (if applicable)
Matrix3r currTrsf;
currTrsf.row(0) = currNormal;
for (int i = 1; i < 3; i++) {
currTrsf.row(i) = prevTrsf.row(i) - prevTrsf.row(i).cross(normRotVec) - prevTrsf.row(i).cross(normTwistVec);
}
if ((scene->iter % trsfRenorm) == 0 && trsfRenorm > 0) {
currTrsf.row(0).normalize();
currTrsf.row(1) -= currTrsf.row(0) * currTrsf.row(1).dot(currTrsf.row(0)); // take away y projected on x, to stabilize numerically
currTrsf.row(1).normalize();
currTrsf.row(2) = currTrsf.row(0).cross(currTrsf.row(1));
currTrsf.row(2).normalize();
#ifdef YADE_DEBUG
if (math::abs(currTrsf.determinant() - 1) > .05) {
LOG_ERROR("##" << I->getId1() << "+" << I->getId2() << ", |trsf|=" << currTrsf.determinant());
g.trsf = currTrsf;
throw runtime_error("Transformation matrix far from orthonormal.");
}
#endif
}
/* Previous local trsf u'⁻ must be updated to current u'⁰. We have transformation T⁻ and T⁰,
δ(a) denotes increment of a as defined above. Two possibilities:
1. convert to global, update, convert back: T⁰(T⁻*(u'⁻)+δ(T⁻*(u'⁻))). Quite heavy.
2. update u'⁻ straight, using relShearVel in local coords; since relShearVel is computed
at (t-Δt/2), we would have to find intermediary transformation (same axis, half angle;
the same as slerp at t=.5 between the two).
This could be perhaps simplified by using T⁰ or T⁻ since they will not differ much,
but it would have to be verified somehow.
*/
// if requested via approxMask, just use prevTrsf
Quaternionr midTrsf = (approxMask & APPROX_NO_MID_TRSF) ? Quaternionr(prevTrsf) : Quaternionr(prevTrsf).slerp(.5, Quaternionr(currTrsf));
// updates of geom here
// midTrsf*relShearVel should have the 0-th component (approximately) zero -- to be checked
g.u += midTrsf * relShearDu;
//cerr<<"midTrsf=\n"<<midTrsf<<",relShearDu="<<relShearDu<<", transformed "<<midTrsf*relShearDu<<endl;
g.u[0] = uN; // this does not have to be computed incrementally
g.trsf = currTrsf;
// GenericSpheresContact
g.refR1 = r1;
g.refR2 = r2;
g.normal = currNormal;
g.contactPoint = contPt;
if (is6Dof) {
// update phi, from the difference of angular velocities
// the difference is transformed to local coord using the midTrsf transformation
// perhaps not consistent when spheres have different radii (depends how bending moment is computed)
I->geom->cast<L6Geom>().phi += midTrsf * (scene->dt * (state2.angVel - state1.angVel));
}
};
bool Ig2_Wall_Sphere_L3Geom::go(
const shared_ptr<Shape>& s1,
const shared_ptr<Shape>& s2,
const State& state1,
const State& state2,
const Vector3r& shift2,
const bool& force,
const shared_ptr<Interaction>& I)
{
if (scene->isPeriodic) throw std::logic_error("Ig2_Wall_Sphere_L3Geom does not handle periodic boundary conditions.");
const Real& radius = s2->cast<Sphere>().radius;
const int& ax(s1->cast<Wall>().axis);
const int& sense(s1->cast<Wall>().sense);
Real dist = state2.pos[ax] + shift2[ax] - state1.pos[ax]; // signed "distance" between centers
if (!I->isReal() && math::abs(dist) > radius && !force) { return false; } // wall and sphere too far from each other
// contact point is sphere center projected onto the wall
Vector3r contPt = state2.pos + shift2;
contPt[ax] = state1.pos[ax];
Vector3r normal = Vector3r::Zero();
// wall interacting from both sides: normal depends on sphere's position
assert(sense == -1 || sense == 0 || sense == 1);
if (sense == 0) normal[ax] = dist > 0 ? 1. : -1.;
else
normal[ax] = (sense == 1 ? 1. : -1);
Real uN = normal[ax] * dist - radius; // takes in account sense, radius and distance
// check that the normal did not change orientation (would be abrup here)
if (I->geom && I->geom->cast<L3Geom>().normal != normal) {
std::ostringstream oss;
oss << "Ig2_Wall_Sphere_L3Geom: normal changed from (" << I->geom->cast<L3Geom>().normal << " to " << normal << " in Wall+Sphere ##"
<< I->getId1() << "+" << I->getId2() << " (with Wall.sense=0, a particle might cross the Wall plane, if Δt is too high)";
throw std::logic_error(oss.str().c_str());
}
handleSpheresLikeContact(I, state1, state2, shift2, /*is6Dof*/ false, normal, contPt, uN, /*r1*/ 0, /*r2*/ radius);
return true;
};
bool Ig2_Facet_Sphere_L3Geom::go(
const shared_ptr<Shape>& s1,
const shared_ptr<Shape>& s2,
const State& state1,
const State& state2,
const Vector3r& shift2,
const bool& force,
const shared_ptr<Interaction>& I)
{
const Facet& facet(s1->cast<Facet>());
Real radius = s2->cast<Sphere>().radius;
// begin facet-local coordinates
Vector3r cogLine = state1.ori.conjugate() * (state2.pos + shift2 - state1.pos); // connect centers of gravity
Vector3r normal = facet.normal; // trial contact normal
Real planeDist = normal.dot(cogLine);
if (math::abs(planeDist) > radius && !I->isReal() && !force) return false; // sphere too far
if (planeDist < 0) {
normal *= -1;
planeDist *= -1;
}
Vector3r planarPt = cogLine - planeDist * normal; // project sphere center to the facet plane
Vector3r contactPt; // facet's point closes to the sphere
Real normDotPt[3]; // edge outer normals dot products
for (int i = 0; i < 3; i++)
normDotPt[i] = facet.ne[i].dot(planarPt - facet.vertices[i]);
short w = (normDotPt[0] > 0 ? 1 : 0) + (normDotPt[1] > 0 ? 2 : 0)
+ (normDotPt[2] > 0 ? 4 : 0); // bitmask whether the closest point is outside (1,2,4 for respective edges)
switch (w) {
case 0: contactPt = planarPt; break; // ---: inside triangle
case 1: contactPt = getClosestSegmentPt(planarPt, facet.vertices[0], facet.vertices[1]); break; // +-- (n1)
case 2: contactPt = getClosestSegmentPt(planarPt, facet.vertices[1], facet.vertices[2]); break; // -+- (n2)
case 4: contactPt = getClosestSegmentPt(planarPt, facet.vertices[2], facet.vertices[0]); break; // --+ (n3)
case 3: contactPt = facet.vertices[1]; break; // ++- (v1)
case 5: contactPt = facet.vertices[0]; break; // +-+ (v0)
case 6: contactPt = facet.vertices[2]; break; // -++ (v2)
case 7:
throw logic_error("Ig2_Facet_Sphere_L3Geom: Impossible sphere-facet intersection (all points are outside the edges). (please report "
"bug)"); // +++ (impossible)
default: throw logic_error("Ig2_Facet_Sphere_L3Geom: Nonsense intersection value. (please report bug)");
}
normal = cogLine - contactPt; // normal is now the contact normal, still in local coords
if (!I->isReal() && normal.squaredNorm() > radius * radius && !force) { return false; } // fast test before sqrt
Real dist = normal.norm();
normal /= dist; // normal is unit vector now
// end facet-local coordinates
normal = state1.ori * normal; // normal is in global coords now
handleSpheresLikeContact(
I, state1, state2, shift2, /*is6Dof*/ false, normal, /*contact pt*/ state2.pos + shift2 - normal * dist, dist - radius, 0, radius);
return true;
}
bool Law2_L3Geom_FrictPhys_ElPerfPl::go(shared_ptr<IGeom>& ig, shared_ptr<IPhys>& ip, Interaction* I)
{
L3Geom* geom = static_cast<L3Geom*>(ig.get());
FrictPhys* phys = static_cast<FrictPhys*>(ip.get());
// compute force
Vector3r& localF(geom->F);
localF = geom->relU().cwiseProduct(Vector3r(phys->kn, phys->ks, phys->ks));
// break if necessary
if (localF[0] > 0 && !noBreak) return false;
if (!noSlip) {
// plastic slip, if necessary; non-zero elastic limit only for compression
Real maxFs = -min((Real)0., localF[0] * phys->tangensOfFrictionAngle);
Eigen::Map<Vector2r> Fs(&localF[1]);
//cerr<<"u="<<geom->relU()<<", maxFs="<<maxFs<<", Fn="<<localF[0]<<", |Fs|="<<Fs.norm()<<", Fs="<<Fs<<endl;
if (Fs.squaredNorm() > maxFs * maxFs) {
Real ratio = sqrt(maxFs * maxFs / Fs.squaredNorm());
Vector3r u0slip = (1 - ratio) * Vector3r(/*no slip in the normal sense*/ 0, geom->relU()[1], geom->relU()[2]);
geom->u0 += u0slip; // increment plastic displacement
Fs *= ratio; // decrement shear force value;
if (scene->trackEnergy) {
Real dissip = Fs.norm() * u0slip.norm();
if (dissip > 0) scene->energy->add(dissip, "plastDissip", plastDissipIx, /*reset*/ false);
}
}
}
if (scene->trackEnergy) {
scene->energy->add(
0.5 * (pow(geom->relU()[0], 2) * phys->kn + (pow(geom->relU()[1], 2) + pow(geom->relU()[2], 2)) * phys->ks),
"elastPotential",
elastPotentialIx,
/*reset at every timestep*/ true);
}
// apply force: this converts the force to global space, updates NormShearPhys::{normal,shear}Force, applies to particles
geom->applyLocalForce(localF, I, scene, phys);
return true;
}
bool Law2_L6Geom_FrictPhys_Linear::go(shared_ptr<IGeom>& ig, shared_ptr<IPhys>& ip, Interaction* I)
{
L6Geom& geom = ig->cast<L6Geom>();
FrictPhys& phys = ip->cast<FrictPhys>();
// simple linear relationships
Vector3r localF = geom.relU().cwiseProduct(Vector3r(phys.kn, phys.ks, phys.ks));
Vector3r localT = charLen * (geom.relPhi().cwiseProduct(Vector3r(phys.kn, phys.ks, phys.ks)));
geom.applyLocalForceTorque(localF, localT, I, scene, static_cast<NormShearPhys*>(ip.get()));
return true;
}
#ifdef YADE_OPENGL
bool Gl1_L3Geom::axesLabels;
Real Gl1_L3Geom::axesWd;
Real Gl1_L3Geom::axesScale;
Real Gl1_L3Geom::uPhiWd;
Real Gl1_L3Geom::uScale;
Real Gl1_L6Geom::phiScale;
void Gl1_L3Geom::go(const shared_ptr<IGeom>& ig, const shared_ptr<Interaction>&, const shared_ptr<Body>&, const shared_ptr<Body>&, bool) { draw(ig); }
void Gl1_L6Geom::go(const shared_ptr<IGeom>& ig, const shared_ptr<Interaction>&, const shared_ptr<Body>&, const shared_ptr<Body>&, bool)
{
draw(ig, true, phiScale);
}
void Gl1_L3Geom::draw(const shared_ptr<IGeom>& ig, bool isL6Geom, const Real& phiScale)
{
const L3Geom& g(ig->cast<L3Geom>());
glTranslatev(g.contactPoint);
glMultMatrix(Eigen::Transform<Real, 3, Eigen::Affine>(g.trsf.transpose()).data());
Real rMin = g.refR1 <= 0 ? g.refR2 : (g.refR2 <= 0 ? g.refR1 : min(g.refR1, g.refR2));
if (axesWd > 0) {
glLineWidth(axesWd);
for (int i = 0; i < 3; i++) {
Vector3r pt = Vector3r::Zero();
pt[i] = .5 * rMin * axesScale;
Vector3r color = .3 * Vector3r::Ones();
color[i] = 1;
GLUtils::GLDrawLine(Vector3r::Zero(), pt, color);
if (axesLabels) GLUtils::GLDrawText(string(i == 0 ? "x" : (i == 1 ? "y" : "z")), pt, color);
}
}
if (uPhiWd > 0) {
glLineWidth(uPhiWd);
if (uScale != 0) GLUtils::GLDrawLine(Vector3r::Zero(), uScale * g.relU(), Vector3r(0, 1, .5));
if (isL6Geom && phiScale > 0)
GLUtils::GLDrawLine(Vector3r::Zero(), ig->cast<L6Geom>().relPhi() / Mathr::PI * rMin * phiScale, Vector3r(.8, 0, 1));
}
glLineWidth(1.);
};
#endif
} // namespace yade
|