← Back to team overview

yade-dev team mailing list archive

[Branch ~yade-dev/yade/trunk] Rev 2360: - Cylinder : fix segment orientation, use correct contact point, and clean code.

 

------------------------------------------------------------
revno: 2360
committer: bchareyre <bchareyre@dt-rv020>
branch nick: trunk
timestamp: Tue 2010-07-13 21:14:55 +0200
message:
  - Cylinder : fix segment orientation, use correct contact point, and clean code.
  - ScGeom : last step before removing old code, some functors adapted (others are using even older duplicated code apparently)
  - Small sphinx fixes.
modified:
  doc/references.bib
  doc/sphinx/formulation.rst
  doc/yade-articles.bib
  pkg/common/DataClass/Shape/Cylinder.cpp
  pkg/common/DataClass/Shape/Cylinder.hpp
  pkg/dem/DataClass/InteractionGeometry/ScGeom.cpp
  pkg/dem/DataClass/InteractionGeometry/ScGeom.hpp
  pkg/dem/Engine/Functor/Ig2_Box_Sphere_ScGeom.cpp
  pkg/dem/Engine/Functor/Ig2_Facet_Sphere_ScGeom.cpp
  pkg/dem/Engine/Functor/Ig2_Sphere_Sphere_ScGeom.cpp
  pkg/dem/Engine/GlobalEngine/CohesiveFrictionalContactLaw.cpp
  pkg/dem/Engine/GlobalEngine/CohesiveFrictionalPM.cpp
  pkg/dem/Engine/GlobalEngine/ElasticContactLaw.cpp
  scripts/test/chained-cylinder-spring.py


--
lp:yade
https://code.launchpad.net/~yade-dev/yade/trunk

Your team Yade developers is subscribed to branch lp:yade.
To unsubscribe from this branch go to https://code.launchpad.net/~yade-dev/yade/trunk/+edit-subscription
=== modified file 'doc/references.bib'
--- doc/references.bib	2010-07-12 14:26:27 +0000
+++ doc/references.bib	2010-07-13 19:14:55 +0000
@@ -73,18 +73,31 @@
 	volume = {131},
 	number = {7},
 	pages = {689-698},
