Пример #1
0
    /// <summary>
    /// Accumulates the forces internal to the JelloPressureBody.
    /// Calculates forces from gas pressure.
    /// Base class will calculate internal, edge, and custom JelloSpring forces.
    /// This is called JelloWorld.Iterations times per Time.FixedUpdate by JelloWorld.update();
    /// </summary>
    /// <param name="deltaTime">Amount of time to calculate forces over.</param>
    public override void accumulateInternalForces(float deltaTime)
    {
        //spring forces calculated here
        base.accumulateInternalForces(deltaTime);
        // internal forces based on pressure equations.  we need 2 loops to do this.  one to find the overall volume of the
        // body, and 1 to apply forces.  we will need the normals for the edges in both loops, so we will cache them and remember them.
        int     j;
        Vector2 edge;
        Vector2 pressureV;

        mVolume = 0f;

        if (prevScale != Scale)
        {
            scaleRatio = Vector2.Distance(Vector2.zero, (Vector2)Scale) * 0.8761f;
            prevScale  = Scale;
        }

        // first calculate the volume of the body, and cache normals as we go.
        for (int i = 0; i < mEdgePointMasses.Length; i++)
        {
            j = i + 1 < mEdgePointMasses.Length ? i + 1 : 0;

            edge = JelloVectorTools.getPerpendicular(mEdgePointMasses[j].Position - mEdgePointMasses[i].Position);

            //cache edge length
            mEdgeLengthList[i] = Vector2.Distance(Vector2.zero, edge);             //TODO consider incorporating collision info class?

            // cache normal
            mNormalList[i] = edge;
            if (mEdgeLengthList[i] != 0f)
            {
                mNormalList[i] /= mEdgeLengthList[i];
            }

            if (Shape.winding == JelloClosedShape.Winding.CounterClockwise)
            {
                mNormalList[i] *= -1f;
            }

            // add to volume
            mVolume += 0.5f * (mEdgePointMasses[j].Position.x - mEdgePointMasses[i].Position.x) * (mEdgePointMasses[j].Position.y + mEdgePointMasses[i].Position.y);
        }

        mVolume        = Mathf.Abs(mVolume);
        mInverseVolume = 1 / mVolume;

        // now loop through, adding forces!
        for (int i = 0; i < mEdgePointMasses.Length; i++)
        {
            j = i + 1 < mEdgePointMasses.Length ? i + 1 : 0;

            pressureV = mNormalList[i] * mInverseVolume * mEdgeLengthList[i] * mGasAmount * scaleRatio;

            mEdgePointMasses[i].force += pressureV;
            mEdgePointMasses[j].force += pressureV;
        }
    }
	/// <summary>
	/// Checks if two convex shapes overlap.
	/// Requires ClockWise winding. 
	/// Uses separating axis theorem.
	/// </summary>
	/// <param name="shapeA">The first convex shape.</param>
	/// <param name="shapeB">The second convex shape.</param>
	/// <returns>Whether the two convex shapes overlap.</returns>
	/// 
	/// <dl class="example"><dt>Example</dt></dl>
	/// ~~~{.c}
	/// //keep moving a triangle to the right until it no longer overlaps another one
	/// Vector2[] triangleOne, triangleTwo;
	/// 
	/// while(JelloShapeTools.Intersect_SAT(triangleOne, triangleTwo)))
	/// {
	/// 	foreach(Vector2 v in triangleOne)
	/// 	{
	/// 		v += Vector2.Right;
	/// 	}
	/// }
	/// ~~~
	public static bool IntersectSAT(Vector2[] shapeA, Vector2[] shapeB)
	{		
		float maxA = 0f,
				minA = 0f,
				maxB = 0f,
				minB = 0f,
				p = 0f;
		
		Vector2 axis = Vector2.zero;
		
		//project shapes onto each axis and compare max/min
		for(int i = 0; i < shapeA.Length + shapeB.Length; i++)
		{
			if(i < shapeA.Length)
				axis = JelloVectorTools.getPerpendicular(shapeA[i + 1 < shapeA.Length ? i + 1 : 0] - shapeA[i]);
			else
				axis = JelloVectorTools.getPerpendicular(shapeB[i + 1 - shapeA.Length < shapeB.Length ? i + 1 - shapeA.Length : 0] - shapeB[i - shapeA.Length]);
			
			maxA = Vector2.Dot (shapeA[0], axis);
			minA = maxA;
			for(int a = 1; a < shapeA.Length; a++)
			{
				p = Vector2.Dot (shapeA[a], axis);
				
				if(p  > maxA)
					maxA = p;
				if(p < minA)
					minA = p;
			}
									
			maxB = Vector2.Dot(shapeB[0], axis);
			minB = maxB;
			for(int a = 1; a < shapeB.Length; a++)
			{
				p = Vector2.Dot (shapeB[a], axis);

				if(p  > maxB)
					maxB = p;
				if(p < minB)
					minB = p;
			}						
			
			if(maxA < minB || minA > maxB)
				return false;
		}
		
		return true;
	}
	/// <summary>
	/// Processes the pull.
	/// </summary>
	/// <returns>IEnumerator.</returns>
	IEnumerator ProcessPull()
	{
		//Grab the closest point mass and process adjacent points.
		GrabPointMass();

		//could be true later if you are still pulling the body, but do not have a specific point grabbed.
		//this would happen if when you are holding down the mouse button, its position is within the jello body.
		bool waitingForNewGrab = false;

		//keep processing the pull as long as the mouse button is depressed.
		while(Input.GetMouseButton(0))
		{
//			if(requireGrounded && !grounded)
//				break;

			//wake up the body if its being grabbed.
			body.IsAwake = true;

			//find the mouses current position in world space.
			mousePosInWorld = Camera.main.ScreenToWorldPoint(Input.mousePosition);

			//If our mouse position is outside of the body's transformed base shape
			if(!JelloShapeTools.Contains(body.Shape.EdgeVertices, body.transform.InverseTransformPoint(mousePosInWorld)))
			{
				//if our mouse position was inside of the base shape last step, grab a new point mass.
				if(waitingForNewGrab)
				{
					GrabPointMass();
					waitingForNewGrab = false;
				}

				//the base shape position (local and global) respective to the selected point mass.
				Vector2 pt = body.Shape.EdgeVertices[pmIndex];
				Vector2 ptGlobal = (Vector2)body.transform.TransformPoint(pt);

				//if we want the body to rotate to align with the pull
				if(rotate)
				{
					//find the difference in angle between the two vector
					Vector2 dir1 = mousePosInWorld - body.Position; 																						//body position to mouse position
					Vector2 dir2 = (Vector2)body.transform.TransformPoint(body.Shape.EdgeVertices[pmIndex]) - body.Position; 	//body position to xformed base shape position
					float ang = Vector2.Angle(dir1, dir2);

					//correct our body angle only a bit at a time for smooth rotations.
					ang = Mathf.Clamp(ang, 0f, ang * rotateSpeed * Time.fixedDeltaTime);
					
					if(JelloVectorTools.CrossProduct(dir1, dir2) < 0f)
						ang *=  -1f;
					body.Angle -= ang;
				}
				else
				{
					//we dont want the body to rotate to align and will constrain the point mass to a cone.

					//find the two shape positions next to our selected shape position.
					Vector2 prev = body.Shape.EdgeVertices[pmIndex > 0 ? pmIndex - 1 : body.Shape.EdgeVertexCount - 1];
					Vector2 next = body.Shape.EdgeVertices[pmIndex + 1 < body.Shape.EdgeVertexCount ? pmIndex + 1: 0];

					//vectors to/from adjacent ponts
					Vector2 fromPrev = pt - prev;
					Vector2 toNext = next - pt;
					//normal created by adjacent vectors
					//this is the bisector of the angle created by the prev to pt to next vectors
					//and will be used as the bisector of the constraining cone.
					Vector2 ptNorm = JelloVectorTools.getPerpendicular(fromPrev + toNext);
					//correct normal direction by shape winding.
					ptNorm = body.Shape.winding == JelloClosedShape.Winding.Clockwise ? ptNorm : -ptNorm;

					//convert to global coordinates
					ptNorm = (Vector2)body.transform.TransformDirection(ptNorm);
					//find the angle between the our mouse and the bisector.
					float ang = Vector2.Angle (ptNorm, mousePosInWorld - ptGlobal);

					//if we exceed the constraint of the cone.
					if(ang > coneAngle * 0.5f) //0.5 because the bisector cuts the cone angle in half.
					{
						//find the vector representing the edge of the cone
						Vector2 limitVector;
						if(JelloVectorTools.CrossProduct (ptNorm, mousePosInWorld - ptGlobal) < 0f )//which side of the bisector are we on
							limitVector = JelloVectorTools.rotateVector(ptNorm, -coneAngle * 0.5f);
						else
							limitVector = JelloVectorTools.rotateVector(ptNorm, coneAngle * 0.5f);

						//move our position to the closest point on the limit vector. Handle max pull back at the same time.
						mousePosInWorld = JelloVectorTools.getClosestPointOnSegment(mousePosInWorld, ptGlobal, ptGlobal + limitVector.normalized * maxPullBack);
					}
				}

				//how far away from xformed base shape position we are.
				pullBackDistance = (mousePosInWorld - ptGlobal).sqrMagnitude;
				if(pullBackDistance != 0f)
				{
					//if we exceed the max pullback, set to the max pullback.
					if(pullBackDistance > maxPullBack * maxPullBack)
					{
						mousePosInWorld = ptGlobal + (mousePosInWorld - ptGlobal).normalized * maxPullBack; // still do this when angle is not right.
						pullBackDistance = maxPullBack * maxPullBack;
					}
				}

				//explicitly set the selected pointmass position and velocity.
				pointmass.Position = mousePosInWorld;
				pointmass.velocity = Vector2.zero;	
			}
			else//our mouse is down, but is inside the perimeter of the body.
			{
				ReleasePointMass();//release the selected point mass and restore its ajacent point masses
				waitingForNewGrab = true;//wait for a new grab. would occur if the mouse was dragged outside of the body again. 
			}
				
			//keep the body still while being pulled.
			body.Position = position;

			//sync up with fixed update
			yield return new WaitForFixedUpdate();
		}

		//mouse button has been released!
		//release the selected point mass.
		ReleasePointMass();
		//release the body and apply force if we had a point mass selected at the time.
		ReleaseBody(!waitingForNewGrab);
	}