public static float InOut(float k) { if ((k *= 2f) < 1f) { return(-0.5f * (Mathf.Sqrt(1f - k * k) - 1)); } return(0.5f * (Mathf.Sqrt(1f - (k -= 2f) * k) + 1f)); }
/// <summary> /// Modeled after the piecewise circular function /// y = (1/2)(1 - Math.Sqrt(1 - 4x^2)) ; [0, 0.5) /// y = (1/2)(Math.Sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1] /// </summary> static public float CircularEaseInOut(float p) { if (p < 0.5f) { return(0.5f * (1.0f - Math.Sqrt(1.0f - 4.0f * (p * p)))); } else { return(0.5f * (Math.Sqrt(-((2.0f * p) - 3.0f) * ((2.0f * p) - 1.0f)) + 1.0f)); } }
/// <summary> /// Modeled after the piecewise circular function /// y = (1/2)(1 - Math.Sqrt(1 - 4x^2)) ; [0, 0.5) /// y = (1/2)(Math.Sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1] /// </summary> static public float CircularEaseInOut(float p) { if (p < 0.5f) { return(0.5f * (1 - Math.Sqrt(1 - 4 * (p * p)))); } else { return(0.5f * (Math.Sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1)); } }
/** Evaluate gradient and value of the cost function at velocity p */ Vector2 EvaluateGradient(VOBuffer vos, Vector2 p, out float value) { Vector2 gradient = Vector2.zero; value = 0; // Avoid other agents for (int i = 0; i < vos.length; i++) { float w; var grad = vos.buffer[i].ScaledGradient(p, out w); if (w > value) { value = w; gradient = grad; } } // Move closer to the desired velocity var dirToDesiredVelocity = desiredVelocity - p; var distToDesiredVelocity = dirToDesiredVelocity.magnitude; if (distToDesiredVelocity > 0.0001f) { gradient += dirToDesiredVelocity * (DesiredVelocityWeight / distToDesiredVelocity); value += distToDesiredVelocity * DesiredVelocityWeight; } // Prefer speeds lower or equal to the desired speed // and avoid speeds greater than the max speed var sqrSpeed = p.sqrMagnitude; if (sqrSpeed > desiredSpeed * desiredSpeed) { var speed = Mathf.Sqrt(sqrSpeed); if (speed > maxSpeed) { const float MaxSpeedWeight = 3; value += MaxSpeedWeight * (speed - maxSpeed); gradient -= MaxSpeedWeight * (p / speed); } // Scale needs to be strictly greater than DesiredVelocityWeight // otherwise the agent will not prefer the desired speed over // the maximum speed float scale = 2 * DesiredVelocityWeight; value += scale * (speed - desiredSpeed); gradient -= scale * (p / speed); } return(gradient); }
public static float Circ_EaseOut(float a, float b, float t) { if (t > 1.0F) { t = 1.0F; } if (t < 0.0F) { t = 0.0F; } float x = (float)Math.Sqrt(1 - (t - 1f) * (t - 1f)); // F(x) = sqrt(1 - (x-1)*(x-1)) for x in (0,1) return(a + x * (b - a)); }
private static void Reconstruct(byte[] components, out float a, out float b, out float c, out float r, bool shouldNegate) { a = Read7BitFloat(components[0]); b = Read7BitFloat(components[1]); c = Read7BitFloat(components[2]); //Reconstruct r = Mathf.Sqrt(1 - (a * a) - (b * b) - (c * c)); if (shouldNegate) { a = -a; b = -b; c = -c; } }
public static float Circ_EaseIn(float a, float b, float t) { if (t > 1.0F) { t = 1.0F; } if (t < 0.0F) { t = 0.0F; } float x = -((float)Math.Sqrt(1 - t * t) - 1.0F); // F(x) = -(sqrt(1 - x*x) - 1) for x in (0,1) return(a + x * (b - a)); }
/** Returns points in a spiral centered around the origin with a minimum clearance from other points. * The points are laid out on the involute of a circle * \see http://en.wikipedia.org/wiki/Involute * Which has some nice properties. * All points are separated by \a clearance world units. * This method is O(n), yes if you read the code you will see a binary search, but that binary search * has an upper bound on the number of steps, so it does not yield a log factor. * * \note Consider recycling the list after usage to reduce allocations. * \see Pathfinding.Util.ListPool */ public static List <Vector3> GetSpiralPoints(int count, float clearance) { List <Vector3> pts = ListPool <Vector3> .Claim(count); // The radius of the smaller circle used for generating the involute of a circle // Calculated from the separation distance between the turns float a = clearance / (2 * Mathf.PI); float t = 0; pts.Add(InvoluteOfCircle(a, t)); for (int i = 0; i < count; i++) { Vector3 prev = pts[pts.Count - 1]; // d = -t0/2 + sqrt( t0^2/4 + 2d/a ) // Minimum angle (radians) which would create an arc distance greater than clearance float d = -t / 2 + Mathf.Sqrt(t * t / 4 + 2 * clearance / a); // Binary search for separating this point and the previous one float mn = t + d; float mx = t + 2 * d; while (mx - mn > 0.01f) { float mid = (mn + mx) / 2; Vector3 p = InvoluteOfCircle(a, mid); if ((p - prev).sqrMagnitude < clearance * clearance) { mn = mid; } else { mx = mid; } } pts.Add(InvoluteOfCircle(a, mx)); t = mx; } return(pts); }
void Tick() { if (Event.current.type == EventType.Repaint) { float deltaTime = Time.realtimeSinceStartup - lastUpdate; // Right at the start of a transition the deltaTime will // not be reliable, so use a very small value instead // until the next repaint if (value == 0f || value == 1f) { deltaTime = 0.001f; } deltaTime = Mathf.Clamp(deltaTime, 0.00001F, 0.1F); // Larger regions fade slightly slower deltaTime /= Mathf.Sqrt(Mathf.Max(lastRect.height, 100)); lastUpdate = Time.realtimeSinceStartup; float targetValue = open ? 1F : 0F; if (!Mathf.Approximately(targetValue, value)) { value += deltaTime * animationSpeed * Mathf.Sign(targetValue - value); value = Mathf.Clamp01(value); editor.Repaint(); if (!fancyEffects) { value = targetValue; } } else { value = targetValue; } } }
public void CircleXZ(Vector3 center, float radius, Color color, float startAngle = 0f, float endAngle = 2 *Mathf.PI) { int steps = 40; #if UNITY_EDITOR if (gizmos) { steps = (int)Mathf.Clamp(Mathf.Sqrt(radius / UnityEditor.HandleUtility.GetHandleSize((UnityEngine.Gizmos.matrix * matrix).MultiplyPoint3x4(center))) * 25, 4, 40); } #endif while (startAngle > endAngle) { startAngle -= 2 * Mathf.PI; } Vector3 prev = new Vector3(Mathf.Cos(startAngle) * radius, 0, Mathf.Sin(startAngle) * radius); for (int i = 0; i <= steps; i++) { Vector3 c = new Vector3(Mathf.Cos(Mathf.Lerp(startAngle, endAngle, i / (float)steps)) * radius, 0, Mathf.Sin(Mathf.Lerp(startAngle, endAngle, i / (float)steps)) * radius); Line(center + prev, center + c, color); prev = c; } }
public static float Circ_EaseInOut(float a, float b, float t) { if (t > 1.0F) { t = 1.0F; } if (t < 0.0F) { t = 0.0F; } float x = t; if (t <= 0.5f) { x = -0.5F * ((float)Math.Sqrt(1.0F - t * t * 4) - 1.0F); // x <= 0,5: F(x) = -0.5*(sqrt(1 - x*x*4) - 1) in (0, 0.5) } else { x = (float)Math.Sqrt(-(t - 0.5F) * (t - 1.5F)) + 0.5F; // x > 0,5: F(x) = 0.5*(sqrt(1 - (x-1)*(x-1)*4) +1 ) === (sqrt(-(x-0.5)*(x-1.5)) +0.5) in (0.5, 1) } return(a + x * (b - a)); }
/// <summary> /// Modeled after shifted quadrant IV of unit circle /// </summary> static public float CircularEaseIn(float p) { return(1 - Math.Sqrt(1 - (p * p))); }
void Update() { Vec3 pos; /* Cosine of the angle on the X-Z ("horizontal") plane */ float xCosTeta; /* Sine of the angle on the Z-Y ("vertical") plane */ float ySinPhi; if (this.player == null) { GO pl = null; this.rootEvent <GetPlayer>((x, y) => x.Get(out pl)); if (pl != null) { this.player = pl.transform; } return; } if (!Input.GetMouseCameraEnabled()) { /* Try to manipulate the camera using a gamepad */ xCosTeta = Global.camX * -1.0f * Input.GetCameraX(); ySinPhi = Global.camY * Input.GetCameraY(); this.wasUsingMouse = false; } else { if (this.wasUsingMouse) { /* Move the camera, using a 50px (?) circle around the mouse */ Vec3 mouseDelta = Input.GetMousePosition() - this.mouse; xCosTeta = Global.camX * -1.0f * mouseDelta.x * 0.02f; ySinPhi = Global.camY * -1.0f * mouseDelta.y * 0.02f; ySinPhi = Math.Clamp(ySinPhi, -1.0f, 1.0f); } else { /* Use the current position as the mouse's origin */ this.mouse = Input.GetMousePosition(); this.wasUsingMouse = true; xCosTeta = 0.0f; ySinPhi = 0.0f; } } xCosTeta = Math.Clamp(xCosTeta, -0.8f, 0.8f); float dist = Math.Sqrt(xCosTeta * xCosTeta + ySinPhi * ySinPhi); if (!this.wasUsingMouse && dist < 0.5f) { pos = new Vec3(this.baseDX, this.baseDY, this.baseDZ); } else { float zSinTeta = -1.0f * Math.Sqrt(1.0f - xCosTeta * xCosTeta); float zCosPhi = -1.0f * Math.Sqrt(1.0f - ySinPhi * ySinPhi); pos = new Vec3(xCosTeta, ySinPhi, (zSinTeta + zCosPhi) * 0.5f); } pos = pos.normalized * this.distance; this.lastPos = 0.75f * this.lastPos + pos * 0.25f; this.cam.position = this.player.position + this.lastPos; this.cam.LookAt(this.player); }
/** Will calculate a number of points around \a center which are on the graph and are separated by \a clearance from each other. * The maximum distance from \a center to any point will be \a radius. * Points will first be tried to be laid out as \a previousPoints and if that fails, random points will be selected. * This is great if you want to pick a number of target points for group movement. If you pass all current agent points from e.g the group's average position * this method will return target points so that the units move very little within the group, this is often aesthetically pleasing and reduces jitter if using * some kind of local avoidance. * * \param center The point to generate points around * \param g The graph to use for linecasting. If you are only using one graph, you can get this by AstarPath.active.graphs[0] as IRaycastableGraph. * Note that not all graphs are raycastable, recast, navmesh and grid graphs are raycastable. On recast and navmesh it works the best. * \param previousPoints The points to use for reference. Note that these should not be in world space. They are treated as relative to \a center. * The new points will overwrite the existing points in the list. The result will be in world space, not relative to \a center. * \param radius The final points will be at most this distance from \a center. * \param clearanceRadius The points will if possible be at least this distance from each other. * * \todo Write unit tests */ public static void GetPointsAroundPoint(Vector3 center, IRaycastableGraph g, List <Vector3> previousPoints, float radius, float clearanceRadius) { if (g == null) { throw new System.ArgumentNullException("g"); } var graph = g as NavGraph; if (graph == null) { throw new System.ArgumentException("g is not a NavGraph"); } NNInfoInternal nn = graph.GetNearestForce(center, NNConstraint.Default); center = nn.clampedPosition; if (nn.node == null) { // No valid point to start from return; } // Make sure the enclosing circle has a radius which can pack circles with packing density 0.5 radius = Mathf.Max(radius, 1.4142f * clearanceRadius * Mathf.Sqrt(previousPoints.Count)); //Mathf.Sqrt(previousPoints.Count*clearanceRadius*2)); clearanceRadius *= clearanceRadius; for (int i = 0; i < previousPoints.Count; i++) { Vector3 dir = previousPoints[i]; float magn = dir.magnitude; if (magn > 0) { dir /= magn; } float newMagn = radius; //magn > radius ? radius : magn; dir *= newMagn; GraphHitInfo hit; int tests = 0; while (true) { Vector3 pt = center + dir; if (g.Linecast(center, pt, nn.node, out hit)) { if (hit.point == PF.Vector3.zero) { // Oops, linecast actually failed completely // try again unless we have tried lots of times // then we just continue anyway tests++; if (tests > 8) { previousPoints[i] = pt; break; } } else { pt = hit.point; } } bool worked = false; for (float q = 0.1f; q <= 1.0f; q += 0.05f) { Vector3 qt = Vector3.Lerp(center, pt, q); worked = true; for (int j = 0; j < i; j++) { if ((previousPoints[j] - qt).sqrMagnitude < clearanceRadius) { worked = false; break; } } // Abort after 8 tests or when we have found a valid point if (worked || tests > 8) { worked = true; previousPoints[i] = qt; break; } } // Break out of nested loop if (worked) { break; } // If we could not find a valid point, reduce the clearance radius slightly to improve // the chances next time clearanceRadius *= 0.9f; // This will pick points in 2D closer to the edge of the circle with a higher probability dir = Random.onUnitSphere * Mathf.Lerp(newMagn, radius, tests / 5); dir.y = 0; tests++; } } }
RasterizationMesh RasterizeCapsuleCollider(float radius, float height, Bounds bounds, Matrix4x4 localToWorldMatrix) { // Calculate the number of rows to use // grows as sqrt(x) to the radius of the sphere/capsule which I have found works quite well int rows = Mathf.Max(4, Mathf.RoundToInt(colliderRasterizeDetail * Mathf.Sqrt(localToWorldMatrix.MultiplyVector(Vector3.one).magnitude))); if (rows > 100) { Debug.LogWarning("Very large detail for some collider meshes. Consider decreasing Collider Rasterize Detail (RecastGraph)"); } int cols = rows; Vector3[] verts; int[] trisArr; // Check if we have already calculated a similar capsule CapsuleCache cached = null; for (int i = 0; i < capsuleCache.Count; i++) { CapsuleCache c = capsuleCache[i]; if (c.rows == rows && Mathf.Approximately(c.height, height)) { cached = c; } } if (cached == null) { // Generate a sphere/capsule mesh verts = new Vector3[(rows) * cols + 2]; var tris = new List <int>(); verts[verts.Length - 1] = Vector3.up; for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { verts[c + r * cols] = new Vector3(Mathf.Cos(c * Mathf.PI * 2 / cols) * Mathf.Sin((r * Mathf.PI / (rows - 1))), Mathf.Cos((r * Mathf.PI / (rows - 1))) + (r < rows / 2 ? height : -height), Mathf.Sin(c * Mathf.PI * 2 / cols) * Mathf.Sin((r * Mathf.PI / (rows - 1)))); } } verts[verts.Length - 2] = Vector3.down; for (int i = 0, j = cols - 1; i < cols; j = i++) { tris.Add(verts.Length - 1); tris.Add(0 * cols + j); tris.Add(0 * cols + i); } for (int r = 1; r < rows; r++) { for (int i = 0, j = cols - 1; i < cols; j = i++) { tris.Add(r * cols + i); tris.Add(r * cols + j); tris.Add((r - 1) * cols + i); tris.Add((r - 1) * cols + j); tris.Add((r - 1) * cols + i); tris.Add(r * cols + j); } } for (int i = 0, j = cols - 1; i < cols; j = i++) { tris.Add(verts.Length - 2); tris.Add((rows - 1) * cols + j); tris.Add((rows - 1) * cols + i); } // Add calculated mesh to the cache cached = new CapsuleCache(); cached.rows = rows; cached.height = height; cached.verts = verts; cached.tris = tris.ToArray(); capsuleCache.Add(cached); } // Read from cache verts = cached.verts; trisArr = cached.tris; return(new RasterizationMesh(verts, trisArr, bounds, localToWorldMatrix)); }
private static float GuassianFunc(float x) => Mathf.Exp((-(x * x)) / 2F) / Mathf.Sqrt(2F * Mathf.PI);
/** Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not */ protected override void MovementUpdateInternal(float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation) { float currentAcceleration = maxAcceleration; // If negative, calculate the acceleration from the max speed if (currentAcceleration < 0) { currentAcceleration *= -maxSpeed; } if (updatePosition) { // Get our current position. We read from transform.position as few times as possible as it is relatively slow // (at least compared to a local variable) simulatedPosition = tr.position; } if (updateRotation) { simulatedRotation = tr.rotation; } var currentPosition = simulatedPosition; // Update which point we are moving towards interpolator.MoveToCircleIntersection2D(currentPosition, pickNextWaypointDist, movementPlane); var dir = movementPlane.ToPlane(steeringTarget - currentPosition); // Calculate the distance to the end of the path float distanceToEnd = dir.magnitude + Mathf.Max(0, interpolator.remainingDistance); // Check if we have reached the target var prevTargetReached = reachedEndOfPath; reachedEndOfPath = distanceToEnd <= endReachedDistance && interpolator.valid; if (!prevTargetReached && reachedEndOfPath) { OnTargetReached(); } float slowdown; // Normalized direction of where the agent is looking var forwards = movementPlane.ToPlane(simulatedRotation * (rotationIn2D ? Vector3.up : Vector3.forward)); // Check if we have a valid path to follow and some other script has not stopped the character if (interpolator.valid && !isStopped) { // How fast to move depending on the distance to the destination. // Move slower as the character gets closer to the destination. // This is always a value between 0 and 1. slowdown = distanceToEnd < slowdownDistance?Mathf.Sqrt(distanceToEnd / slowdownDistance) : 1; if (reachedEndOfPath && whenCloseToDestination == CloseToDestinationMode.Stop) { // Slow down as quickly as possible velocity2D -= Vector2.ClampMagnitude(velocity2D, currentAcceleration * deltaTime); } else { velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir.ToUnityV2(), dir.normalized * maxSpeed, velocity2D, currentAcceleration, rotationSpeed, maxSpeed, forwards) * deltaTime; } } else { slowdown = 1; // Slow down as quickly as possible velocity2D -= Vector2.ClampMagnitude(velocity2D, currentAcceleration * deltaTime); } velocity2D = MovementUtilities.ClampVelocity(velocity2D, maxSpeed, slowdown, slowWhenNotFacingTarget, forwards.ToUnityV2()); ApplyGravity(deltaTime); if (rvoController != null && rvoController.enabled) { // Send a message to the RVOController that we want to move // with this velocity. In the next simulation step, this // velocity will be processed and it will be fed back to the // rvo controller and finally it will be used by this script // when calling the CalculateMovementDelta method below // Make sure that we don't move further than to the end point // of the path. If the RVO simulation FPS is low and we did // not do this, the agent might overshoot the target a lot. var rvoTarget = currentPosition.ToPFV3() + movementPlane.ToWorld(Vector2.ClampMagnitude(velocity2D, distanceToEnd), 0f); rvoController.SetTarget(rvoTarget, velocity2D.magnitude, maxSpeed); } // Set how much the agent wants to move during this frame var delta2D = lastDeltaPosition = CalculateDeltaToMoveThisFrame(movementPlane.ToPlane(currentPosition), distanceToEnd, deltaTime); nextPosition = currentPosition + movementPlane.ToWorld(delta2D.ToPFV2(), verticalVelocity * lastDeltaTime).ToUnityV3(); CalculateNextRotation(slowdown, out nextRotation); }
public static float Out(float k) { return(Mathf.Sqrt(1f - ((k -= 1f) * k))); }
public static float In(float k) { return(1f - Mathf.Sqrt(1f - k * k)); }
/// <summary> /// Modeled after shifted quadrant II of unit circle /// </summary> static public float CircularEaseOut(float p) { return(Math.Sqrt((2 - p) * p)); }
void Update() { if (Detector.targetTexture != null) { // get a copy of the pixels into a Texture2D // the script assumes the 2d and rendertexture are the same size if ever minds are changed this is a reminder // if (cameraTexture2d.width != cameraRenderTexture.width || cameraTexture2d.height != cameraRenderTexture.height) // { // cameraTexture2d.Resize(cameraRenderTexture.width, cameraRenderTexture.height); // } var previous = RenderTexture.active; RenderTexture.active = cameraRenderTexture; cameraTexture2d.ReadPixels(new Rect(0, 0, cameraRenderTexture.width, cameraRenderTexture.height), 0, 0); cameraTexture2d.Apply(false, false); RenderTexture.active = previous; // average the colors in the image var colors = cameraTexture2d.GetPixels32(); var total = colors.Length; var r = 0; var g = 0; var b = 0; for (int i = 0; i < total; i++) { r += colors[i].r; g += colors[i].g; b += colors[i].b; } var color = new Color(r / total, g / total, b / total); // https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color var brightness = Math.Sqrt( color.r * color.r * .299f + color.g * color.g * .587f + color.b * color.b * .114f ) / 255; if (brightness > 1) { brightness = 1; } else if (brightness < 0) { brightness = 0; } // set our public properties now DetectedColor = color; DetectedBrightness = brightness; #if LFE_DEBUG SuperController.LogMessage($"color = {color} brightness = {brightness}"); #endif // stop capturing the screen Detector.targetTexture = null; #if !LFE_DEBUG if (PollFrequency > 0) { Detector.enabled = false; } #endif } pollCountdown -= Time.deltaTime; if (pollCountdown > 0) { // wait return; } else { // schedule the next update to capture the screen #if !LFE_DEBUG if (PollFrequency > 0) { Detector.enabled = true; } #endif pollCountdown = PollFrequency; Detector.targetTexture = cameraRenderTexture; } }
public static float Magnitude(Vector4 a) { return(Mathf.Sqrt(Vector4.Dot(a, a))); }
/** Creates a VO for avoiding another agent. * Note that the segment is directed, the agent will want to be on the left side of the segment. */ public static VO SegmentObstacle(Vector2 segmentStart, Vector2 segmentEnd, Vector2 offset, float radius, float inverseDt, float inverseDeltaTime) { var vo = new VO(); // Adjusted so that a parameter weightFactor of 1 will be the default ("natural") weight factor vo.weightFactor = 1; // Just higher than anything else vo.weightBonus = Mathf.Max(radius, 1) * 40; var closestOnSegment = VectorMath.ClosestPointOnSegment(segmentStart.ToPFV2(), segmentEnd.ToPFV2(), Vector2.zero.ToPFV2()); // Collision? if (closestOnSegment.magnitude <= radius) { vo.colliding = true; vo.line1 = closestOnSegment.normalized.ToUnityV3() * (closestOnSegment.magnitude - radius) * 0.3f * inverseDeltaTime; vo.dir1 = new Vector2(vo.line1.y, -vo.line1.x).normalized; vo.line1 += offset; vo.cutoffDir = Vector2.zero; vo.cutoffLine = Vector2.zero; vo.dir2 = Vector2.zero; vo.line2 = Vector2.zero; vo.radius = 0; vo.segmentStart = Vector2.zero; vo.segmentEnd = Vector2.zero; vo.segment = false; } else { vo.colliding = false; segmentStart *= inverseDt; segmentEnd *= inverseDt; radius *= inverseDt; var cutoffTangent = (segmentEnd - segmentStart).normalized; vo.cutoffDir = cutoffTangent; vo.cutoffLine = segmentStart + new Vector2(-cutoffTangent.y, cutoffTangent.x) * radius; vo.cutoffLine += offset; // See documentation for details // The call to Max is just to prevent floating point errors causing NaNs to appear var startSqrMagnitude = segmentStart.sqrMagnitude; var normal1 = -VectorMath.ComplexMultiply(segmentStart, new Vector2(radius, Mathf.Sqrt(Mathf.Max(0, startSqrMagnitude - radius * radius)))) / startSqrMagnitude; var endSqrMagnitude = segmentEnd.sqrMagnitude; var normal2 = -VectorMath.ComplexMultiply(segmentEnd, new Vector2(radius, -Mathf.Sqrt(Mathf.Max(0, endSqrMagnitude - radius * radius)))) / endSqrMagnitude; vo.line1 = segmentStart + normal1.ToUnityV2() * radius + offset; vo.line2 = segmentEnd + normal2.ToUnityV2() * radius + offset; // Note that the normals are already normalized vo.dir1 = new Vector2(normal1.y, -normal1.x); vo.dir2 = new Vector2(normal2.y, -normal2.x); vo.segmentStart = segmentStart; vo.segmentEnd = segmentEnd; vo.radius = radius; vo.segment = true; } return(vo); }