/*
* Copyright (c) 2006-2009 Erin Catto http://www.gphysics.com
*
* This software is provided 'as-is', without any express or implied
* warranty.  In no event will the authors be held liable for any damages
* arising from the use of this software.
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/

#ifndef ELASTIC_BODY_H
#define ELASTIC_BODY_H

class ElasticBody : public Test
{
public:
	b2Body* bodies[64];
    b2Body*  m_ground;
	b2Body*  m_elev;
	b2PrismaticJoint* m_joint_elev;
	/// Main...
	ElasticBody()
	{
		/// Bottom static body
		{ 
			b2PolygonDef sd;
			sd.SetAsBox(50.0f, 2.0f);
			sd.friction = 0.1f;
			sd.restitution = 0.1f;
			b2BodyDef bd;
			bd.position.Set(-1.0f, -7.5f);
			m_ground = m_world->CreateBody(&bd);
			m_ground->CreateFixture(&sd);
		}
		/// Upper static body
		{
            b2PolygonDef sd;
			sd.SetAsBox(20.0f, 0.50f,b2Vec2(0.f,0.f),0.047f*b2_pi);
			sd.friction    = 0.01f;
			sd.restitution = 0.001f;  
			b2BodyDef bd;
			bd.position.Set(-20.f, 93.0f);
			b2Body* g = m_world->CreateBody(&bd);
			g->CreateFixture(&sd);
			sd.SetAsBox(15.f, 0.50f,b2Vec2(-15.0f,12.5f),0.0f);
            g->CreateFixture(&sd);

            sd.SetAsBox(20.f,0.5f,b2Vec2(0.0f,-25.0f),-0.5f);
			g->CreateFixture(&sd);
        }
		/// Left channel left wall
		{
            b2PolygonDef sd;
			sd.SetAsBox(0.7f, 55.0f);
			sd.friction    = 0.1f;
			sd.restitution = 0.1f;  
			b2BodyDef bd;
			bd.position.Set(-49.3f, 50.0f);
			b2Body* g = m_world->CreateBody(&bd);
			g->CreateFixture(&sd);
        }
		/// Right wall
		{
            b2PolygonDef sd;
			sd.SetAsBox(0.7f, 55.0f);
			sd.friction    = 0.1f;
			sd.restitution = 0.1f;  
			b2BodyDef bd;
			bd.position.Set(45.f, 50.0f);
			b2Body* g = m_world->CreateBody(&bd);
			g->CreateFixture(&sd);
        }
		/// Left channel right upper wall  
		{
            b2PolygonDef sd;
			sd.SetAsBox(0.5f, 20.0f);
			sd.friction    = 0.05f;
			sd.restitution = 0.01f;  
			b2BodyDef bd;
			bd.position.Set(-42.0f, 70.0f);
		    bd.angle = -0.03f*b2_pi;
			b2Body* g = m_world->CreateBody(&bd);
			g->CreateFixture(&sd);
		}
		/// Left channel right lower wall
		{
            b2PolygonDef sd;
			sd.SetAsBox(0.50f, 23.0f);
			sd.friction    = 0.05f;
			sd.restitution = 0.01f;  
			b2BodyDef bd;
			bd.position.Set(-44.0f, 27.0f);
			b2Body* g = m_world->CreateBody(&bd);
			g->CreateFixture(&sd);
        /// Bottom motors
		    b2CircleDef cd;
			cd.radius   = 3.0f;
			cd.density  = 15.0f;
			cd.friction = 1.f;
			cd.restitution = 0.2f;
        /// 1. 
			bd.position.Set(-40.0f,2.5f);
			b2Body* body = m_world->CreateBody(&bd);
			body->CreateFixture(&cd);
            body->SetMassFromShapes(); 
            b2RevoluteJointDef jr;
			jr.Initialize (g,body,body->GetWorldCenter()+b2Vec2(0.f,1.f));
            jr.maxMotorTorque = 30000.f;
            jr.enableMotor    = true; 
            jr.motorSpeed     = 20.f;
			m_world->CreateJoint(&jr);
        /// 1. left down
			bd.position.Set(-46.0f,-2.5f);
            cd. radius = 1.5f;  jr.motorSpeed  = -20.f;
			body = m_world->CreateBody(&bd);
			body->CreateFixture(&cd);
            sd.SetAsBox(2.0f, 0.50f);
            body->CreateFixture(&sd);
            body->SetMassFromShapes(); 
			jr.Initialize (g,body,body->GetWorldCenter());
			m_world->CreateJoint(&jr);
        /// 2.
            cd.radius   = 3.0f; jr.motorSpeed  = 20.f;
			bd.position.Set(-32.0f,2.5f);
			body = m_world->CreateBody(&bd);
			body->CreateFixture(&cd);
            body->SetMassFromShapes(); 
			jr.Initialize (g,body,body->GetWorldCenter()+b2Vec2(0.f,1.f));
			m_world->CreateJoint(&jr);
        /// 3.
            jr.motorSpeed     = 20.f;
			bd.position.Set(-24.0f,1.5f);
			body = m_world->CreateBody(&bd);
			body->CreateFixture(&cd);
            body->SetMassFromShapes(); 
			jr.Initialize (g,body,body->GetWorldCenter()+b2Vec2(0.f,1.f));
			m_world->CreateJoint(&jr);
        /// 4.
			bd.position.Set(-16.0f,0.8f);
			body = m_world->CreateBody(&bd);
			body->CreateFixture(&cd);
            body->SetMassFromShapes(); 
			jr.Initialize (g,body,body->GetWorldCenter()+b2Vec2(0.f,1.f));
			m_world->CreateJoint(&jr);
        /// 5.
			bd.position.Set(-8.0f,0.5f);
			body = m_world->CreateBody(&bd);
			body->CreateFixture(&cd);
            body->SetMassFromShapes(); 
			jr.Initialize (g,body,body->GetWorldCenter()+b2Vec2(0.f,1.f));
			m_world->CreateJoint(&jr);
        /// 6.
			bd.position.Set(0.0f,0.1f);
			body = m_world->CreateBody(&bd);
			body->CreateFixture(&cd);
            body->SetMassFromShapes(); 
			jr.Initialize (g,body,body->GetWorldCenter()+b2Vec2(0.f,1.f));
			m_world->CreateJoint(&jr);
        /// 7.
			bd.position.Set(8.0f,-0.5f);
			body = m_world->CreateBody(&bd);
			body->CreateFixture(&cd);
			sd.SetAsBox(3.7f, 0.5f);
			body->CreateFixture(&sd);
            body->SetMassFromShapes(); 
			jr.Initialize (g,body,body->GetWorldCenter()+b2Vec2(0.f,1.f));
			m_world->CreateJoint(&jr);
        /// 8. right rotator
            sd.SetAsBox(5.f, 0.5f);
            sd.density = 2.0f;
			bd.position.Set(18.0f,1.f);
			b2Body* rightmotor = m_world->CreateBody(&bd);
			rightmotor->CreateFixture(&sd);
			sd.SetAsBox(4.5f, 0.5f, b2Vec2(0.f,0.f),b2_pi/3.f);
			rightmotor->CreateFixture(&sd);
			sd.SetAsBox(4.5f, 0.5f, b2Vec2(0.f,0.f),b2_pi*2.f/3.f);
			rightmotor->CreateFixture(&sd);
			cd.radius = 4.2f;
			rightmotor->CreateFixture(&cd);
            rightmotor->SetMassFromShapes(); 
			jr.Initialize (g,rightmotor,rightmotor->GetWorldCenter());
            jr.maxMotorTorque = 70000.f;
            jr.motorSpeed     = -4.f;
            m_world->CreateJoint(&jr);
        /// 9. left rotator
            sd.SetAsBox(8.5f, 0.5f);
            sd.density = 2.0f;
			bd.position.Set(-34.0f,17.f);
			body = m_world->CreateBody(&bd);
			body->CreateFixture(&sd);
			sd.SetAsBox(8.5f, 0.5f, b2Vec2(0.f,0.f),b2_pi*.5f);
			body->CreateFixture(&sd);
			cd.radius = 7.f;
			cd.friction = 0.9f;
			body->CreateFixture(&cd);
            body->SetMassFromShapes(); 
			jr.Initialize (g,body,body->GetWorldCenter());
            jr.maxMotorTorque = 100000.f;
            jr.motorSpeed     = -5.f;            
            m_world->CreateJoint(&jr);
        /// big compressor
            sd.SetAsBox(3.0f,4.f);
            sd.density = 10.0f;
			bd.position.Set(-16.0f,17.f);
			b2Body *hammerleft = m_world->CreateBody(&bd);
			hammerleft->CreateFixture(&sd);
			hammerleft->SetMassFromShapes();
			b2DistanceJointDef jd;
			jd.Initialize(body, hammerleft, body->GetWorldCenter()+b2Vec2(0.f,6.f), hammerleft->GetWorldCenter() );
			m_world->CreateJoint(&jd);

			bd.position.Set(4.0f,17.f);
			b2Body *hammerright = m_world->CreateBody(&bd);
			hammerright->CreateFixture(&sd);
			hammerright->SetMassFromShapes();
			jd.Initialize(body, hammerright, body->GetWorldCenter()-b2Vec2(0.f,6.f), hammerright->GetWorldCenter() );
			m_world->CreateJoint(&jd);
            /// pusher
            sd.SetAsBox(6.f,0.75f);
			bd.position.Set(-21.0f,9.f);
			b2Body* pusher = m_world->CreateBody(&bd);
			pusher->CreateFixture(&sd);
			sd.SetAsBox(2.f,1.5f,b2Vec2(-5.f,0.f),0.f);
			pusher->SetMassFromShapes();
			pusher->CreateFixture(&sd);
			jd.Initialize(rightmotor,pusher,rightmotor->GetWorldCenter()+b2Vec2(-8.0f,0.f),
				          pusher->GetWorldCenter()+b2Vec2(5.0f,0.f) );
			m_world->CreateJoint(&jd);
        }
        /// Static bodies above motors
       {
            b2PolygonDef sd;
			b2CircleDef  cd;
			sd.SetAsBox(9.0f, 0.5f);
			sd.friction    = 0.05f;
			sd.restitution = 0.01f;  
			b2BodyDef bd;
			bd.position.Set(-15.5f, 12.f);
            bd.angle = 0.0;
			b2Body* g = m_world->CreateBody(&bd);
			g->CreateFixture(&sd);
		    
			sd.SetAsBox(8.f, 0.5f, b2Vec2(23.f,0.f),0.f);
			g->CreateFixture(&sd);
            /// compressor statics  
			sd.SetAsBox(7.0f, 0.5f, b2Vec2(-2.f,9.f),0.f);
			g->CreateFixture(&sd);
			sd.SetAsBox(9.0f, 0.5f, b2Vec2(22.f,9.f),0.f);
			g->CreateFixture(&sd);

			sd.SetAsBox(19.0f, 0.5f, b2Vec2(-9.f,15.f),-0.05f);
			g->CreateFixture(&sd);
			sd.SetAsBox(4.7f, 0.5f, b2Vec2(15.f,11.5f),-0.5f);
			g->CreateFixture(&sd);
            /// below compressor
			sd.SetAsBox(26.0f, 0.3f, b2Vec2(17.f,-4.4f),-0.02f);
			g->CreateFixture(&sd);
			cd.radius   = 1.0f;	cd.friction = 1.0;
			cd.localPosition = b2Vec2(29.f,-6.f);
            g->CreateFixture(&cd); 
            cd.radius   = 0.7f;
			cd.localPosition = b2Vec2(-2.f,-4.5f);
            g->CreateFixture(&cd);
        }
        /// Elevator
        {
            b2BodyDef  bd;	
            b2CircleDef cd;
            b2PolygonDef sd;

			bd.position.Set(40.0f,4.0f);
			m_elev = m_world->CreateBody(&bd);

			sd.SetAsBox(0.5f, 2.5f,b2Vec2(3.0f,-3.0f), 0.f);
			sd.density     = 1.f;
			sd.friction    = 0.01f;
			m_elev->CreateFixture(&sd);
			sd.SetAsBox(7.0f, 0.5f, b2Vec2(-3.5f,-5.5f), 0.f);
			m_elev->CreateFixture(&sd);
			sd.SetAsBox(0.5f, 2.5f, b2Vec2(-11.f,-3.5f), 0.f);
			m_elev->CreateFixture(&sd);
            m_elev->SetMassFromShapes();           
		    
			b2PrismaticJointDef jp;
			jp.Initialize(m_ground,m_elev, bd.position, b2Vec2(0.0f, 1.0f));
			jp.lowerTranslation =  0.0f;
			jp.upperTranslation = 100.0f;
			jp.enableLimit = true;		
			jp.enableMotor = true;
			jp.maxMotorForce = 10000.f;
			jp.motorSpeed    = 0.f; 
			m_joint_elev = (b2PrismaticJoint*)m_world->CreateJoint(&jp);			  
            
			/// Korb
            sd.SetAsBox(2.3f, 0.5f,b2Vec2(1.f,0.0f), 0.0f);
            sd.density = 0.5f;
            bd.position.Set(29.0f,6.5f);
			b2Body* body = m_world->CreateBody(&bd);
			body->CreateFixture(&sd);
            sd.SetAsBox(2.5f, 0.5f,b2Vec2(3.0f,-2.f), b2_pi/2.f);
            body->CreateFixture(&sd);
            sd.SetAsBox(4.6f, 0.5f,b2Vec2(7.8f,-4.0f), 0.f);
            body->CreateFixture(&sd);
            sd.SetAsBox(0.5f, 4.5f,b2Vec2(12.f,0.0f), 0.f);
            body->CreateFixture(&sd);
            
			sd.SetAsBox(0.5f, 0.5f,b2Vec2(13.f,4.0f), 0.f);
            body->CreateFixture(&sd);

            cd.radius   = 0.7f; cd.density  = 1.f; cd.friction = 0.01f;
            cd.localPosition = b2Vec2(0.f,0.f);
            body->CreateFixture(&cd);
            body->SetMassFromShapes();  

            b2RevoluteJointDef jr;
            jr.Initialize(m_elev,body, bd.position);
            jr.enableLimit = true;
            jr.lowerAngle  = -0.2f;
            jr.upperAngle  = b2_pi*1.1f;
            jr.collideConnected = true;
            m_world->CreateJoint(&jr);
            /// upper body exit
            sd.SetAsBox(14.0f, 0.5f,b2Vec2(-3.5f,-10.0f), 0.0f);
            bd.position.Set(17.5f,96.0f);
            body = m_world->CreateBody(&bd);
            body->CreateFixture(&sd);
		}
		/// "Elastic body" 64 bodies - something like a lin. elastic compound
		/// connected via dynamic forces (springs) 
		{
			b2PolygonDef sd;
			sd.SetAsBox(0.55f, 0.55f);
			sd.density    = 1.5f;
			sd.friction   = 0.01f;
			sd.filter.groupIndex = -1;
			b2Vec2       startpoint(30.f,20.f);
			b2BodyDef    bd;
			bd.isBullet   = false;
  	 	    bd.allowSleep = false;	
			for (int i = 0; i < 8; ++i) 
            {
				for (int j = 0; j < 8; ++j) 
                {
					bd.position.Set(j*1.02f, 2.51f + 1.02f * i);
					bd.position  += startpoint;
					b2Body* body  = m_world->CreateBody(&bd);
					bodies[8*i+j] = body;
					body->CreateFixture(&sd);
					body->SetMassFromShapes();
				}
			}
		}
	}
	///  Apply dynamic forces (springs) and check elevator state
	void Step(Settings* settings)
	{
		Test::Step(settings);
		for (int i=0; i<8; ++i){
			for (int j=0; j<8; ++j){
				b2Vec2 zero(0.0f,0.0f);
				b2Vec2 down(0.0f, -0.5f);
				b2Vec2 up(0.0f, 0.5f);
				b2Vec2 right(0.5f, 0.0f);
				b2Vec2 left(-0.5f, 0.0f);
				int ind = i*8+j;
				int indr = ind+1;
				int indd = ind+8;
				float32 spring = 500.0f;
				float32 damp = 5.0f;
				if (j<7) {
					AddSpringForce(*(bodies[ind]),zero,*(bodies[indr]),zero,spring, damp, 1.0f);
					AddSpringForce(*(bodies[ind]),right,*(bodies[indr]),left,0.5f*spring, damp, 0.0f);
				}
				if (i<7) {
					AddSpringForce(*(bodies[ind]),zero,*(bodies[indd]),zero,spring, damp, 1.0f);
					AddSpringForce(*(bodies[ind]),up,*(bodies[indd]),down,0.5f*spring,damp,0.0f);
				}
				int inddr = indd + 1;
				int inddl = indd - 1;
				float32 drdist = sqrtf(2.0f);
				if (i < 7 && j < 7){
					AddSpringForce(*(bodies[ind]),zero,*(bodies[inddr]),zero,spring, damp, drdist);
				}
				if (i < 7 && j > 0){
					AddSpringForce(*(bodies[ind]),zero,*(bodies[inddl]),zero,spring, damp, drdist);
				}

				indr = ind+2;
				indd = ind+8*2;
				if (j<6) {
					AddSpringForce(*(bodies[ind]),zero,*(bodies[indr]),zero,spring, damp, 2.0f);
				}
				if (i<6) {
					AddSpringForce(*(bodies[ind]),zero,*(bodies[indd]),zero,spring,damp,2.0f);
				}

				inddr = indd + 2;
				inddl = indd - 2;
				drdist = sqrtf(2.0f)*2.0f;
				if (i < 6 && j < 6){
					AddSpringForce(*(bodies[ind]),zero,*(bodies[inddr]),zero,spring, damp, drdist);
				}
				if (i < 6 && j > 1){
					AddSpringForce(*(bodies[ind]),zero,*(bodies[inddl]),zero,spring, damp, drdist);
				}
			}
		}
		/// Check if bodies are near elevator
		///  Look if the body to lift is near the elevator
		b2Vec2 p1 = bodies[0]->GetWorldCenter(); 
        b2Vec2 p2 = bodies[63]->GetWorldCenter(); 
		///    m_elev:   elevator prism. joint
		b2Vec2 e = m_elev->GetWorldCenter() + b2Vec2(0.f,7.f);  
		// maybe not the best way to do it...
		// Bodies reached the elevator side 
		if ( p1.x>e.x || p2.x>e.x )	{
  		    // go up
			if ( ( p1.y<e.y || p2.y<e.y ) &&
				 ( m_joint_elev->GetJointTranslation()<=m_joint_elev->GetLowerLimit()+1.f ) ) 
			{
				m_joint_elev->SetMotorSpeed(20.f);
                //printf("lift goes up trans: %G\n",m_joint_elev->GetJointTranslation());
			}
		}
		// go down
		if ( (m_joint_elev->GetJointTranslation()>=m_joint_elev->GetUpperLimit()-2.f) ) 
		{
               m_joint_elev->SetMotorSpeed(-15.f);
               //printf("lift goes down: %G\n",m_joint_elev->GetJointTranslation());
		}
	}
   /// Add a spring force
   void AddSpringForce(b2Body& bA, b2Vec2& localA, b2Body& bB, b2Vec2& localB, float32 k, float32 friction, float32 desiredDist)
   {
        b2Vec2 pA = bA.GetWorldPoint(localA);
        b2Vec2 pB = bB.GetWorldPoint(localB);
        b2Vec2 diff = pB - pA;
        //Find velocities of attach points
        b2Vec2 vA = bA.GetLinearVelocity() - b2Cross(bA.GetWorldVector(localA), bA.GetAngularVelocity());
        b2Vec2 vB = bB.GetLinearVelocity() - b2Cross(bB.GetWorldVector(localB), bB.GetAngularVelocity());
        b2Vec2 vdiff = vB-vA;
        float32 dx = diff.Normalize(); //normalizes diff and puts length into dx
        float32 vrel = vdiff.x*diff.x + vdiff.y*diff.y;
        float32 forceMag = -k*(dx-desiredDist) - friction*vrel;
        diff *= forceMag; // diff *= forceMag
        bB.ApplyForce(diff, bA.GetWorldPoint(localA));
        diff *= -1.0f;
        bA.ApplyForce(diff, bB.GetWorldPoint(localB));
    }
    /// Default constructor
	static Test* Create()
	{
		return new ElasticBody;
	}
};

#endif