-	url = {https://yade-dem.org/wiki/File:Chareyre%26Villard2005_licensed.pdf},
+	url = {http://link.aip.org/link/?QEM/131/689/1},
 	doi = {10.1061/(ASCE)0733-9399(2005)131:7(689)}
 }
 
 @phdthesis{Scholtes2009,
-	author = {Luc Scholtès},
-	title = {Modélisation micromécanique des milieux granulaires partiellement saturés},
+	author = { Luc Scholtès },
+	title = { Modélisation micromécanique des milieux granulaires partiellement saturés },
 	year = {2009},
 	school = {Institut National Polytechnique de Grenoble},
 	url = {http://tel.archives-ouvertes.fr/tel-00363961/en/}
 }
 
+@article{Scholtes2009b,
+	author = "L. Scholtès and B. Chareyre and F. Nicot and F. Darve",
+	title = "Micromechanics of granular materials with capillary effects",
+	journal = "International Journal of Engineering Science",
+	volume = "47",
+	number = "1",
+	pages = "64--75",
+	year = "2009",
+	issn = "0020-7225",
+	doi = "10.1016/j.ijengsci.2008.07.002",
+	url = "http://www.sciencedirect.com/science/article/B6V32-4TDJGDJ-1/2/9878efa5e573197aff6ee14d5abb58a2";,
+}
+
 @proceedings{DeghmReport2006,
 	title={Annual Report 2006},
 	year={2006},
@@ -94,6 +107,18 @@
 	url={ http://geo.hmg.inpg.fr/frederic/Discrete_Element_Group_FVD.html }
 }
 
+@article{Jerier2009,
+	journal={Granular Matter},
+	title={A geometric algorithm based on tetrahedral meshes to generate a dense polydisperse sphere packing},
+	volume={11},
+	number={1},
+	pages={43--52},
+	year={2009},
+	author={Jean-François Jerier and  Didier Imbault and Fréderic-Victor Donzé and Pierre Doremus},
+	doi={10.1007/s10035-008-0116-0},
+	url={http://www.springerlink.com/content/w0x307g110421035}
+}
+
 @article{Pournin2001,
   title = {Molecular-dynamics force models for better control of energy dissipation in numerical simulations of dense granular media},
   author = {Pournin, L.  and Liebling, Th. M. and Mocellin, A. },

=== modified file 'doc/sphinx/formulation.rst'
--- doc/sphinx/formulation.rst	2010-07-08 20:01:59 +0000
+++ doc/sphinx/formulation.rst	2010-07-13 19:14:55 +0000
@@ -717,7 +717,7 @@
 				
 DEM simulations
 """"""""""""""""
-In DEM simulations, per-particle stiffness $\vec{K}_{ij}$ is determined from the stiffnesses of contacts in which it participates [Chareyre2005]_. Suppose each contact has normal stiffness $K_{Nk}$, shear stiffness $K_{Tk}=\xi K_{Nk}$ and is oriented by normal $\vec{n}_{k}$. A translational stiffness matrix $\vec{K}_ij$ can be defined as the sum of contributions of all contacts in which it participates (indices $k$), as
+In DEM simulations, per-particle stiffness $\vec{K}_{ij}$ is determined from the stiffnesses of contacts in which it participates [Chareyre2005]_. Suppose each contact has normal stiffness $K_{Nk}$, shear stiffness $K_{Tk}=\xi K_{Nk}$ and is oriented by normal $\vec{n}_{k}$. A translational stiffness matrix $\vec{K}_{ij}$ can be defined as the sum of contributions of all contacts in which it participates (indices $k$), as
 
 .. math::
 	:label: eq-dtcr-particle-stiffness

=== modified file 'doc/yade-articles.bib'
--- doc/yade-articles.bib	2010-07-05 08:16:58 +0000
+++ doc/yade-articles.bib	2010-07-13 19:14:55 +0000
@@ -149,7 +149,8 @@
 	journal = { J. Engrg. Mech.},
 	volume = {131},
 	number = {7},
-	year = {2005}
+	year = {2005},
+	url={https://yade-dem.org/wiki/File:Chareyre%26Villard2005_licensed.pdf}
 }
 
 @article{Hentz2004a,

=== modified file 'pkg/common/DataClass/Shape/Cylinder.cpp'
--- pkg/common/DataClass/Shape/Cylinder.cpp	2010-07-12 19:41:57 +0000
+++ pkg/common/DataClass/Shape/Cylinder.cpp	2010-07-13 19:14:55 +0000
@@ -16,7 +16,7 @@
 	#ifdef YADE_OPENGL
 		(Gl1_Cylinder)(Gl1_ChainedCylinder)
 	#endif
-	(Bo1_Cylinder_Aabb)/*(Bo1_ChainedCylinder_Aabb)*/
+	(Bo1_Cylinder_Aabb)(Bo1_ChainedCylinder_Aabb)
 );
 
 vector<vector<int> > ChainedState::chains;
@@ -59,10 +59,10 @@
 		scm->radius1=s1->radius;
 		scm->radius2=s2->radius;
 #ifdef IGCACHE
-		if (scene->isPeriodic) {
-			Vector3r shiftVel = scene->cell->velGrad*scene->cell->Hsize*c->cellDist.cast<Real>();
- 			scm->precompute(state1,state2,scene->dt,shiftVel,true);}
- 		else scm->precompute(state1,state2,scene->dt,true);
+// 		if (scene->isPeriodic) {
+// 			Vector3r shiftVel = scene->cell->velGrad*scene->cell->Hsize*c->cellDist.cast<Real>();
+//  			scm->precompute(state1,state2,scene->dt,shiftVel,true);}
+ 		/*else */scm->precompute(state1,state2,scene,c,true);
 #endif
 		return true;
 	}
@@ -97,65 +97,48 @@
 	const ChainedState *pChain1, *pChain2;
 	pChain1=YADE_CAST<const ChainedState*>(&state1);
 	pChain2=YADE_CAST<const ChainedState*>(&state2);
-	const ChainedState& chain1 = *pChain1;
-	const ChainedState& chain2 = *pChain2;
 	if (!pChain1 || !pChain2) {
 		cerr <<"cast failed8567"<<endl;
 	}
-	bool revert = (pChain2->rank-chain1.rank == -1);
-
+	const bool revert = (pChain2->rank-pChain1->rank == -1);
+	const ChainedState& bchain1 = revert? *pChain2 : *YADE_CAST<const ChainedState*>(&state1);
+	const ChainedState& bchain2 = revert? *pChain1 : *pChain2;
+	if (bchain2.rank-bchain1.rank != 1) {/*cerr<<"Mutual contacts in same chain between not adjacent elements, not handled*/ return false;}
 	if (pChain2->chainNumber!=pChain1->chainNumber) {cerr<<"PROBLEM0124"<<endl; return false;}
-	else if (chain2.rank-chain1.rank != 1 && pChain2->rank-pChain1->rank != -1) {/*cerr<<"PROBLEM0123"<<endl;*/ return false;}
-	const ChainedState* pChain = revert ? pChain2 : pChain1;
-
-	ChainedCylinder *s1=static_cast<ChainedCylinder*>(cm1.get()), *s2=static_cast<ChainedCylinder*>(cm2.get());
-	ChainedCylinder *s = revert ? s2 : s1;
+	
+	ChainedCylinder *bs1=static_cast<ChainedCylinder*>(revert? cm2.get():cm1.get());
+	
 	shared_ptr<ScGeom> scm;
 	bool isNew = !c->interactionGeometry;
 	if(!isNew) scm=YADE_PTR_CAST<ScGeom>(c->interactionGeometry);
 	else { scm=shared_ptr<ScGeom>(new ScGeom()); c->interactionGeometry=scm; }
-// 	Real length=abs((pChain->ori*Vector3r::UnitZ()).dot(chain2.pos-chain1.pos));
-	Real length=(chain2.pos-chain1.pos).norm();
-//  	Vector3r segment =pChain->ori*Vector3r::UnitZ()*length;
-	Vector3r segment =chain2.pos-chain1.pos;
-	if (revert) segment = -segment;
-	s->chainedOrientation.setFromTwoVectors(Vector3r::UnitZ(),pChain->ori.conjugate()*segment);
-// 	s->chainedOrientation=pChain->ori.conjugate()*s->chainedOrientation;
-	
-	if (revert) segment = -segment; 
-	if(isNew) {scm->normal=scm->prevNormal=segment/length;s->initLength=length;}
-	else {scm->prevNormal=scm->normal; scm->normal=segment/length;}
-
-	if (!revert) {
-		scm->radius1=s->initLength;
-		scm->radius2=0;}
-	else {
-		scm->radius1=0;
-		scm->radius2=s->initLength;}
-	//length only used for display
-	s->length=length;
-	scm->penetrationDepth=s->initLength-length;
-	
-// 	scm->contactPoint=pChain->pos+segment;//generates instabilites (TODO)
-	scm->contactPoint=pChain->pos+pChain->ori*Vector3r::UnitZ()*length;
+	Real length=(bchain2.pos-bchain1.pos).norm();
+	Vector3r segt =pChain2->pos-pChain1->pos;
+	if(isNew) {scm->normal=scm->prevNormal=segt/length;bs1->initLength=length;}
+	else {scm->prevNormal=scm->normal; scm->normal=segt/length;}
+	scm->radius1=revert ? 0:bs1->initLength;
+	scm->radius2=revert ? bs1->initLength:0;
+	scm->penetrationDepth=bs1->initLength-length;
+	scm->contactPoint=bchain2.pos;
+	//bs1->segment used for fast BBs and projections + display
+	if (revert) segt = -segt;
+	bs1->segment=segt;
+#ifdef YADE_OPENGL 
+	//bs1->length and s1->chainedOrientation used for display only, 
+	bs1->length=length;
+	bs1->chainedOrientation.setFromTwoVectors(Vector3r::UnitZ(),bchain1.ori.conjugate()*segt);
+#endif
 
 #ifdef IGCACHE
-	if (scene->isPeriodic) {
-		Vector3r shiftVel = scene->cell->velGrad*scene->cell->Hsize*c->cellDist.cast<Real>();
-		scm->precompute(state1,state2,scene->dt,shiftVel,true);}
-	else {
-		scm->precompute(state1,state2,scene->dt,true);
-	}
+	scm->precompute(state1,state2,scene,c,true);
 #else
 	cerr<<"this is not supposed to work without #define IGCACHE"<<endl;
 #endif
 	//Set values that will be considered in Ip2 functor, geometry (precomputed) is really defined with values above
-	scm->radius1=s->initLength*0.5;
-	scm->radius2=s->initLength*0.5;
+	scm->radius1 = scm->radius2 = bs1->initLength*0.5;
 	return true;
 }
 
-
 bool Ig2_ChainedCylinder_ChainedCylinder_ScGeom::goReverse(	const shared_ptr<Shape>& cm1,
 								const shared_ptr<Shape>& cm2,
 								const State& state1,
@@ -176,12 +159,6 @@
 int  Gl1_Cylinder::glutStacks;
 int Gl1_Cylinder::glCylinderList=-1;
 
-// bool Gl1_ChainedCylinder::wire;
-// bool Gl1_ChainedCylinder::glutNormalize;
-// int  Gl1_ChainedCylinder::glutSlices;
-// int  Gl1_ChainedCylinder::glutStacks;
-// int Gl1_ChainedCylinder::glCylinderList=-1;
-
 void Gl1_Cylinder::out( Quaternionr q )
 {
 	AngleAxisr aa(q);
@@ -194,7 +171,7 @@
 	Real length=(static_cast<Cylinder*>(cm.get()))->length;
 	//glMaterialv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, Vector3f(cm->color[0],cm->color[1],cm->color[2]));
 	glColor3v(cm->color);
-	if(glutNormalize)	glPushAttrib(GL_NORMALIZE); // as per http://lists.apple.com/archives/Mac-opengl/2002/Jul/msg00085.html
+	if(glutNormalize) glPushAttrib(GL_NORMALIZE);
 // 	glPushMatrix();
 	Quaternionr shift = (static_cast<ChainedCylinder*>(cm.get()))->chainedOrientation;
 	if (wire || wire2) drawCylinder(true, r,length,shift);
@@ -204,20 +181,17 @@
 	return;
 }
 
-void Gl1_ChainedCylinder::go(const shared_ptr<Shape>& cm, const shared_ptr<State>& ,bool wire2, const GLViewInfo&)
+void Gl1_ChainedCylinder::go(const shared_ptr<Shape>& cm, const shared_ptr<State>& state,bool wire2, const GLViewInfo&)
 {
 	Real r=(static_cast<ChainedCylinder*>(cm.get()))->radius;
 	Real length=(static_cast<ChainedCylinder*>(cm.get()))->length;
-	Quaternionr shift = (static_cast<ChainedCylinder*>(cm.get()))->chainedOrientation;
-	//glMaterialv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, Vector3f(cm->color[0],cm->color[1],cm->color[2]));
+	Quaternionr shift;// = (static_cast<ChainedCylinder*>(cm.get()))->chainedOrientation;
+	shift.setFromTwoVectors(Vector3r::UnitZ(),state->ori.conjugate()*(static_cast<ChainedCylinder*>(cm.get()))->segment);
 	glColor3v(cm->color);
-	if(glutNormalize) glPushAttrib(GL_NORMALIZE); // as per http://lists.apple.com/archives/Mac-opengl/2002/Jul/msg00085.html
-// 	glPushMatrix();
- 	//out(shift);
+	if(glutNormalize) glPushAttrib(GL_NORMALIZE);
 	if (wire || wire2) drawCylinder(true, r,length,shift);
 	else drawCylinder(false, r,length,shift);
 	if(glutNormalize) glPopAttrib();
-// 	glPopMatrix();
 	return;
 }
 
@@ -316,15 +290,14 @@
 // 	aabb->min = scene->cell->unshearPt(se3.position)-minkSize;
 // 	aabb->max = scene->cell->unshearPt(se3.position)+minkSize;
 }
-/*
+
+
 void Bo1_ChainedCylinder_Aabb::go(const shared_ptr<Shape>& cm, shared_ptr<Bound>& bv, const Se3r& se3, const Body* b){
 	Cylinder* cylinder = static_cast<Cylinder*>(cm.get());
 	Aabb* aabb = static_cast<Aabb*>(bv.get());
 	if(!scene->isPeriodic){
-		const Vector3r& O = se3.position-se3.positioncylinder->segment*0.5;
-		//In this case, segment will be updated by Ig2_ChainedCylinder_ChainedCylinder_ScGeom, no need to rotate it
-		Vector3r O2 = se3.position+cylinder->segment*0.5;
-// 		cerr << O<<" "<<O2<<endl;
+		const Vector3r& O = se3.position;
+		Vector3r O2 = se3.position+cylinder->segment;
 		aabb->min=aabb->max=O;
 		for (int k=0;k<3;k++){
 			aabb->min[k]=min(aabb->min[k],min(O[k],O2[k])-cylinder->radius);
@@ -332,8 +305,5 @@
 		}
 		return;
 	}
-}*/
-
-
-
+}
 

=== modified file 'pkg/common/DataClass/Shape/Cylinder.hpp'
--- pkg/common/DataClass/Shape/Cylinder.hpp	2010-07-12 19:41:57 +0000
+++ pkg/common/DataClass/Shape/Cylinder.hpp	2010-07-13 19:14:55 +0000
@@ -4,6 +4,7 @@
 #include<yade/core/Body.hpp>
 #include<yade/pkg-dem/ScGeom.hpp>
 #include<yade/pkg-common/InteractionGeometryFunctor.hpp>
+#include<yade/pkg-common/Sphere.hpp>
 #include<yade/core/Scene.hpp>
 #ifdef YADE_OPENGL
 	#include<yade/pkg-common/GLDrawFunctors.hpp>
@@ -11,17 +12,18 @@
 #include<yade/pkg-common/BoundFunctor.hpp>
 
 
-class Cylinder: public Shape{
+class Cylinder: public Sphere{
 	public:
-		Cylinder(Real _radius, Real _length): radius(_radius), length(_length), segment(Vector3r(0,0,1)*_length){ createIndex(); }
+		Cylinder(Real _radius, Real _length): length(_length) { /*segment=Vector3r(0,0,1)*_length;*/ radius=_radius; createIndex(); }
 		virtual ~Cylinder ();
-	YADE_CLASS_BASE_DOC_ATTRS_CTOR(Cylinder,Shape,"Geometry of a cylinder, as Minkowski sum of line and sphere.",
-		((Real,radius,NaN,"Radius [m]"))
+	YADE_CLASS_BASE_DOC_ATTRS_CTOR(Cylinder,Sphere,"Geometry of a cylinder, as Minkowski sum of line and sphere.",
+// 		((Real,radius,NaN,"Radius [m]"))
 		((Real,length,NaN,"Length [m]"))
 		((Vector3r,segment,Vector3r::Zero(),"Length vector")),
-		createIndex(); /*ctor*/
+		createIndex();
+		/*ctor*/
 	);
-	REGISTER_CLASS_INDEX(Cylinder,Shape);
+	REGISTER_CLASS_INDEX(Cylinder,Sphere);
 };
 
 class ChainedCylinder: public Cylinder{
@@ -142,7 +144,7 @@
 //!This doesn't work : the 1D dispatcher will pick Gl1_Cylinder to display ChainedCylinders, workaround : add shift to cylinders (should be a variable of chained cylinders only).
 class Gl1_ChainedCylinder : public Gl1_Cylinder{
 	public:
-		virtual void go(const shared_ptr<Shape>&, const shared_ptr<State>&,bool,const GLViewInfo&);
+		virtual void go(const shared_ptr<Shape>&, const shared_ptr<State>& state, bool,const GLViewInfo&);
 	YADE_CLASS_BASE_DOC(Gl1_ChainedCylinder,Gl1_Cylinder,"Renders :yref:`ChainedCylinder` object including a shift for compensating flexion."
 	);
 	RENDERS(ChainedCylinder);
@@ -180,6 +182,18 @@
 	);
 };
 
+class Bo1_ChainedCylinder_Aabb : public BoundFunctor
+{
+	public :
+		void go(const shared_ptr<Shape>& cm, shared_ptr<Bound>& bv, const Se3r&, const Body*);
+	FUNCTOR2D(ChainedCylinder,Aabb);
+	YADE_CLASS_BASE_DOC_ATTRS(Bo1_ChainedCylinder_Aabb,BoundFunctor,"Functor creating :yref:`Aabb` from :yref:`ChainedCylinder`.",
+		((Real,aabbEnlargeFactor,((void)"deactivated",-1),"Relative enlargement of the bounding box; deactivated if negative.\n\n.. note::\n\tThis attribute is used to create distant interaction, but is only meaningful with an :yref:`InteractionGeometryFunctor` which will not simply discard such interactions: :yref:`Ig2_Cylinder_Cylinder_Dem3DofGeom::distFactor` / :yref:`Ig2_Cylinder_Cylinder_ScGeom::interactionDetectionFactor` should have the same value as :yref:`aabbEnlargeFactor<Bo1_Cylinder_Aabb::aabbEnlargeFactor>`."))
+	);
+};
+
+
+
 // Keep this : Cylinders and ChainedCylinders will have different centers maybe.
 // class Bo1_ChainedCylinder_Aabb : public Bo1_Cylinder_Aabb
 // {
@@ -195,7 +209,7 @@
 
 
 REGISTER_SERIALIZABLE(Bo1_Cylinder_Aabb);
-// REGISTER_SERIALIZABLE(Bo1_ChainedCylinder_Aabb);
+REGISTER_SERIALIZABLE(Bo1_ChainedCylinder_Aabb);
 #ifdef YADE_OPENGL
 REGISTER_SERIALIZABLE(Gl1_Cylinder);
 REGISTER_SERIALIZABLE(Gl1_ChainedCylinder);

=== modified file 'pkg/dem/DataClass/InteractionGeometry/ScGeom.cpp'
--- pkg/dem/DataClass/InteractionGeometry/ScGeom.cpp	2010-07-12 08:50:35 +0000
+++ pkg/dem/DataClass/InteractionGeometry/ScGeom.cpp	2010-07-13 19:14:55 +0000
@@ -5,12 +5,15 @@
 
 #include "ScGeom.hpp"
 #include<yade/core/Omega.hpp>
+#include<yade/core/Scene.hpp>
+
+
 YADE_PLUGIN((ScGeom));
 
 // At least one virtual method must be in the .cpp file (!!!)
 ScGeom::~ScGeom(){};
 
-Vector3r& ScGeom::rotate(Vector3r& shearForce){
+Vector3r& ScGeom::rotate(Vector3r& shearForce) const {
 	// approximated rotations
 	shearForce -= shearForce.cross(orthonormal_axis);
 	shearForce -= shearForce.cross(twist_axis);
@@ -19,15 +22,10 @@
 	return shearForce;
 }
 
-void ScGeom::precompute(const State& rbp1, const State& rbp2, const Real& dt,bool avoidGranularRatcheting){
-	//Pass null shift to the periodic version
-	precompute(rbp1,rbp2,dt,Vector3r::Zero(),avoidGranularRatcheting);
-}
-
-void ScGeom::precompute(const State& rbp1, const State& rbp2, const Real& dt, const Vector3r& shiftVel, bool avoidGranularRatcheting){
-//Precompute data needed for rotating tangent vectors attached to the interaction
+//!Precompute data needed for rotating tangent vectors attached to the interaction
+void ScGeom::precompute(const State& rbp1, const State& rbp2, const Scene* scene, const shared_ptr<Interaction>& c, bool avoidGranularRatcheting){
 	orthonormal_axis = prevNormal.cross(normal);
-	Real angle = dt*0.5*normal.dot(rbp1.angVel + rbp2.angVel);
+	Real angle = scene->dt*0.5*normal.dot(rbp1.angVel + rbp2.angVel);
 	twist_axis = angle*normal;
 	//The previous normal could be updated here after being used, them remove it from Ig2's? 
  	//prevNormal=normal;
@@ -60,76 +58,29 @@
 		c2x = (x - rbp2.pos);
 	}
 	Vector3r relativeVelocity = (rbp2.vel+rbp2.angVel.cross(c2x)) - (rbp1.vel+rbp1.angVel.cross(c1x));
-	relativeVelocity+=shiftVel;
+	if (scene->isPeriodic) relativeVelocity += scene->cell->velGrad*scene->cell->Hsize*c->cellDist.cast<Real>();
 	//keep the shear part only
 	relativeVelocity = relativeVelocity-normal.dot(relativeVelocity)*normal;
-	shearIncrement = relativeVelocity*dt;
-}
-
-
-
-Vector3r ScGeom::updateShear(const State* rbp1, const State* rbp2, Real dt, bool avoidGranularRatcheting){
-
-	Vector3r axis;
-	Real angle;
-
-	Vector3r shearIncrement(Vector3r::Zero());
-
-	// approximated rotations
-		axis = prevNormal.cross(normal); 
-		shearIncrement -= shear.cross(axis);
-		angle = dt*0.5*normal.dot(rbp1->angVel + rbp2->angVel);
-		axis = angle*normal;
-		shearIncrement -= (shear+shearIncrement).cross(axis);
-		
-	// exact rotations (not adapted to shear/shearIncrement!)
-	#if 0
-		Quaternionr q;
-		axis					= prevNormal.Cross(normal);
-		angle					= acos(normal.Dot(prevNormal));
-		q.FromAngleAxis(angle,axis);
-		shearForce        = shearForce*q;
-		angle             = dt*0.5*normal.dot(rbp1->angVel+rbp2->angVel);
-		axis					= normal;
-		q.FromAngleAxis(angle,axis);
-		shearForce        = q*shearForce;
-	#endif
-
-	Vector3r& x = contactPoint;
-	Vector3r c1x, c2x;
-
-	if(avoidGranularRatcheting){
-		// FIXME: For sphere-facet contact this will give an erroneous value of relative velocity...
-		c1x =   radius1*normal; 
-		c2x =  -radius2*normal;
-	}
-	else {
-		// FIXME: It is correct for sphere-sphere and sphere-facet contact
-		c1x = (x - rbp1->pos);
-		c2x = (x - rbp2->pos);
-	}
-
-	Vector3r relativeVelocity = (rbp2->vel+rbp2->angVel.cross(c2x)) - (rbp1->vel+rbp1->angVel.cross(c1x));
-	Vector3r shearVelocity = relativeVelocity-normal.dot(relativeVelocity)*normal;
-	Vector3r shearDisplacement = shearVelocity*dt;
-	shearIncrement -= shearDisplacement;
-
-	shear+=shearIncrement;
-	return shearIncrement;
-}
-
-//Removing this function will need to adapt all laws
+	shearInc = relativeVelocity*scene->dt;
+}
+
+
+//Do we need a function for such simple operation? BTW, the original was BAD duplicated code. ;-)
+Vector3r ScGeom::updateShear(const State* rbp1, const State* rbp2, Real dt, bool avoidGranularRatcheting){rotate(shear); shear -= shearInc;}
+
+#ifndef IGCACHE
 Vector3r ScGeom::rotateAndGetShear(Vector3r& shearForce, const Vector3r& prevNormal, const State* rbp1, const State* rbp2, Real dt, bool avoidGranularRatcheting){
 	//Just pass null shift to the periodic version
 	return rotateAndGetShear(shearForce,prevNormal,rbp1,rbp2,dt,Vector3r::Zero(),avoidGranularRatcheting);
 }
+#endif
 
 //Removing this function will need to adapt all laws
-Vector3r ScGeom::rotateAndGetShear(Vector3r& shearForce, const Vector3r& prevNormal, const State* rbp1, const State* rbp2, Real dt, const Vector3r& shiftVel, bool avoidGranularRatcheting){
-// #ifdef IGCACHE
-// 	rotate(shearForce);
-// 	return shearIncrement;
-// #else
+const Vector3r& ScGeom::rotateAndGetShear(Vector3r& shearForce, const Vector3r& prevNormal, const State* rbp1, const State* rbp2, Real dt, const Vector3r& shiftVel, bool avoidGranularRatcheting){
+#ifdef IGCACHE
+ 	rotate(shearForce);
+ 	return shearInc;
+ #else
 	rotate(shearForce,prevNormal,rbp1,rbp2,dt);
 	Vector3r& x = contactPoint;
 	Vector3r c1x, c2x;
@@ -150,23 +101,23 @@
 	relativeVelocity = relativeVelocity-normal.dot(relativeVelocity)*normal;
 	Vector3r shearDisplacement = relativeVelocity*dt;
 	return shearDisplacement;
-// #endif //IGCACHE
+#endif //IGCACHE
 }
 
 Vector3r& ScGeom::rotate(Vector3r& shearForce, const Vector3r& prevNormal, const State* rbp1, const State* rbp2, Real dt){
-// #ifdef IGCACHE
-// 	return rotate(shearForce);
-// #else
+#ifdef IGCACHE
+ 	return rotate(shearForce);
+#else
 	Vector3r axis;
 	Real angle;
 	// approximated rotations
-		axis = prevNormal.cross(normal); 
+		axis = prevNormal.cross(normal);
 		shearForce -= shearForce.cross(axis);
 		angle = dt*0.5*normal.dot(rbp1->angVel + rbp2->angVel);
 		axis = angle*normal;
 		shearForce -= shearForce.cross(axis);
 	// exact rotations
-	
+
 // 		Quaternionr q;
 // 		axis					= prevNormal.Cross(normal);
 // 		angle					= acos(normal.Dot(prevNormal));
@@ -178,7 +129,7 @@
 // 		shearForce        = q*shearForce;
 
 	return shearForce;
-// #endif
+#endif
 }
 
 Vector3r ScGeom::getIncidentVel(const State* rbp1, const State* rbp2, Real dt, const Vector3r& shiftVel, bool avoidGranularRatcheting){

=== modified file 'pkg/dem/DataClass/InteractionGeometry/ScGeom.hpp'
--- pkg/dem/DataClass/InteractionGeometry/ScGeom.hpp	2010-07-12 08:50:35 +0000
+++ pkg/dem/DataClass/InteractionGeometry/ScGeom.hpp	2010-07-13 19:14:55 +0000
@@ -3,12 +3,12 @@
 // © 2006 Bruno Chareyre <bruno.chareyre@xxxxxxxxxxx>
 
 #pragma once
-
+#include<yade/core/Interaction.hpp>
 #include<yade/core/InteractionGeometry.hpp>
 #include<yade/core/State.hpp>
 #include<yade/lib-base/Math.hpp>
 #include<yade/pkg-dem/DemXDofGeom.hpp>
-/*! Class representing geometry of two spheres in contact.
+/*! Class representing geometry of two bodies in contact.
  *
  * The code under SCG_SHEAR is experimental and is used only if ElasticContactLaw::useShear is explicitly true
  */
@@ -17,48 +17,50 @@
 #define IGCACHE
 
 class ScGeom: public GenericSpheresContact {
+	private:
+		//cached values
+		Vector3r twist_axis;//rotation vector arounf normal
+		Vector3r orthonormal_axis;//rotation vector in contact plane
+		Vector3r shearInc;//shear disp. increment
 	public:
 		// inherited from GenericSpheresContact: Vector3r& normal; 
 		Real &radius1, &radius2;
-		//cached values
-		Vector3r twist_axis;//rotation vector arounf normal
-		Vector3r orthonormal_axis;//rotation vector in contact plane
 		Real penetrationDepth;
-		Vector3r shearIncrement;
+		virtual ~ScGeom();
 		
+		//!precompute values of shear increment and interaction rotation data
+		void precompute(const State& rbp1, const State& rbp2, const Scene* scene, const shared_ptr<Interaction>& c, bool avoidGranularRatcheting=true);
+		//!  Periodic variant. Needs the velocity shift across periods for periodic BCs (else it is safe to pass Vector3r::Zero()). Typically obtained as scene->cell->velGrad*scene->cell->Hsize*cellDist.
+// 		void precompute(const State& rbp1, const State& rbp2, const Real& dt, const Vector3r& shiftVel, bool avoidGranularRatcheting=true);
+
+		//! Rotates a "shear" vector to keep track of contact orientation. Returns reference of the updated vector.
+		Vector3r& rotate(Vector3r& tangentVector) const;
+		const Vector3r& shearIncrement() const {return shearInc;}
+
+//!________________REMOVED IN NEXT STEP___________________________________
+ 		Vector3r& rotate(Vector3r& tangentVector, const Vector3r& prevNormal, const State* rbp1, const State* rbp2, Real dt);
 		//! update shear on this contact given dynamic parameters of bodies. Should be called from constitutive law, exactly once per iteration. Returns shear increment in this update, which is already added to shear.
 		Vector3r updateShear(const State* rbp1, const State* rbp2, Real dt, bool avoidGranularRatcheting=true);
-		
-		virtual ~ScGeom();
 		//! Update the shear force orientation (or any other vector, think "shear") on this contact given dynamic parameters of bodies. Compute and return shear displacement increment.
-		Vector3r rotateAndGetShear(Vector3r& shearForce, const Vector3r& prevNormal, const State* rbp1, const State* rbp2, Real dt,bool avoidGranularRatcheting=true);
+		const Vector3r& rotateAndGetShear(Vector3r& shearForce, const Vector3r& prevNormal, const State* rbp1, const State* rbp2, Real dt,bool avoidGranularRatcheting=true);
 		//!  Periodic variant. Needs the velocity shift across periods for periodic BCs (else it is safe to pass Vector3r::Zero()). Typically obtained as scene->cell->velGrad*scene->cell->Hsize*cellDist. It would be better to define the shift transparently inside the function, but it needs scene and interaction pointers, which we don't have here.
-		Vector3r rotateAndGetShear(Vector3r& shearForce, const Vector3r& prevNormal, const State* rbp1, const State* rbp2, Real dt, const Vector3r& shiftVel, bool avoidGranularRatcheting=true);
-		
-		//!precompute values of shear increment and interaction rotation data
-		void precompute(const State& rbp1, const State& rbp2, const Real& dt,bool avoidGranularRatcheting=true);
-		//!  Periodic variant. Needs the velocity shift across periods for periodic BCs (else it is safe to pass Vector3r::Zero()). Typically obtained as scene->cell->velGrad*scene->cell->Hsize*cellDist.
-		void precompute(const State& rbp1, const State& rbp2, const Real& dt, const Vector3r& shiftVel, bool avoidGranularRatcheting=true);
-
-		//! Add method which only rotates the shear vector (or another tangent vector as well). Returns reference of the updated vector.
-		Vector3r& rotate(Vector3r& tangentVector, const Vector3r& prevNormal, const State* rbp1, const State* rbp2, Real dt);
-		//! Same rotation using precomputed values
-		Vector3r& rotate(Vector3r& tangentVector);
+		const Vector3r& rotateAndGetShear(Vector3r& shearForce, const Vector3r& prevNormal, const State* rbp1, const State* rbp2, Real dt, const Vector3r& shiftVel, bool avoidGranularRatcheting=true);
+//!________________END REMOVED IN NEXT STEP___________________________________
 		
 		// Add method which returns the impact velocity (then, inside the contact law, this can be split into shear and normal component). Handle periodicity.
 		Vector3r getIncidentVel(const State* rbp1, const State* rbp2, Real dt, const Vector3r& shiftVel, bool avoidGranularRatcheting=true);
 		// Implement another version of getIncidentVel which does not handle periodicity.
 		Vector3r getIncidentVel(const State* rbp1, const State* rbp2, Real dt, bool avoidGranularRatcheting=true);
 	
-		YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(ScGeom,GenericSpheresContact,"Class representing :yref:`geometry<InteractionGeometry>` of two :yref:`spheres<Sphere>` in contact. The contact has 3 DOFs (normal and 2×shear) and uses incremental algorithm for updating shear. (For shear formulated in total displacements and rotations, see :yref:`Dem3DofGeom` and related classes).\n\nWe use symbols $\\vec{x}$, $\\vec{v}$, $\\vec{\\omega}$ respectively for position, linear and angular velocities (all in global coordinates) and $r$ for particles radii; subscripted with 1 or 2 to distinguish 2 spheres in contact. Then we compute unit contact normal\n\n.. math::\n\n\t\\vec{n}=\\frac{\\vec{x}_2-\\vec{x}_1}{||\\vec{x}_2-\\vec{x}_1||}\n\nRelative velocity of spheres is then\n\n.. math::\n\n\t\\vec{v}_{12}=(\\vec{v}_2+\\vec{\\omega}_2\\times(-r_2\\vec{n}))-(\\vec{v}_1+\\vec{\\omega}_1\\times(r_1\\vec{n}))\n\nand its shear component\n\n.. math::\n\n\t\\Delta\\vec{v}_{12}^s=\\vec{v}_{12}-(\\vec{n}\\cdot\\vec{v}_{12})\\vec{n}.\n\nTangential displacement increment over last step then reads\n\n.. math::\n\n\t\\vec{x}_{12}^s=\\Delta t \\vec{v}_{12}^s.",
+		YADE_CLASS_BASE_DOC_ATTRS_INIT_CTOR_PY(ScGeom,GenericSpheresContact,"Class representing :yref:`geometry<InteractionGeometry>` of two :yref:`bodies<Body>` in contact. The contact has 3 DOFs (normal and 2×shear) and uses incremental algorithm for updating shear. (For shear formulated in total displacements and rotations, see :yref:`Dem3DofGeom` and related classes).\n\nWe use symbols $\\vec{x}$, $\\vec{v}$, $\\vec{\\omega}$ respectively for position, linear and angular velocities (all in global coordinates) and $r$ for particles radii; subscripted with 1 or 2 to distinguish 2 spheres in contact. Then we compute unit contact normal\n\n.. math::\n\n\t\\vec{n}=\\frac{\\vec{x}_2-\\vec{x}_1}{||\\vec{x}_2-\\vec{x}_1||}\n\nRelative velocity of spheres is then\n\n.. math::\n\n\t\\vec{v}_{12}=(\\vec{v}_2+\\vec{\\omega}_2\\times(-r_2\\vec{n}))-(\\vec{v}_1+\\vec{\\omega}_1\\times(r_1\\vec{n}))\n\nand its shear component\n\n.. math::\n\n\t\\Delta\\vec{v}_{12}^s=\\vec{v}_{12}-(\\vec{n}\\cdot\\vec{v}_{12})\\vec{n}.\n\nTangential displacement increment over last step then reads\n\n.. math::\n\n\t\\vec{x}_{12}^s=\\Delta t \\vec{v}_{12}^s.",
 		((Vector3r,contactPoint,Vector3r::Zero(),"Reference point of the contact. |ycomp|"))
-		((Vector3r,shear,Vector3r::Zero(),"Total value of the current shear. Update the value using ScGeom::updateShear. |ycomp|"))
-		((Vector3r,prevNormal,Vector3r::Zero(),"Normal of the contact in the previous step. |ycomp|"))
-		,
+		((Vector3r,shear,Vector3r::Zero(),"Total value of the current shear. Update the value using ScGeom::updateShear. This is experimental. You have to update it yourself in constitutive laws using e.g. :yref:`ScGeom::rotate` and :yref:`ScGeom::shearIncrement`"))
+		((Vector3r,prevNormal,Vector3r::Zero(),"Normal of the contact in the previous step. |ycomp|"))		,
 		/* extra initializers */ ((radius1,GenericSpheresContact::refR1)) ((radius2,GenericSpheresContact::refR2)),
-		/* ctor */ createIndex(); shearIncrement=Vector3r::Zero(); twist_axis=orthonormal_axis=Vector3r::Zero();,
+		/* ctor */ createIndex(); shearInc=Vector3r::Zero(); twist_axis=orthonormal_axis=Vector3r::Zero();,
 		/* py */
 		.def_readwrite("penetrationDepth",&ScGeom::penetrationDepth,"documentation")
+		.def_readonly("shearIncrement",&ScGeom::shearInc,"documentation")
 	);
 	REGISTER_CLASS_INDEX(ScGeom,GenericSpheresContact);
 };

=== modified file 'pkg/dem/Engine/Functor/Ig2_Box_Sphere_ScGeom.cpp'
--- pkg/dem/Engine/Functor/Ig2_Box_Sphere_ScGeom.cpp	2010-07-08 18:37:01 +0000
+++ pkg/dem/Engine/Functor/Ig2_Box_Sphere_ScGeom.cpp	2010-07-13 19:14:55 +0000
@@ -156,10 +156,10 @@
 		c->interactionGeometry = scm;
 	}
 #ifdef IGCACHE
-	if (scene->isPeriodic){
-		Vector3r shiftVel = scene->cell->velGrad*scene->cell->Hsize*c->cellDist.cast<Real>();
- 		scm->precompute(state1,state2,scene->dt,shiftVel,true);}
- 	else scm->precompute(state1,state2,scene->dt,true);
+// 	if (scene->isPeriodic){
+// 		Vector3r shiftVel = scene->cell->velGrad*scene->cell->Hsize*c->cellDist.cast<Real>();
+//  		scm->precompute(state1,state2,scene->dt,shiftVel,true);}
+ 	/*else */scm->precompute(state1,state2,scene,c,true);
 #endif
 	return true;
 }

=== modified file 'pkg/dem/Engine/Functor/Ig2_Facet_Sphere_ScGeom.cpp'
--- pkg/dem/Engine/Functor/Ig2_Facet_Sphere_ScGeom.cpp	2010-07-08 18:14:12 +0000
+++ pkg/dem/Engine/Functor/Ig2_Facet_Sphere_ScGeom.cpp	2010-07-13 19:14:55 +0000
@@ -112,10 +112,10 @@
 		scm->radius2 = sphereRadius;
 		if (!c->interactionGeometry)
 			c->interactionGeometry = scm;
-		if (scene->isPeriodic){
-			Vector3r shiftVel = scene->cell->velGrad*scene->cell->Hsize*c->cellDist.cast<Real>();
- 			scm->precompute(state1,state2,scene->dt,shiftVel,true);}
- 		else scm->precompute(state1,state2,scene->dt,true);
+// 		if (scene->isPeriodic){
+// 			Vector3r shiftVel = scene->cell->velGrad*scene->cell->Hsize*c->cellDist.cast<Real>();
+//  			scm->precompute(state1,state2,scene->dt,shiftVel,true);}
+ 		/*else */scm->precompute(state1,state2,scene,c,true);
 		return true;
 	}
 	return false;

=== modified file 'pkg/dem/Engine/Functor/Ig2_Sphere_Sphere_ScGeom.cpp'
--- pkg/dem/Engine/Functor/Ig2_Sphere_Sphere_ScGeom.cpp	2010-07-08 18:14:12 +0000
+++ pkg/dem/Engine/Functor/Ig2_Sphere_Sphere_ScGeom.cpp	2010-07-13 19:14:55 +0000
@@ -43,10 +43,10 @@
 		scm->radius1=s1->radius;
 		scm->radius2=s2->radius;
 #ifdef IGCACHE
-		if (scene->isPeriodic) {
-			Vector3r shiftVel = scene->cell->velGrad*scene->cell->Hsize*c->cellDist.cast<Real>();
- 			scm->precompute(state1,state2,scene->dt,shiftVel,true);}
- 		else scm->precompute(state1,state2,scene->dt,true);
+// 		if (scene->isPeriodic) {
+// 			Vector3r shiftVel = scene->cell->velGrad*scene->cell->Hsize*c->cellDist.cast<Real>();
+//  			scm->precompute(state1,state2,scene->dt,shiftVel,true);}
+ 		/*else */scm->precompute(state1,state2,scene,c,true);
 #endif
 				
 		// keep this for reference on how to compute bending and torsion from relative orientation; parts in ScGeom header

=== modified file 'pkg/dem/Engine/GlobalEngine/CohesiveFrictionalContactLaw.cpp'
--- pkg/dem/Engine/GlobalEngine/CohesiveFrictionalContactLaw.cpp	2010-07-12 17:44:48 +0000
+++ pkg/dem/Engine/GlobalEngine/CohesiveFrictionalContactLaw.cpp	2010-07-13 19:14:55 +0000
@@ -63,7 +63,6 @@
 	if (un < 0 && (currentContactPhysics->normalForce.squaredNorm() > pow(currentContactPhysics->normalAdhesion,2)
 	               || currentContactPhysics->normalAdhesion==0)) {
 		// BREAK due to tension
-		cerr <<"requesterase";
 		ncb->interactions->requestErase(contact->getId1(),contact->getId2());
 		// contact->interactionPhysics was reset now; currentContactPhysics still hold the object, but is not associated with the interaction anymore
 // 			currentContactPhysics->cohesionBroken = true;
@@ -77,7 +76,7 @@
 		///////////////////////// CREEP END ////////////
 #ifdef IGCACHE
 		Vector3r& shearForce = currentContactGeometry->rotate(currentContactPhysics->shearForce);
-		const Vector3r& dus = currentContactGeometry->shearIncrement;
+		const Vector3r& dus = currentContactGeometry->shearIncrement();
 #else
 		Vector3r dus = currentContactGeometry->rotateAndGetShear(shearForce,currentContactPhysics->prevNormal,de1,de2,dt);
 #endif

=== modified file 'pkg/dem/Engine/GlobalEngine/CohesiveFrictionalPM.cpp'
--- pkg/dem/Engine/GlobalEngine/CohesiveFrictionalPM.cpp	2010-06-08 22:25:00 +0000
+++ pkg/dem/Engine/GlobalEngine/CohesiveFrictionalPM.cpp	2010-07-13 19:14:55 +0000
@@ -66,9 +66,15 @@
 	// using scGeom function rotateAndGetShear	
 	State* st1 = Body::byId(id1,scene)->state.get();
 	State* st2 = Body::byId(id2,scene)->state.get();
+
+#ifdef IGCACHE
+	geom->rotate(phys->shearForce);
+	const Vector3r& dus = geom->shearIncrement();
+#else
 	// define shift to handle periodicity
 	Vector3r shiftVel = scene->isPeriodic ? (Vector3r)((scene->cell->velGrad*scene->cell->Hsize)*Vector3r((Real) contact->cellDist[0],(Real) contact->cellDist[1],(Real) contact->cellDist[2])) : Vector3r::Zero();
   	Vector3r dus = geom->rotateAndGetShear(shearForce, phys->prevNormal, st1, st2, dt, shiftVel, preventGranularRatcheting);
+#endif
 	/// before changes to adapt periodic scene it was like that: Vector3r dus = geom->rotateAndGetShear(shearForce, phys->prevNormal, st1, st2, dt, preventGranularRatcheting);
 	//Linear elasticity giving "trial" shear force
 	shearForce -= phys->ks*dus;

=== modified file 'pkg/dem/Engine/GlobalEngine/ElasticContactLaw.cpp'
--- pkg/dem/Engine/GlobalEngine/ElasticContactLaw.cpp	2010-07-08 18:14:12 +0000
+++ pkg/dem/Engine/GlobalEngine/ElasticContactLaw.cpp	2010-07-13 19:14:55 +0000
@@ -66,7 +66,7 @@
 	currentContactPhysics->normalForce=currentContactPhysics->kn*std::max(un,(Real) 0)*currentContactGeometry->normal;
 #ifdef IGCACHE
 	Vector3r& shearForce = currentContactGeometry->rotate(currentContactPhysics->shearForce);
-	Vector3r& shearDisp = currentContactGeometry->shearIncrement;
+	const Vector3r& shearDisp = currentContactGeometry->shearIncrement();
 #else
 	Vector3r& shearForce = currentContactPhysics->shearForce;
 	Vector3r shiftVel = scene->isPeriodic ? (Vector3r)((scene->cell->velGrad*scene->cell->Hsize)*Vector3r((Real) contact->cellDist[0],(Real) contact->cellDist[1],(Real) contact->cellDist[2])) : Vector3r::Zero();

=== modified file 'scripts/test/chained-cylinder-spring.py'
--- scripts/test/chained-cylinder-spring.py	2010-07-12 19:41:57 +0000
+++ scripts/test/chained-cylinder-spring.py	2010-07-13 19:14:55 +0000
@@ -6,21 +6,21 @@
 
 from yade import utils
 young=1.0e5
-poisson=10
+poisson=5
 density=2.60e3 
 frictionAngle=radians(30)
 O.materials.append(CohFrictMat(young=young,poisson=poisson,density=density,frictionAngle=frictionAngle,label='mat'))
-O.dt=9e-5
+O.dt=1e-4
 
 O.initializers=[
 	## Create bounding boxes. They are needed to zoom the 3d view properly before we start the simulation.
-	BoundDispatcher([Bo1_Sphere_Aabb(),Bo1_Cylinder_Aabb()])
+	BoundDispatcher([Bo1_Sphere_Aabb(),Bo1_ChainedCylinder_Aabb()])
 ]
 
 O.engines=[
 	ForceResetter(),
 	BoundDispatcher([
-		Bo1_Cylinder_Aabb(),
+		Bo1_ChainedCylinder_Aabb(),
 		Bo1_Sphere_Aabb()
 	]),
 	InsertionSortCollider(),
@@ -34,7 +34,7 @@
 	## Motion equation
 	NewtonIntegrator(damping=0.15),
 	PeriodicPythonRunner(iterPeriod=500,command='history()'),
-	#PeriodicPythonRunner(iterPeriod=100,command='yade.qt.center()')
+	PeriodicPythonRunner(iterPeriod=5000,command='if O.iter<21000 : yade.qt.center()')
 ]
 
 #Generate a spiral
@@ -66,7 +66,6 @@
 		     t=O.time)
 
 #yade.qt.Renderer().bound=True
-#O.run(1,True)
 plot.plot()
 #O.bodies[0].state.angVel=Vector3(0.05,0,0)
 


Follow ups