public static bool PointOnPerimeter(Vector2[] shape, Vector2 point) { bool hit = false; for (int a = 0; a < shape.Length; a++) { int b = a + 1 < shape.Length ? a + 1 : 0; if (JelloVectorTools.CrossProduct(point - shape[a], shape[b] - shape[a]) == 0f) { if ( point.x > Mathf.Max(shape[b].x, shape[a].x) || point.x <Mathf.Min(shape[b].x, shape[a].x) || point.y> Mathf.Max(shape[b].y, shape[a].y) || point.y < Mathf.Min(shape[b].y, shape[a].y) ) { continue; } hit = true; } } return(hit); }
public void ChangePivot(JelloBody t) { t.polyCollider.points = JelloShapeTools.RemoveDuplicatePoints(t.polyCollider.points); t.Shape.changeVertices(t.polyCollider.points, t.Shape.InternalVertices); Vector2 diff = pivot; diff = new Vector2(diff.x / t.Scale.x, diff.y / t.Scale.y); diff = JelloVectorTools.rotateVector(diff, -t.Angle); if (t.meshLink != null) { MonoBehaviour monoBehavior; if (t.meshLink.UpdatePivotPoint(diff, out monoBehavior)) { EditorUtility.SetDirty(monoBehavior); } } for (int i = 0; i < t.Shape.VertexCount; i++) { t.Shape.setVertex(i, t.Shape.getVertex(i) - diff); } t.polyCollider.points = t.Shape.EdgeVertices; t.transform.position += (Vector3)JelloVectorTools.rotateVector(new Vector2(diff.x * t.Scale.x, diff.y * t.Scale.y), t.Angle); if (t.transform.childCount > 0) { for (int i = 0; i < t.transform.childCount; i++) { t.transform.GetChild(i).position -= (Vector3)diff; } } if (t.JointCount > 0) { for (int i = 0; i < t.JointCount; i++) { t.GetJoint(i).localAnchorA -= diff; } } if (t.AttachPointCount > 0) { for (int i = 0; i < t.AttachPointCount; i++) { t.GetAttachPoint(i).point -= diff; } } t.updateGlobalShape(true); EditorUtility.SetDirty(t); pivot = Vector2.zero; }
/// <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; } }
public void CenterPivot(JelloBody t) { Vector2 center = new Vector2(); t.polyCollider.points = JelloShapeTools.RemoveDuplicatePoints(t.polyCollider.points); t.Shape.changeVertices(t.polyCollider.points, t.Shape.InternalVertices); center = JelloShapeTools.FindCenter(t.Shape.EdgeVertices); //using vertices instead of collider.points because need of assigning entire array at once if (t.meshLink != null) { MonoBehaviour monoBehavior; if (t.meshLink.UpdatePivotPoint(center, out monoBehavior)) { EditorUtility.SetDirty(monoBehavior); } } for (int i = 0; i < t.Shape.VertexCount; i++) { t.Shape.setVertex(i, t.Shape.getVertex(i) - center); } t.polyCollider.points = t.Shape.EdgeVertices; t.transform.position += (Vector3)JelloVectorTools.rotateVector(new Vector2(center.x * t.Scale.x, center.y * t.Scale.y), t.Angle); if (t.transform.childCount > 0) { for (int i = 0; i < t.transform.childCount; i++) { t.transform.GetChild(i).position -= (Vector3)center; } } if (t.JointCount > 0) { for (int i = 0; i < t.JointCount; i++) { t.GetJoint(i).localAnchorA -= center; } } if (t.AttachPointCount > 0) { for (int i = 0; i < t.AttachPointCount; i++) { t.GetAttachPoint(i).point -= center; } } t.updateGlobalShape(true); EditorUtility.SetDirty(t); pivot = Vector2.zero; }
/// <summary> /// Initialize the MeshLink. /// </summary> /// <param name="forceUpdate">Whether to force an update to the MeshLink and MeshLink.LinkedMeshFilter.sharedMesh.</param> public override void Initialize(bool forceUpdate = false) { base.Initialize(); meshLinkType = MeshLinkType.TextureMeshLink; if(texture == null) { Debug.LogWarning("No texture found. exiting operation."); return; } if(LinkedMeshRenderer.sharedMaterial == null) LinkedMeshRenderer.sharedMaterial = new Material(Shader.Find("Unlit/Texture")); if(LinkedMeshRenderer.sharedMaterial.mainTexture == null) LinkedMeshRenderer.sharedMaterial.mainTexture = texture; if(forceUpdate || LinkedMeshFilter.sharedMesh.vertexCount != body.Shape.VertexCount) { if(LinkedMeshFilter.sharedMesh.vertexCount != body.Shape.VertexCount) LinkedMeshFilter.sharedMesh.Clear(); vertices = new Vector3[body.Shape.VertexCount]; for(int i = 0; i < vertices.Length; i++) vertices[i] = (Vector3)body.Shape.getVertex(i); Vector2[] uvPts = new Vector2[body.Shape.VertexCount]; for(int i= 0; i < uvPts.Length; i++) { uvPts[i] = body.Shape.getVertex(i) - pivotOffset; uvPts[i] = JelloVectorTools.rotateVector(uvPts[i], angle); uvPts[i] = new Vector2(uvPts[i].x / scale.x, uvPts[i].y / scale.y); uvPts[i] -= offset; } LinkedMeshFilter.sharedMesh.vertices = vertices; LinkedMeshFilter.sharedMesh.uv = uvPts; LinkedMeshFilter.sharedMesh.triangles = body.Shape.Triangles; if(CalculateNormals) LinkedMeshFilter.sharedMesh.RecalculateNormals(); if(CalculateTangents) calculateMeshTangents(); LinkedMeshFilter.sharedMesh.RecalculateBounds(); var o_112_3_636507003832678496 = LinkedMeshFilter.sharedMesh; LinkedMeshFilter.sharedMesh.MarkDynamic(); } }
/// <summary> /// Get an array of vertices by transformin the local vertices by the given position, angle, and scale. /// Transformation is applied in the following order: scale -> rotation -> position. /// </summary> /// <param name="position">The position transform vertices to.</param> /// <param name="angle">The angle (in degrees) to rotate the vertices to.</param> /// <param name="scale">The scale to transform the vertices by.</param> /// <param name="vertices">A new array of transformed vertices.</param> /// <param name="includeInternal">Whether to include JelloClosedShape.InternalVertices.</param> public void transformVertices(ref Vector2 position, ref float angle, ref Vector2 scale, ref Vector2[] vertices, bool includeInternal = false) { if (includeInternal) { if (vertices.Length != mEdgeVertices.Length + mInternalVertices.Length) { vertices = new Vector2[mEdgeVertices.Length + mInternalVertices.Length]; } for (int i = 0; i < mEdgeVertices.Length; i++) { // rotate the point, and then translate. vertices[i].x = mEdgeVertices[i].x * scale.x; vertices[i].y = mEdgeVertices[i].y * scale.y; JelloVectorTools.rotateVector(ref vertices[i], angle); vertices[i] += position; } for (int i = 0; i < mInternalVertices.Length; i++) { // rotate the point, and then translate. vertices[mEdgeVertices.Length + i].x = mInternalVertices[i].x * scale.x; vertices[mEdgeVertices.Length + i].y = mInternalVertices[i].y * scale.y; JelloVectorTools.rotateVector(ref vertices[mEdgeVertices.Length + i], angle); vertices[mEdgeVertices.Length + i] += position; } } else { if (vertices.Length != mEdgeVertices.Length) { vertices = new Vector2[mEdgeVertices.Length]; } for (int i = 0; i < mEdgeVertices.Length; i++) { // rotate the point, and then translate. vertices[i].x = mEdgeVertices[i].x * scale.x; vertices[i].y = mEdgeVertices[i].y * scale.y; JelloVectorTools.rotateVector(ref vertices[i], angle); vertices[i] += position; } } }
/// <summary> /// Removes the points on perimeter. /// </summary> /// <returns>The points on perimeter.</returns> /// <param name="shape">Shape.</param> /// <param name="points">Points.</param> public static Vector2[] RemovePointsOnPerimeter(Vector2[] shape, Vector2[] points) { int num = 0; for (int i = 0; i < points.Length; i++) { if (points[i].x == Mathf.Infinity) { num++; continue; } for (int a = 0; a < shape.Length; a++) { int b = a + 1 < shape.Length ? a + 1 : 0; if (JelloVectorTools.CrossProduct(points[i] - shape[a], shape[b] - shape[a]) == 0f) { if ( points[i].x > Mathf.Max(shape[b].x, shape[a].x) || points[i].x <Mathf.Min(shape[b].x, shape[a].x) || points[i].y> Mathf.Max(shape[b].y, shape[a].y) || points[i].y < Mathf.Min(shape[b].y, shape[a].y) ) { continue; } points[i] = Vector2.one * Mathf.Infinity; num++; break; } } } Vector2[] returnPoints = new Vector2[points.Length - num]; num = 0; for (int i = 0; i < points.Length; i++) { if (points[i].x != Mathf.Infinity) { returnPoints[num] = points[i]; num++; } } return(returnPoints); }
/// <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> /// Get an array of vertices by transformin the local vertices by the given position, angle, and scale. /// Transformation is applied in the following order: scale -> rotation -> position. /// </summary> /// <param name="position">The position transform vertices to.</param> /// <param name="angle">The angle (in degrees) to rotate the vertices to.</param> /// <param name="scale">The scale to transform the vertices by.</param> /// <param name="includeInternal">Whether to include JelloClosedShape.InternalVertices.</param> /// <returns>A new array of transformed vertices.</returns> public Vector2[] transformVertices(Vector2 position, float angle, Vector2 scale, bool includeInternal = false) { Vector2[] ret; if(includeInternal) { ret = new Vector2[mEdgeVertices.Length + mInternalVertices.Length]; for (int i = 0; i < mEdgeVertices.Length; i++) { // rotate the point, and then translate. ret[i].x = mEdgeVertices[i].x * scale.x; ret[i].y = mEdgeVertices[i].y * scale.y; JelloVectorTools.rotateVector(ref ret[i], angle); ret[i] += position; } for(int i = 0; i < mInternalVertices.Length; i++) { ret[mEdgeVertices.Length + i].x = mInternalVertices[i].x * scale.x; ret[mEdgeVertices.Length + i].y = mInternalVertices[i].y * scale.y; JelloVectorTools.rotateVector(ref ret[mEdgeVertices.Length + i], angle); ret[mEdgeVertices.Length + i] += position; } } else { ret = new Vector2[mEdgeVertices.Length]; for (int i = 0; i < mEdgeVertices.Length; i++) { // rotate the point, and then translate. ret[i].x = mEdgeVertices[i].x * scale.x; ret[i].y = mEdgeVertices[i].y * scale.y; JelloVectorTools.rotateVector(ref ret[i], angle); ret[i] += position; } } return ret; }
/// <summary> /// Find the closest edge on shape to a given point. /// </summary> /// <param name="point">The point to find the closest edge to.</param> /// <param name="shape">The shape to test against.</param> /// <returns>The closest edge on shape as indices of the shape.</returns> public static int[] FindClosestEdgeOnShape(Vector2 point, Vector2[] shape) { float distance = Mathf.Infinity; int[] indices = new int[2]; for(int i = 0; i < shape.Length; i++) { int next = i + 1 < shape.Length ? i + 1 : 0; Vector2 hit; float dist = JelloVectorTools.getClosestPointOnSegmentSquared(point, shape[i], shape[next], out hit); if(dist < distance) { distance = dist; indices[0] = i; indices[1] = next; } } return indices; }
/// <summary> /// Initialize the SpriteMeshLink. /// </summary> /// <param name="forceUpdate">Whether to force an update to the MeshLink and MeshLink.LinkedMeshFilter.sharedMesh.</param> public override void Initialize(bool forceUpdate = false) { base.Initialize(forceUpdate); meshLinkType = MeshLinkType.SpriteMeshLink; if(sprites == null || sprites.Length == 0 || texture == null) { Debug.LogWarning("No sprites and/or texture found. exiting operation."); return; } if(LinkedMeshRenderer.sharedMaterial == null) LinkedMeshRenderer.sharedMaterial = new Material(Shader.Find("Sprites/Default")); if(LinkedMeshRenderer.sharedMaterial.mainTexture == null) LinkedMeshRenderer.sharedMaterial.mainTexture = texture; if(selectedSprite < 0 || selectedSprite >= sprites.Length) selectedSprite = 0; Sprite sprite = sprites[selectedSprite]; if(sprite == null) return; if(forceUpdate || LinkedMeshFilter.sharedMesh.vertexCount != body.Shape.VertexCount) { if(LinkedMeshFilter.sharedMesh.vertexCount != body.Shape.VertexCount) LinkedMeshFilter.sharedMesh.Clear(); vertices = new Vector3[body.Shape.VertexCount]; for(int i = 0; i < vertices.Length; i++) vertices[i] = (Vector3)body.Shape.getVertex(i); Vector2 length = new Vector2(sprite.texture.width, sprite.texture.height); float pixelsToUnits = sprite.textureRect.width / sprite.bounds.size.x; length /= pixelsToUnits; Vector2[] uvPts = new Vector2[body.Shape.VertexCount]; for(int i= 0; i < uvPts.Length; i++) { uvPts[i] = body.Shape.getVertex(i) - pivotOffset; uvPts[i] = JelloVectorTools.rotateVector(uvPts[i], angle); uvPts[i] = new Vector2(uvPts[i].x / scale.x, uvPts[i].y / scale.y); uvPts[i] = new Vector2(0.5f + uvPts[i].x / length.x, 0.5f + uvPts[i].y / length.y); uvPts[i] -= offset; } LinkedMeshFilter.sharedMesh.vertices = vertices; LinkedMeshFilter.sharedMesh.uv = uvPts; LinkedMeshFilter.sharedMesh.triangles = body.Shape.Triangles; LinkedMeshFilter.sharedMesh.colors = null; if(CalculateNormals) LinkedMeshFilter.sharedMesh.RecalculateNormals(); if(CalculateTangents) calculateMeshTangents(); LinkedMeshFilter.sharedMesh.RecalculateBounds(); var o_174_3_636386045451834383 = LinkedMeshFilter.sharedMesh; LinkedMeshFilter.sharedMesh.MarkDynamic(); } }
/// <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); }
/// <summary> /// Rebuild this JelloAttachPoint. /// </summary> /// <param name="attachPoint">The point (local to the JelloAttachPoint.body) at which to attach the JelloAttachPoint.AttachedTransform.</param> /// <param name="jelloBody">The JelloBody to to be attached to.</param> /// <param name="useBaseShape">Whether to use the JelloBody.Shape positions (instead of JelloPointMass.Position) when building the JelloAttachPoint.</param> /// <param name="numLegs">Number of JelloPointMass objects to use as "legs" (use 1, 2, or 3).</param> public void Rebuild(Vector2 attachPoint, JelloBody jelloBody, bool useBaseShape = true, int numLegs = 0) { body = jelloBody; transform = body.transform; if (numLegs != 0) { if (numLegs < 0) { numLegs = 1; } if (numLegs > 3) { numLegs = 3; } affectedIndices = new int[numLegs]; } else if (affectedIndices == null || affectedIndices.Length == 0 || affectedIndices.Length > 3) { affectedIndices = new int[2]; //default to 3? } Vector2[] shape = new Vector2[body.Shape.VertexCount]; if (useBaseShape) { for (int i = 0; i < shape.Length; i++) { shape[i] = body.Shape.getVertex(i); } } else { attachPoint = transform.TransformPoint(attachPoint); for (int i = 0; i < shape.Length; i++) { shape[i] = body.getPointMass(i).Position; } } if (affectedIndices.Length == 1) { affectedIndices = JelloShapeTools.GetClosestIndices(attachPoint, shape, 1); scalars = new float[1]; scalars[0] = 1f; } else if (affectedIndices.Length == 2) { Vector2 hit; affectedIndices = JelloShapeTools.FindClosestEdgeOnShape(attachPoint, shape); scalars = new float[2]; JelloVectorTools.getClosestPointOnSegmentSquared(attachPoint, shape[affectedIndices[0]], shape[affectedIndices[1]], out hit, out scalars[1]); scalars[0] = 1 - scalars[1]; } else if (affectedIndices.Length == 3) { Vector2[] shapePerimeter = new Vector2[body.EdgePointMassCount]; if (useBaseShape) { shapePerimeter = body.Shape.EdgeVertices; } else { for (int i = 0; i < shapePerimeter.Length; i++) { shapePerimeter[i] = body.getEdgePointMass(i).Position; } } affectedIndices = JelloShapeTools.FindContainingTriangle(attachPoint, shape, shapePerimeter, body.Shape.Triangles, out scalars); } point = Vector2.zero; for (int i = 0; i < affectedIndices.Length; i++) { point += shape[affectedIndices[i]] * scalars[i]; } if (!useBaseShape) { point = transform.InverseTransformPoint(point); } if (mAttachedTransform != null) { Vector3 newPos = transform.TransformPoint(point); newPos.z = mAttachedTransform.position.z; mAttachedTransform.position = newPos; } }
/// <summary> /// Merge two given shapes. /// Does not support holes. Only call this if you already know the to shapes overlap. /// </summary> /// <param name="shapeA">The first shape.</param> /// <param name="shapeB">The second shape.</param> /// <returns>The silhouette of the overlapping shapes.</returns> /// /// <dl class="example"><dt>Example</dt></dl> /// ~~~{.c} /// //combine terrain shape and tree shape into a single shape and draw a glow around the resulting shape /// Vector2[] terrainShape, treeShape; /// /// if(JelloShapeTools.Intersect(terrainShape, treeShape)) /// { /// //only do this if you already know that the shapes overlap /// Vector2[] shape = ShapeTools.Merge(terrainShape, treeShape); /// /// DrawShapeGlow(shapes); /// } /// ~~~ public static Vector2[] Merge(Vector2[] shapeA, Vector2[] shapeB) { //find the start point (bottom most point. if multiple bottom most, then left most) Vector2 startPoint = shapeA[0]; int index = 0; //index of the shape that the startPoint represents //start at 1 becasue we already assigned it to 0 for (int i = 1; i < shapeA.Length; i++) { if (shapeA[i].y < startPoint.y) { startPoint = shapeA[i]; index = i; } else if (shapeA[i].y == startPoint.y && shapeA[i].x < startPoint.x) { startPoint = shapeA[i]; index = i; } } bool startWithShapeB = false; for (int i = 0; i < shapeB.Length; i++) { if (shapeB[i].y < startPoint.y) { startPoint = shapeB[i]; startWithShapeB = true; } else if (shapeB[i].y == startPoint.y && shapeB[i].x < startPoint.x) { startPoint = shapeB[i]; startWithShapeB = true; } } Vector2[][] shapes = new Vector2[2][]; if (!startWithShapeB) { shapes[0] = shapeA; shapes[1] = shapeB; } else { shapes[0] = shapeB; shapes[1] = shapeA; } List <Vector2> newShape = new List <Vector2>(); int iterations = 0; bool startWithHit = false; Vector2 hitPointToAdd = new Vector2(); while (iterations < 1000) { float dist = Mathf.Infinity; bool found = false; int edgenum = 0; int numHits = 0; for (int b = index; b < shapes[0].Length + index; b++) { int i = b < shapes[0].Length ? b : b - shapes[0].Length; Vector2 thisPos1 = startWithHit ? hitPointToAdd : shapes[0][i]; Vector2 nextPos1 = shapes[0][i + 1 < shapes[0].Length ? i + 1 : 0]; if (newShape.Count > 0 && thisPos1 == newShape[0]) { return(newShape.ToArray()); } if (!startWithHit) { newShape.Add(thisPos1); } startWithHit = false; hitPointToAdd = Vector2.zero; for (int a = 0; a < shapes[1].Length; a++) { Vector2 thisPos2 = shapes[1][a]; Vector2 nextPos2 = shapes[1][a + 1 < shapes[1].Length ? a + 1 : 0]; Vector2 hitPt = new Vector2(); float distToA = 0f; float distToB = 0f; //if(VectorTools.lineIntersect(thisPos1, nextPos1, thisPos2, nextPos2, out hitPt, out distToA, out distToB)) if (JelloVectorTools.LineSegmentsIntersect(thisPos1, nextPos1, thisPos2, nextPos2, out hitPt, out distToA, out distToB)) { bool disregard = false; if (newShape.Count > 0 && hitPt == newShape[newShape.Count - 1]) { disregard = true; } if (!disregard) { found = true; numHits++; if (distToA < dist) { dist = distToA; hitPointToAdd = hitPt; edgenum = a; } } } } if (found) { startWithHit = true; newShape.Add(hitPointToAdd); //reverse arays and break, set index to new edgenum index = edgenum; Vector2[] temp1 = shapes[0]; Vector2[] temp2 = shapes[1]; shapes[0] = temp2; shapes[1] = temp1; break; } } //this could only occur if the other shape is wholy inside this one. if (numHits == 0) { return(shapes[0]); } iterations++; } return(newShape.ToArray()); }
/// <summary> /// Rebuild this JelloAttachPoint. /// </summary> /// <param name="attachPoint">The point (local to the JelloAttachPoint.body) at which to attach the JelloAttachPoint.AttachedTransform.</param> /// <param name="jelloBody">The JelloBody to to be attached to.</param> /// <param name="indices">The JelloPointMass Indices. Should have a length of 1, 2, or 3.</param> /// <param name="useBaseShape">Whether to use the JelloBody.Shape positions (instead of JelloPointMass.Position) when building the JelloAttachPoint.</param> public void Rebuild(Vector2 attachPoint, JelloBody jelloBody, int[] indices, bool useBaseShape = true) { body = jelloBody; transform = body.transform; if (indices == null) { if (affectedIndices == null) { Rebuild(attachPoint, jelloBody, useBaseShape); return; } else { indices = affectedIndices; } } else if (indices.Length > 4) { affectedIndices = new int[3]; for (int i = 0; i < 3; i++) { affectedIndices[i] = indices[i]; } } else { affectedIndices = indices; } Vector2[] verts = new Vector2[3]; if (useBaseShape) { for (int i = 0; i < affectedIndices.Length; i++) { verts[i] = body.Shape.getVertex(affectedIndices[i]); } } else { attachPoint = transform.TransformPoint(attachPoint); for (int i = 0; i < affectedIndices.Length; i++) { verts[i] = body.getPointMass(affectedIndices[i]).Position; } } if (affectedIndices.Length == 1) { scalars = new float[1]; scalars[0] = 1f; } else if (affectedIndices.Length == 2) { Vector2 hit; scalars = new float[2]; JelloVectorTools.getClosestPointOnSegmentSquared(attachPoint, verts[0], verts[1], out hit, out scalars[1]); scalars[0] = 1 - scalars[1]; } else if (affectedIndices.Length == 3) { scalars = JelloShapeTools.GetBarycentricCoords(attachPoint, verts); } //throw into for loop... point = Vector2.zero; for (int i = 0; i < affectedIndices.Length; i++) { point += scalars[i] * verts[i]; } if (!useBaseShape) { point = transform.InverseTransformPoint(point); } if (mAttachedTransform != null) { Vector3 newPos = transform.TransformPoint(point); newPos.z = mAttachedTransform.position.z; mAttachedTransform.position = newPos; } }
/// <summary> /// Solves the JelloJoint and applies velocities to each Rigidbody2D / JelloBody involved. /// This is called regularly by the simulation and should not need to be called by the user. /// </summary> /// <param name="deltaTime">The amount of time elapsed.</param> public void Solve(float deltaTime) { if(mTransformA == null && mTransformB == null) { destroyed = true; return; } Vector2 vap = Vector2.zero; Vector2 vbp = Vector2.zero; float aMassSum = Mathf.Infinity; float bMassSum = Mathf.Infinity; float invma = 0f; float invmb = 0f; Vector2 posA = Vector2.zero; Vector2 posB = Vector2.zero; Vector2 directionA = Vector2.zero; Vector2 directionB = Vector2.zero; if(TransformA != null) { if(bodyA != null) { for(int i = 0; i < affectedIndicesA.Length; i++) { vap += bodyA.getPointMass(affectedIndicesA[i]).velocity * scalarsA[i]; invma += bodyA.getPointMass(affectedIndicesA[i]).InverseMass * scalarsA[i]; aMassSum += bodyA.getPointMass(affectedIndicesA[i]).Mass * scalarsA[i]; posA += bodyA.getPointMass(affectedIndicesA[i]).Position * scalarsA[i]; } } else if (rigidbodyA != null) { posA = mTransformA.TransformPoint(localAnchorA); directionA = posA - (Vector2)mTransformA.position; vap = rigidbodyA.velocity + rigidbodyA.angularVelocity * Mathf.Deg2Rad * new Vector2(-directionA.y, directionA.x); if(rigidbodyA.mass != 0f && !rigidbodyA.isKinematic) { invma = 1f / rigidbodyA.mass; aMassSum = rigidbodyA.mass; } } else { posA = mTransformA.TransformPoint(localAnchorA); directionA = posA - (Vector2)mTransformA.position; } } else if(TransformB != null) { posA = globalAnchorA; } else { return; } if(mTransformB != null) { if(bodyB != null) { for(int i = 0; i < affectedIndicesB.Length; i++) { vbp += bodyB.getPointMass(affectedIndicesB[i]).velocity * scalarsB[i]; invmb += bodyB.getPointMass(affectedIndicesB[i]).InverseMass * scalarsB[i]; bMassSum += bodyB.getPointMass(affectedIndicesB[i]).Mass * scalarsB[i]; posB += bodyB.getPointMass(affectedIndicesB[i]).Position * scalarsB[i]; } } else if (rigidbodyB != null) { posB = TransformB.TransformPoint(localAnchorB); directionB = posB - (Vector2)mTransformB.position; vbp = rigidbodyB.velocity + rigidbodyB.angularVelocity * Mathf.Deg2Rad * new Vector2(-directionB.y, directionB.x); if(rigidbodyB.mass != 0f && !rigidbodyB.isKinematic) { invmb = 1f / rigidbodyB.mass; bMassSum = rigidbodyB.mass; } } else { posB = mTransformB.TransformPoint(localAnchorB); directionB = posB - (Vector2)mTransformB.position; } } else if(mTransformA != null) { posB = globalAnchorB; } else { return; } //////////////////////////////////////////////////////// //Debug.DrawLine(posA, posB, Color.magenta); Vector2 normal = posA - posB; //this isnt normalized... float distance = normal.magnitude; if(distance != 0f) normal /= distance; Vector2 vab = vap - vbp; float invMomentInertiaA = aMassSum == Mathf.Infinity ? 0f : 1f; float invMomentInertiaB = bMassSum == Mathf.Infinity ? 0f : 1f; float denomA = 0f; float denomB = 0f; if(bodyA == null && aMassSum != Mathf.Infinity) { denomA = JelloVectorTools.CrossProduct(directionA, normal); denomA *= denomA * invMomentInertiaA; } if(bodyB == null && bMassSum != Mathf.Infinity) { denomB = JelloVectorTools.CrossProduct(directionB, normal); denomB *= denomB * invMomentInertiaB; } //constraint impulse scalar float j = -Vector2.Dot (vab, normal); j -= distance / deltaTime;//TODO assign all distance to bodies based on mass.......? bool destroy = false; if(breakable && Mathf.Abs(j) > breakVelocity) destroy = true; float denom = invma + invmb + denomA + denomB; if(denom == 0f) denom = 1f; j /= denom; //////////////////////////////////////////////////////// if(bodyA != null) { for(int i = 0; i < affectedIndicesA.Length; i++) bodyA.getPointMass(affectedIndicesA[i]).velocity += j * normal * invma * scalarsA[i]; } else if (rigidbodyA != null && !rigidbodyA.isKinematic) { rigidbodyA.velocity += j * normal * invma / (numSimilar > 0 ? numSimilar : 1f); rigidbodyA.angularVelocity += JelloVectorTools.CrossProduct(directionA, j * normal) * Mathf.Rad2Deg / (numSimilar > 0 ? numSimilar : 1f); } if(bodyB != null) { for(int i = 0; i < affectedIndicesB.Length; i++) bodyB.getPointMass(affectedIndicesB[i]).velocity -= j * normal * invmb * scalarsB[i]; } else if (rigidbodyB != null && !rigidbodyB.isKinematic) { rigidbodyB.velocity -= j * normal * invmb / (numSimilar > 0 ? numSimilar : 1f); rigidbodyB.angularVelocity -= JelloVectorTools.CrossProduct(directionB, j * normal) * Mathf.Rad2Deg / (numSimilar > 0 ? numSimilar : 1f); } if(destroy) Destroy(); }
/// <summary> /// Set up the anchor. /// </summary> /// <returns>The anchor's position.</returns> /// <param name="xform">The Transform of the anchor.</param> /// <param name="anchor">The anchor point, local to the given Transform.</param> /// <param name="isAnchorA">Whether to set up the first anchor instead of the second.</param> /// <param name="useBaseShape">Whether to use JelloBody.Shape instead of its JelloPointMass objects. Has no effect if no JelloBody is attached to the Transform.</param> /// <param name="numPointsAffected">The number of PointMasses affected / affecting this anchor. Has no effect if no JelloBody is attached to the Transform.</param> public Vector2 SetupAnchor(Transform xform, Vector2 anchor, bool isAnchorA, bool useBaseShape, int numPointsAffected = 0) { if(isAnchorA) TransformA = xform; else TransformB = xform; if(xform == null) { if(isAnchorA) { affectedIndicesA = null; scalarsA = null; if(TransformB != null) { globalAnchorA = TransformB.TransformPoint(GetAnchorPointB(useBaseShape)); return globalAnchorA; } else { return Vector2.zero; } } else { affectedIndicesB = null; scalarsB = null; if(TransformA != null) { globalAnchorB = TransformA.TransformPoint(GetAnchorPointA(useBaseShape)); return globalAnchorB; } else { return Vector2.zero; } } //return Vector2.zero; } Vector2 returnPosition = anchor; if(numPointsAffected < 1) numPointsAffected = 2; else if(numPointsAffected > 3) numPointsAffected = 3; if(isAnchorA) { localAnchorA = anchor; if(bodyA != null) { Vector2[] shape = new Vector2[bodyA.Shape.VertexCount]; if(useBaseShape) //TODO tighten this up a bit { for(int i = 0; i < shape.Length; i++) shape[i] = bodyA.Shape.getVertex(i); } else { for(int i = 0; i < bodyA.PointMassCount; i++) shape[i] = bodyA.getPointMass(i).Position; } Vector2 point = localAnchorA; if(!useBaseShape) point = xform.TransformPoint(point); if(numPointsAffected == 1) { affectedIndicesA = JelloShapeTools.GetClosestIndices(point, shape, 1); scalarsA = new float[1]; scalarsA[0] = 1f; returnPosition = shape[affectedIndicesA[0]]; } else if(numPointsAffected == 2) { Vector2 hit; affectedIndicesA = JelloShapeTools.FindClosestEdgeOnShape(point, shape); scalarsA = new float[2]; JelloVectorTools.getClosestPointOnSegmentSquared (point, shape[affectedIndicesA[0]], shape[affectedIndicesA[1]], out hit, out scalarsA[1]); scalarsA[0] = 1 - scalarsA[1]; returnPosition = shape[affectedIndicesA[0]] * scalarsA[0] + shape[affectedIndicesA[1]] * scalarsA[1]; } else if(numPointsAffected == 3) { Vector2[] shapePerimeter = new Vector2[bodyA.EdgePointMassCount]; if(useBaseShape) { shapePerimeter = bodyA.Shape.EdgeVertices; } else { for(int i = 0; i < shapePerimeter.Length; i++) shapePerimeter[i] = bodyA.getEdgePointMass(i).Position; } affectedIndicesA = JelloShapeTools.FindContainingTriangle(point, shape, shapePerimeter, bodyA.Shape.Triangles, out scalarsA); returnPosition = shape[affectedIndicesA[0]] * scalarsA[0] + shape[affectedIndicesA[1]] * scalarsA[1] + shape[affectedIndicesA[2]] * scalarsA[2]; } if(!useBaseShape) returnPosition = mTransformA.InverseTransformPoint(returnPosition); } } else { localAnchorB = anchor; if(bodyB != null) { Vector2[] shape = new Vector2[bodyB.Shape.VertexCount]; if(useBaseShape) { for(int i = 0; i < shape.Length; i++) shape[i] = bodyB.Shape.getVertex(i); } else { for(int i = 0; i < bodyB.PointMassCount; i++) shape[i] = bodyB.getPointMass(i).Position; } Vector2 point = localAnchorB; if(!useBaseShape) point = xform.TransformPoint(point); if(numPointsAffected == 1) { affectedIndicesB = JelloShapeTools.GetClosestIndices(point, shape, 1); scalarsB = new float[1]; scalarsB[0] = 1f; returnPosition = shape[affectedIndicesB[0]] * scalarsB[0]; } else if(numPointsAffected == 2) { Vector2 hit; // affectedIndicesB = JelloShapeTools.GetClosestIndices(point, shape, 2); affectedIndicesB = JelloShapeTools.FindClosestEdgeOnShape(point, shape); scalarsB = new float[2]; JelloVectorTools.getClosestPointOnSegmentSquared (point, shape[affectedIndicesB[0]], shape[affectedIndicesB[1]], out hit, out scalarsB[1]); scalarsB[0] = 1 - scalarsB[1]; returnPosition = shape[affectedIndicesB[0]] * scalarsB[0] + shape[affectedIndicesB[1]] * scalarsB[1]; } else if(numPointsAffected == 3) { Vector2[] shapePerimeter = new Vector2[bodyB.EdgePointMassCount]; if(useBaseShape) { shapePerimeter = bodyB.Shape.EdgeVertices; } else { for(int i = 0; i < shapePerimeter.Length; i++) shapePerimeter[i] = bodyB.getEdgePointMass(i).Position; } affectedIndicesB = JelloShapeTools.FindContainingTriangle(point, shape, shapePerimeter, bodyB.Shape.Triangles, out scalarsB); returnPosition = shape[affectedIndicesB[0]] * scalarsB[0] + shape[affectedIndicesB[1]] * scalarsB[1] + shape[affectedIndicesB[2]] * scalarsB[2]; } if(!useBaseShape) returnPosition = mTransformB.InverseTransformPoint(returnPosition);} } return returnPosition; }
//anchor is local //vertices are local //have vector2 return? /// <summary> /// Rebuilds the anchor. /// </summary> /// <param name="anchor">The anchor (In local space).</param> /// <param name="isAnchorA">Whether to rebuild the first anchor as opposed to rebuilding the second anchor.</param> /// <param name="useBaseShape">Whether use JelloBody.Shape instead of JelloPointMass.Position. Has no effect if Transform has no JelloBody attached.</param> /// <param name="affectedIndices">The indices of the affected / affecting JelloPointMass objects. Has no effect if Transform has no JelloBody attached.</param> /// <param name="affectedVertices">The positions (in local space) of the affected / affecting JelloPointMass objects. Has no effect if Transform has no JelloBody attached.</param> public void RebuildAnchor(Vector2 anchor, bool isAnchorA, bool useBaseShape, int[] affectedIndices = null, Vector2[] affectedVertices = null) { //Vector2 point; if(isAnchorA) { localAnchorA = anchor; if(mTransformA == null) return; if(bodyA != null) { if(affectedIndices == null) affectedIndices = affectedIndicesA; else affectedIndicesA = affectedIndices; if(affectedVertices == null)//grab from point mass positions? { if(useBaseShape) { affectedVertices = new Vector2[affectedIndicesA.Length]; for(int i = 0; i < affectedIndicesA.Length; i++) affectedVertices[i] = bodyA.Shape.getVertex(affectedIndicesA[i]); } else { affectedVertices = new Vector2[affectedIndicesA.Length]; for(int i = 0; i < affectedIndicesA.Length; i++) affectedVertices[i] = bodyA.getPointMass(affectedIndicesA[i]).LocalPosition; } } if(affectedIndices != null) { if(affectedIndices.Length == 1) { scalarsA = new float[1]; scalarsA[0] = 1f; } else if(affectedIndices.Length == 2) { Vector2 hit; scalarsA = new float[2]; JelloVectorTools.getClosestPointOnSegmentSquared (localAnchorA, affectedVertices[0], affectedVertices[1], out hit, out scalarsA[1]); scalarsA[0] = 1 - scalarsA[1]; } else if(affectedIndices.Length == 3) { scalarsA = JelloShapeTools.GetBarycentricCoords(localAnchorA, affectedVertices); } } } } else { localAnchorB = anchor; if(mTransformB == null) return; if(bodyB != null) { if(affectedIndices == null) affectedIndices = affectedIndicesB; else affectedIndicesB = affectedIndices; if(affectedVertices == null)//grab from point mass positions? { if(useBaseShape) { affectedVertices = new Vector2[affectedIndicesB.Length]; for(int i = 0; i < affectedIndicesB.Length; i++) affectedVertices[i] = bodyB.Shape.getVertex(affectedIndicesB[i]); } else { affectedVertices = new Vector2[affectedIndicesB.Length]; for(int i = 0; i < affectedIndicesB.Length; i++) affectedVertices[i] = bodyB.getPointMass(affectedIndicesB[i]).LocalPosition; } } if(affectedIndices != null) { if(affectedIndices.Length == 1) { scalarsB = new float[1]; scalarsB[0] = 1f; } else if(affectedIndices.Length == 2) { Vector2 hit; scalarsB = new float[2]; JelloVectorTools.getClosestPointOnSegmentSquared (localAnchorB, affectedVertices[0], affectedVertices[1], out hit, out scalarsB[1]); scalarsB[0] = 1 - scalarsB[1]; } else if(affectedIndices.Length == 3) { scalarsB = JelloShapeTools.GetBarycentricCoords(localAnchorB, affectedVertices); } } } } }