/// <summary> /// Finish adding vertices to this JeloClosedShape, and choose whether to convert into local space. /// JelloClosedShape.winding and JelloClosedShape.Triangles will be set. /// Make sure there are no duplicate points before calling this. use JelloShapeTools.RemoveDuplicatePoints(). /// </summary> /// <param name="recenter">whether to convert the positions of the JelloClosedShape into local space.</param> /// /// <dl class="example"><dt>Example</dt></dl> /// ~~~{.c} /// //Create a closed shape square /// JelloClosedShape shape = new JelloClosedShape(); /// /// shape.begin(); /// /// shape.addPoint(new Vector2(-1,1)); //top left /// shape.addPoint(new Vector2(-1,1)); //top right /// shape.addPoint(new Vector2(-1,1)); //bottom right /// shape.addPoint(new Vector2(-1,1)); //bottom left /// /// shape.finish(); /// ~~~ public void finish(bool recenter = true) { if (mInternalVertices != null && mInternalVertices.Length > 0) { //dont allow duplicate points mInternalVertices = JelloShapeTools.RemoveDuplicatePoints(mInternalVertices); //dont allow points outside of the perimiter for (int i = 0; i < mInternalVertices.Length; i++) { if (!JelloShapeTools.Contains(mEdgeVertices, mInternalVertices[i])) { mInternalVertices[i] = Vector2.one * Mathf.Infinity; } } //dont allow points on the perimiter. (this will also remove any null points) mInternalVertices = JelloShapeTools.RemovePointsOnPerimeter(mEdgeVertices, mInternalVertices); } mCenter = JelloShapeTools.FindCenter(mEdgeVertices); if (recenter) { // now subtract this from each element, to get proper "local" coordinates. for (int i = 0; i < mEdgeVertices.Length; i++) { mEdgeVertices[i] -= mCenter; } if (mInternalVertices != null) { for (int i = 0; i < mInternalVertices.Length; i++) { mInternalVertices[i] -= mCenter; } } } if (JelloShapeTools.HasClockwiseWinding(mEdgeVertices)) { winding = Winding.Clockwise; } else { winding = Winding.CounterClockwise; } Triangulate(); }
/// <summary> /// Initializes a new JelloClosedShape. /// Constructed from an existing list of vertices. /// </summary> /// <param name="edgeVertices">The edge vertices to construct this JelloClosedShape from.</param> /// <param name="internalVertices">The internal vertices to construct this JelloClosedShape from.</param> /// <param name="recenter">Whether to convert the vertices into local space.</param> /// /// <dl class="example"><dt>Example</dt></dl> /// ~~~{.c} /// //Create a closed shape square /// Vector2[] square = new Vector2[4]; /// square[0] = new Vector2(-1,1); //top left /// square[1] = new Vector2(1,1); //top right /// square[2] = new Vector2(1,-1); //bottom right /// square[3] = new Vector2(-1,-1); //bottom left /// /// JelloClosedShape shape = new JelloClosedShape(square); /// ~~~ public JelloClosedShape(Vector2[] edgeVertices, Vector2[] internalVertices = null, bool recenter = true) { mEdgeVertices = new Vector2[edgeVertices.Length]; for (int i = 0; i < mEdgeVertices.Length; i++) { mEdgeVertices[i] = edgeVertices[i]; } if (internalVertices != null) { bool[] validity = new bool[internalVertices.Length]; int num = 0; for (int i = 0; i < internalVertices.Length; i++) { if (JelloShapeTools.Contains(mEdgeVertices, internalVertices[i])) { validity[i] = true; num++; } else { validity[i] = false; } } mInternalVertices = new Vector2[num]; num = 0; for (int i = 0; i < internalVertices.Length; i++) { if (validity[i]) { mInternalVertices[num] = internalVertices[i]; num++; } } } else { mInternalVertices = new Vector2[0]; } finish(recenter); }
/// <summary> /// Change the positions of the vertices of the JelloClosedShape. Be sure to call JelloClosedShape.finish() after this. /// Will fail if number of vertices do not match. /// </summary> /// <param name="edgeVertices">The new edge vertex positions.</param> /// <param name="internalVertices">The new internal vertex positions.</param> /// /// <dl class="example"><dt>Example</dt></dl> /// ~~~{.c} /// //create a closed shape in the form of a square and then change it into a rectangle /// Vector2[] square; /// Vector2[] rectangle; /// /// JelloClosedShape shape = new JelloClosedShape(square); /// /// shape.changeVertices(rectangle, new Vector2[0], true); /// ~~~ public void changeVertices(Vector2[] edgeVertices, Vector2[] internalVertices) { if (edgeVertices.Length == mEdgeVertices.Length) { for (int i = 0; i < edgeVertices.Length; i++) { mEdgeVertices[i] = edgeVertices[i]; } bool[] valid = new bool[internalVertices.Length]; int num = 0; for (int i = 0; i < internalVertices.Length; i++) { if (JelloShapeTools.Contains(mEdgeVertices, internalVertices[i])) { valid[i] = true; num++; } else { valid[i] = false; } } mInternalVertices = new Vector2[num]; num = 0; for (int i = 0; i < internalVertices.Length; i++) { if (valid[i]) { mInternalVertices[num] = internalVertices[i]; num++; } } } else { Debug.LogWarning("new vertices count less than current vertices count"); } }
public void DrawPointMasses(JelloBody body, bool editable) { Handles.color = new Color(0.75f, 0.75f, 0.2f, 0.5f); for(int i = 0; i < body.Shape.EdgeVertexCount; i++) { Vector2 pos = body.transform.TransformPoint (body.Shape.EdgeVertices[i]); if(editable) { int hot = GUIUtility.hotControl; Handles.FreeMoveHandle(pos, Quaternion.identity, HandleUtility.GetHandleSize(pos) * 0.075f, Vector3.zero, Handles.DotCap); if(GUIUtility.hotControl != hot) { if(currentSubEditor == 1)//point mass editor! { subEditors[currentSubEditor].SetEditIndex(i); Repaint(); } } } else { Handles.color = new Color(0.5f, 0.5f, 0.5f, 0.5f); Handles.DotCap(3, pos, Quaternion.identity, HandleUtility.GetHandleSize(pos) * 0.075f); } } for(int i = 0; i < body.Shape.InternalVertexCount; i++) { Handles.color = new Color(0.75f, 0f, 0.75f, 0.5f); Vector2 pos = body.transform.TransformPoint (body.Shape.InternalVertices[i]); if(editable) { int hot = GUIUtility.hotControl; EditorGUI.BeginChangeCheck(); pos = Handles.FreeMoveHandle(pos, Quaternion.identity, HandleUtility.GetHandleSize(pos) * 0.075f, Vector3.zero, Handles.DotCap); if(EditorGUI.EndChangeCheck()) { if(!JelloShapeTools.Contains(body.Shape.EdgeVertices, body.transform.InverseTransformPoint(pos)) || JelloShapeTools.PointOnPerimeter(body.Shape.EdgeVertices, body.transform.InverseTransformPoint(pos))) { JelloClosedShape newShape = new JelloClosedShape(body.Shape.EdgeVertices, null, false); for(int a = 0; a < body.Shape.InternalVertexCount; a++) { //dont add this point if(a == i) continue; newShape.addInternalVertex(body.Shape.InternalVertices[a]); } newShape.finish(false); body.smartSetShape(newShape, JelloBody.ShapeSettingOptions.MovePointMasses, smartShapeSettingOptions); EditorUtility.SetDirty(body); GUIUtility.hotControl = 0; subEditors[currentSubEditor].SetEditIndex(-1); Repaint(); break; } body.Shape.changeInternalVertexPosition(i, body.transform.InverseTransformPoint(pos)); body.Shape.finish(false); EditorUtility.SetDirty(body); } if(GUIUtility.hotControl != hot && GUIUtility.hotControl != 0) { if(currentSubEditor == 1)//point mass editor! { subEditors[currentSubEditor].SetEditIndex(i + body.EdgePointMassCount); Repaint(); } } } else { Handles.color = new Color(0.5f, 0.5f, 0.5f, 0.5f); Handles.DotCap(3, pos, Quaternion.identity, HandleUtility.GetHandleSize(pos) * 0.075f); } } }
/// <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); }