/** Unwraps the funnel portals from 3D space to 2D space. * The result is stored in the \a left and \a right arrays which must have lengths equal to the funnel.left and funnel.right lists. * * The input is a funnel like in the image below. It may be rotated and twisted. * \shadowimage{funnel_unwrap_input.png} * The output will be a funnel in 2D space like in the image below. All twists and bends will have been straightened out. * \shadowimage{funnel_unwrap_output.png} * * \see Calculate(FunnelPortals,bool,bool) */ public static void Unwrap(FunnelPortals funnel, Vector2[] left, Vector2[] right) { var normal = Vector3.Cross(funnel.right[1] - funnel.left[0], funnel.left[1] - funnel.left[0]); left[0] = right[0] = Vector2.zero; var portalLeft = funnel.left[1]; var portalRight = funnel.right[1]; var prevPoint = funnel.left[0]; // The code below is equivalent to this matrix (but a lot faster) // This represents a rotation around a line in 3D space // Matrix4x4 m = Matrix4x4.TRS(Vector3.zero, Quaternion.FromToRotation(normal, Vector3.forward), Vector3.one) * Matrix4x4.TRS(-funnel.right[0], Quaternion.identity, Vector3.one); Quaternion mRot = Quaternion.FromToRotation(normal, Vector3.forward); Vector3 mOffset = mRot * (-funnel.right[0]); for (int i = 1; i < funnel.left.Count; i++) { if (UnwrapHelper(portalLeft, portalRight, prevPoint, funnel.left[i], ref mRot, ref mOffset)) { prevPoint = portalLeft; portalLeft = funnel.left[i]; } left[i] = mRot * funnel.left[i] + mOffset; if (UnwrapHelper(portalLeft, portalRight, prevPoint, funnel.right[i], ref mRot, ref mOffset)) { prevPoint = portalRight; portalRight = funnel.right[i]; } right[i] = mRot * funnel.right[i] + mOffset; } }
public static void ShrinkPortals(FunnelPortals portals, float shrink) { if (shrink <= 0.00001f) { return; } for (int i = 0; i < portals.left.Count; i++) { var left = portals.left[i]; var right = portals.right[i]; var length = (left - right).magnitude; if (length > 0) { float s = Mathf.Min(shrink / length, 0.4f); //Good Game /*portals.left[i] = Vector3.Lerp(left, right, s); * portals.right[i] = Vector3.Lerp(left, right, 1 - s);*/ portals.left[i] = VInt3.Lerp(left, right, s); portals.right[i] = VInt3.Lerp(left, right, 1 - s); } } }
/** Unwraps the funnel portals from 3D space to 2D space. * The result is stored in the \a left and \a right arrays which must be at least as large as the funnel.left and funnel.right lists. * * The input is a funnel like in the image below. It may be rotated and twisted. * \shadowimage{funnel_unwrap_input.png} * The output will be a funnel in 2D space like in the image below. All twists and bends will have been straightened out. * \shadowimage{funnel_unwrap_output.png} * * \see #Calculate(FunnelPortals,bool,bool) */ //Good Game //public static void Unwrap (FunnelPortals funnel, Vector2[] left, Vector2[] right) { public static void Unwrap(FunnelPortals funnel, VInt2[] left, VInt2[] right) { int startingIndex = 1; //Good Game //var normal = Vector3.Cross(funnel.right[1] - funnel.left[0], funnel.left[1] - funnel.left[0]); var normal = VInt3.Cross(funnel.right[1] - funnel.left[0], funnel.left[1] - funnel.left[0]); // This handles the case when the starting point is colinear with the first portal. // Note that left.Length is only guaranteed to be at least as large as funnel.left.Count, it may be larger. //Good Game //while (normal.sqrMagnitude <= 0.00000001f && startingIndex + 1 < funnel.left.Count) { while (normal.sqrMagnitude <= 0.000001 && startingIndex + 1 < funnel.left.Count) { startingIndex++; //Good Game //normal = Vector3.Cross(funnel.right[startingIndex] - funnel.left[0], funnel.left[startingIndex] - funnel.left[0]); normal = VInt3.Cross(funnel.right[startingIndex] - funnel.left[0], funnel.left[startingIndex] - funnel.left[0]); } left[0] = right[0] = VInt2.zero; var portalLeft = funnel.left[1]; var portalRight = funnel.right[1]; var prevPoint = funnel.left[0]; // The code below is equivalent to this matrix (but a lot faster) // This represents a rotation around a line in 3D space // Matrix4x4 m = Matrix4x4.TRS(Vector3.zero, Quaternion.FromToRotation(normal, Vector3.forward), Vector3.one) * Matrix4x4.TRS(-funnel.right[0], Quaternion.identity, Vector3.one); Quaternion mRot = Quaternion.FromToRotation((Vector3)normal, Vector3.forward); Vector3 mOffset = mRot * (-(Vector3)funnel.right[0]); for (int i = 1; i < funnel.left.Count; i++) { //Good Game //if (UnwrapHelper((Vector3)portalLeft, (Vector3)portalRight, (Vector3)prevPoint, (Vector3)funnel.left[i], ref mRot, ref mOffset)) { if (UnwrapHelper((Vector3)portalLeft, (Vector3)portalRight, (Vector3)prevPoint, (Vector3)funnel.left[i], ref mRot, ref mOffset)) { prevPoint = portalLeft; portalLeft = funnel.left[i]; } left[i] = VInt2.FromInt3XZ((VInt3)(mRot * (Vector3)funnel.left[i] + mOffset)); //Good Game //if (UnwrapHelper(portalLeft, portalRight, prevPoint, funnel.right[i], ref mRot, ref mOffset)) { if (UnwrapHelper((Vector3)portalLeft, (Vector3)portalRight, (Vector3)prevPoint, (Vector3)funnel.right[i], ref mRot, ref mOffset)) { prevPoint = portalRight; portalRight = funnel.right[i]; } right[i] = VInt2.FromInt3XZ((VInt3)(mRot * (Vector3)funnel.right[i] + mOffset)); } }
/// <summary> /// Calculate the shortest path through the funnel. /// /// If the unwrap option is disabled the funnel will simply be projected onto the XZ plane. /// If the unwrap option is enabled then the funnel may be oriented arbitrarily and may have twists and bends. /// This makes it possible to support the funnel algorithm in XY space as well as in more complicated cases, such /// as on curved worlds. /// [Open online documentation to see images] /// /// [Open online documentation to see images] /// /// See: Unwrap /// </summary> /// <param name="funnel">The portals of the funnel. The first and last vertices portals must be single points (so for example left[0] == right[0]).</param> /// <param name="unwrap">Determines if twists and bends should be straightened out before running the funnel algorithm.</param> /// <param name="splitAtEveryPortal">If true, then a vertex will be inserted every time the path crosses a portal /// instead of only at the corners of the path. The result will have exactly one vertex per portal if this is enabled. /// This may introduce vertices with the same position in the output (esp. in corners where many portals meet).</param> public static List <Vector3> Calculate(FunnelPortals funnel, bool unwrap, bool splitAtEveryPortal) { if (funnel.left.Count != funnel.right.Count) { throw new System.ArgumentException("funnel.left.Count != funnel.right.Count"); } // Get arrays at least as large as the number of portals var leftArr = ArrayPool <Vector2> .Claim(funnel.left.Count); var rightArr = ArrayPool <Vector2> .Claim(funnel.left.Count); if (unwrap) { Unwrap(funnel, leftArr, rightArr); } else { // Copy to arrays for (int i = 0; i < funnel.left.Count; i++) { leftArr[i] = ToXZ(funnel.left[i]); rightArr[i] = ToXZ(funnel.right[i]); } } int startIndex = FixFunnel(leftArr, rightArr, funnel.left.Count); var intermediateResult = ListPool <int> .Claim(); if (startIndex == -1) { // If funnel algorithm failed, fall back to a simple line intermediateResult.Add(0); intermediateResult.Add(funnel.left.Count - 1); } else { bool lastCorner; Calculate(leftArr, rightArr, funnel.left.Count, startIndex, intermediateResult, int.MaxValue, out lastCorner); } // Get list for the final result var result = ListPool <Vector3> .Claim(intermediateResult.Count); Vector2 prev2D = leftArr[0]; var prevIdx = 0; for (int i = 0; i < intermediateResult.Count; i++) { var idx = intermediateResult[i]; if (splitAtEveryPortal) { // Check intersections with every portal segment var next2D = idx >= 0 ? leftArr[idx] : rightArr[-idx]; for (int j = prevIdx + 1; j < System.Math.Abs(idx); j++) { var factor = VectorMath.LineIntersectionFactorXZ(FromXZ(leftArr[j]), FromXZ(rightArr[j]), FromXZ(prev2D), FromXZ(next2D)); result.Add(Vector3.Lerp(funnel.left[j], funnel.right[j], factor)); } prevIdx = Mathf.Abs(idx); prev2D = next2D; } if (idx >= 0) { result.Add(funnel.left[idx]); } else { result.Add(funnel.right[-idx]); } } // Release lists back to the pool ListPool <int> .Release(ref intermediateResult); ArrayPool <Vector2> .Release(ref leftArr); ArrayPool <Vector2> .Release(ref rightArr); return(result); }
/** Calculate the shortest path through the funnel. * \param funnel The portals of the funnel. The first and last vertices portals must be single points (so for example left[0] == right[0]). * \param unwrap Determines if twists and bends should be straightened out before running the funnel algorithm. * \param splitAtEveryPortal If true, then a vertex will be inserted every time the path crosses a portal * instead of only at the corners of the path. The result will have exactly one vertex per portal if this is enabled. * This may introduce vertices with the same position in the output (esp. in corners where many portals meet). * * If the unwrap option is disabled the funnel will simply be projected onto the XZ plane. * If the unwrap option is enabled then the funnel may be oriented arbitrarily and may have twists and bends. * This makes it possible to support the funnel algorithm in XY space as well as in more complicated cases, such * as on curved worlds. * \shadowimage{funnel_unwrap_illustration.png} * * \shadowimage{funnel_split_at_every_portal.png} * * \see Unwrap */ public static List <Vector3> Calculate(FunnelPortals funnel, bool unwrap, bool splitAtEveryPortal) { var leftArr = new Vector2[funnel.left.Count]; var rightArr = new Vector2[funnel.left.Count]; if (unwrap) { Unwrap(funnel, leftArr, rightArr); } else { // Copy to arrays for (int i = 0; i < leftArr.Length; i++) { leftArr[i] = ToXZ(funnel.left[i]); rightArr[i] = ToXZ(funnel.right[i]); } } var origLeft = leftArr; int startIndex = FixFunnel(ref leftArr, ref rightArr); var left3D = funnel.left; var right3D = funnel.right; if (origLeft != leftArr) { // Flipped order left3D = funnel.right; right3D = funnel.left; } var intermediateResult = ListPool <int> .Claim(); if (startIndex == -1) { // If funnel algorithm failed, degrade to simple line intermediateResult.Add(0); intermediateResult.Add(funnel.left.Count - 1); } else { bool lastCorner; Calculate(leftArr, rightArr, startIndex, intermediateResult, int.MaxValue, out lastCorner); } // Get list for the final result var result = ListPool <Vector3> .Claim(intermediateResult.Count); Vector2 prev2D = leftArr[0]; var prevIdx = 0; for (int i = 0; i < intermediateResult.Count; i++) { var idx = intermediateResult[i]; if (splitAtEveryPortal) { // Check intersections with every portal segment var next2D = idx >= 0 ? leftArr[idx] : rightArr[-idx]; for (int j = prevIdx + 1; j < System.Math.Abs(idx); j++) { var factor = VectorMath.LineIntersectionFactorXZ(FromXZ(leftArr[j]), FromXZ(rightArr[j]), FromXZ(prev2D), FromXZ(next2D)); result.Add(Vector3.Lerp(left3D[j], right3D[j], factor)); } prevIdx = Mathf.Abs(idx); prev2D = next2D; } if (idx >= 0) { result.Add(left3D[idx]); } else { result.Add(right3D[-idx]); } } // Release lists back to the pool ListPool <Vector3> .Release(funnel.left); ListPool <Vector3> .Release(funnel.right); ListPool <int> .Release(intermediateResult); return(result); }