terraria-source-code/Physics/BallCollision.cs
2021-10-26 12:45:26 -04:00

371 lines
16 KiB
C#

// Decompiled with JetBrains decompiler
// Type: Terraria.Physics.BallCollision
// Assembly: Terraria, Version=1.4.0.5, Culture=neutral, PublicKeyToken=null
// MVID: 67F9E73E-0A81-4937-A22C-5515CD405A83
// Assembly location: C:\Users\mikeyisbaeyt\Downloads\depotdownloader-2.4.5\depots\105601\6707058\Terraria.exe
using Microsoft.Xna.Framework;
using System;
using System.Diagnostics;
using Terraria.DataStructures;
namespace Terraria.Physics
{
public static class BallCollision
{
public static BallStepResult Step(
PhysicsProperties physicsProperties,
Entity entity,
ref float entityAngularVelocity,
IBallContactListener listener)
{
Vector2 position = entity.position;
Vector2 velocity1 = entity.velocity;
Vector2 size = entity.Size;
float num1 = entityAngularVelocity;
float num2 = size.X * 0.5f;
float num3 = num1 * physicsProperties.Drag;
Vector2 vector2_1 = velocity1 * physicsProperties.Drag;
float num4 = vector2_1.Length();
if ((double) num4 > 1000.0)
{
vector2_1 = 1000f * Vector2.Normalize(vector2_1);
num4 = 1000f;
}
int num5 = Math.Max(1, (int) Math.Ceiling((double) num4 / 2.0));
float timeScale = 1f / (float) num5;
Vector2 velocity2 = vector2_1 * timeScale;
float angularVelocity = num3 * timeScale;
float num6 = physicsProperties.Gravity / (float) (num5 * num5);
bool flag = false;
for (int index = 0; index < num5; ++index)
{
velocity2.Y += num6;
BallPassThroughType type;
Tile tile;
if (BallCollision.CheckForPassThrough(position + size * 0.5f, out type, out tile))
{
if (type == BallPassThroughType.Tile && Main.tileSolid[(int) tile.type] && !Main.tileSolidTop[(int) tile.type])
{
velocity2 *= 0.0f;
angularVelocity *= 0.0f;
flag = true;
}
else
{
BallPassThroughEvent passThrough = new BallPassThroughEvent(timeScale, tile, entity, type);
listener.OnPassThrough(physicsProperties, ref position, ref velocity2, ref angularVelocity, ref passThrough);
}
}
position += velocity2;
if (!BallCollision.IsBallInWorld(position, size))
return BallStepResult.OutOfBounds();
Vector2 collisionPoint;
if (BallCollision.GetClosestEdgeToCircle(position, size, velocity2, out collisionPoint, out tile))
{
Vector2 normal = Vector2.Normalize(position + size * 0.5f - collisionPoint);
position = collisionPoint + normal * (num2 + 0.0001f) - size * 0.5f;
BallCollisionEvent collision = new BallCollisionEvent(timeScale, normal, collisionPoint, tile, entity);
flag = true;
velocity2 = Vector2.Reflect(velocity2, collision.Normal);
listener.OnCollision(physicsProperties, ref position, ref velocity2, ref collision);
angularVelocity = (float) ((double) collision.Normal.X * (double) velocity2.Y - (double) collision.Normal.Y * (double) velocity2.X) / num2;
}
}
Vector2 vector2_2 = velocity2 / timeScale;
float num7 = angularVelocity / timeScale;
BallStepResult ballStepResult = BallStepResult.Moving();
if (flag && (double) vector2_2.X > -0.00999999977648258 && (double) vector2_2.X < 0.00999999977648258 && (double) vector2_2.Y <= 0.0 && (double) vector2_2.Y > -(double) physicsProperties.Gravity)
ballStepResult = BallStepResult.Resting();
entity.position = position;
entity.velocity = vector2_2;
entityAngularVelocity = num7;
return ballStepResult;
}
private static bool CheckForPassThrough(
Vector2 center,
out BallPassThroughType type,
out Tile contactTile)
{
Point tileCoordinates = center.ToTileCoordinates();
Tile tile = Main.tile[tileCoordinates.X, tileCoordinates.Y];
contactTile = tile;
type = BallPassThroughType.None;
if (tile == null)
return false;
if (tile.nactive())
{
type = BallPassThroughType.Tile;
return BallCollision.IsPositionInsideTile(center, tileCoordinates, tile);
}
if (tile.liquid <= (byte) 0)
return false;
float num = (float) ((double) (tileCoordinates.Y + 1) * 16.0 - (double) tile.liquid / (double) byte.MaxValue * 16.0);
switch (tile.liquidType())
{
case 1:
type = BallPassThroughType.Lava;
break;
case 2:
type = BallPassThroughType.Honey;
break;
default:
type = BallPassThroughType.Water;
break;
}
return (double) num < (double) center.Y;
}
private static bool IsPositionInsideTile(Vector2 position, Point tileCoordinates, Tile tile)
{
if (tile.slope() == (byte) 0 && !tile.halfBrick())
return true;
Vector2 vector2 = position / 16f - new Vector2((float) tileCoordinates.X, (float) tileCoordinates.Y);
switch (tile.slope())
{
case 0:
return (double) vector2.Y > 0.5;
case 1:
return (double) vector2.Y > (double) vector2.X;
case 2:
return (double) vector2.Y > 1.0 - (double) vector2.X;
case 3:
return (double) vector2.Y < 1.0 - (double) vector2.X;
case 4:
return (double) vector2.Y < (double) vector2.X;
default:
return false;
}
}
private static bool IsBallInWorld(Vector2 position, Vector2 size) => (double) position.X > 32.0 && (double) position.Y > 32.0 && (double) position.X + (double) size.X < (double) Main.maxTilesX * 16.0 - 32.0 && (double) position.Y + (double) size.Y < (double) Main.maxTilesY * 16.0 - 32.0;
private static bool GetClosestEdgeToCircle(
Vector2 position,
Vector2 size,
Vector2 velocity,
out Vector2 collisionPoint,
out Tile collisionTile)
{
Rectangle tileBounds = BallCollision.GetTileBounds(position, size);
Vector2 center = position + size * 0.5f;
BallCollision.TileEdges tileEdges1 = BallCollision.TileEdges.None;
BallCollision.TileEdges tileEdges2 = (double) velocity.Y >= 0.0 ? tileEdges1 | BallCollision.TileEdges.Top : tileEdges1 | BallCollision.TileEdges.Bottom;
BallCollision.TileEdges tileEdges3 = (double) velocity.X >= 0.0 ? tileEdges2 | BallCollision.TileEdges.Left : tileEdges2 | BallCollision.TileEdges.Right;
BallCollision.TileEdges tileEdges4 = (double) velocity.Y <= (double) velocity.X ? tileEdges3 | BallCollision.TileEdges.TopRightSlope : tileEdges3 | BallCollision.TileEdges.BottomLeftSlope;
BallCollision.TileEdges edgesToTest = (double) velocity.Y <= -(double) velocity.X ? tileEdges4 | BallCollision.TileEdges.TopLeftSlope : tileEdges4 | BallCollision.TileEdges.BottomRightSlope;
collisionPoint = Vector2.Zero;
collisionTile = (Tile) null;
float num1 = float.MaxValue;
Vector2 closestPointOut = new Vector2();
float distanceSquaredOut = 0.0f;
for (int left = tileBounds.Left; left < tileBounds.Right; ++left)
{
for (int top = tileBounds.Top; top < tileBounds.Bottom; ++top)
{
if (BallCollision.GetCollisionPointForTile(edgesToTest, left, top, center, ref closestPointOut, ref distanceSquaredOut) && (double) distanceSquaredOut < (double) num1 && (double) Vector2.Dot(velocity, center - closestPointOut) <= 0.0)
{
num1 = distanceSquaredOut;
collisionPoint = closestPointOut;
collisionTile = Main.tile[left, top];
}
}
}
float num2 = size.X / 2f;
return (double) num1 < (double) num2 * (double) num2;
}
private static bool GetCollisionPointForTile(
BallCollision.TileEdges edgesToTest,
int x,
int y,
Vector2 center,
ref Vector2 closestPointOut,
ref float distanceSquaredOut)
{
Tile tile = Main.tile[x, y];
if (tile == null || !tile.nactive() || !Main.tileSolid[(int) tile.type] && !Main.tileSolidTop[(int) tile.type] || !Main.tileSolid[(int) tile.type] && Main.tileSolidTop[(int) tile.type] && tile.frameY != (short) 0)
return false;
if (Main.tileSolidTop[(int) tile.type])
edgesToTest &= BallCollision.TileEdges.Top | BallCollision.TileEdges.BottomLeftSlope | BallCollision.TileEdges.BottomRightSlope;
Vector2 tilePosition = new Vector2((float) x * 16f, (float) y * 16f);
bool flag = false;
LineSegment edge = new LineSegment();
if (BallCollision.GetSlopeEdge(ref edgesToTest, tile, tilePosition, ref edge))
{
closestPointOut = BallCollision.ClosestPointOnLineSegment(center, edge);
distanceSquaredOut = Vector2.DistanceSquared(closestPointOut, center);
flag = true;
}
if (BallCollision.GetTopOrBottomEdge(edgesToTest, x, y, tilePosition, ref edge))
{
Vector2 vector2 = BallCollision.ClosestPointOnLineSegment(center, edge);
float num = Vector2.DistanceSquared(vector2, center);
if (!flag || (double) num < (double) distanceSquaredOut)
{
distanceSquaredOut = num;
closestPointOut = vector2;
}
flag = true;
}
if (BallCollision.GetLeftOrRightEdge(edgesToTest, x, y, tilePosition, ref edge))
{
Vector2 vector2 = BallCollision.ClosestPointOnLineSegment(center, edge);
float num = Vector2.DistanceSquared(vector2, center);
if (!flag || (double) num < (double) distanceSquaredOut)
{
distanceSquaredOut = num;
closestPointOut = vector2;
}
flag = true;
}
return flag;
}
private static bool GetSlopeEdge(
ref BallCollision.TileEdges edgesToTest,
Tile tile,
Vector2 tilePosition,
ref LineSegment edge)
{
switch (tile.slope())
{
case 0:
return false;
case 1:
edgesToTest &= BallCollision.TileEdges.Bottom | BallCollision.TileEdges.Left | BallCollision.TileEdges.BottomLeftSlope;
if ((edgesToTest & BallCollision.TileEdges.BottomLeftSlope) == BallCollision.TileEdges.None)
return false;
edge.Start = tilePosition;
edge.End = new Vector2(tilePosition.X + 16f, tilePosition.Y + 16f);
return true;
case 2:
edgesToTest &= BallCollision.TileEdges.Bottom | BallCollision.TileEdges.Right | BallCollision.TileEdges.BottomRightSlope;
if ((edgesToTest & BallCollision.TileEdges.BottomRightSlope) == BallCollision.TileEdges.None)
return false;
edge.Start = new Vector2(tilePosition.X, tilePosition.Y + 16f);
edge.End = new Vector2(tilePosition.X + 16f, tilePosition.Y);
return true;
case 3:
edgesToTest &= BallCollision.TileEdges.Top | BallCollision.TileEdges.Left | BallCollision.TileEdges.TopLeftSlope;
if ((edgesToTest & BallCollision.TileEdges.TopLeftSlope) == BallCollision.TileEdges.None)
return false;
edge.Start = new Vector2(tilePosition.X, tilePosition.Y + 16f);
edge.End = new Vector2(tilePosition.X + 16f, tilePosition.Y);
return true;
case 4:
edgesToTest &= BallCollision.TileEdges.Top | BallCollision.TileEdges.Right | BallCollision.TileEdges.TopRightSlope;
if ((edgesToTest & BallCollision.TileEdges.TopRightSlope) == BallCollision.TileEdges.None)
return false;
edge.Start = tilePosition;
edge.End = new Vector2(tilePosition.X + 16f, tilePosition.Y + 16f);
return true;
default:
return false;
}
}
private static bool GetTopOrBottomEdge(
BallCollision.TileEdges edgesToTest,
int x,
int y,
Vector2 tilePosition,
ref LineSegment edge)
{
if ((edgesToTest & BallCollision.TileEdges.Bottom) != BallCollision.TileEdges.None)
{
Tile tile = Main.tile[x, y + 1];
if ((!BallCollision.IsNeighborSolid(tile) || tile.slope() == (byte) 1 || tile.slope() == (byte) 2 ? 1 : (tile.halfBrick() ? 1 : 0)) == 0)
return false;
edge.Start = new Vector2(tilePosition.X, tilePosition.Y + 16f);
edge.End = new Vector2(tilePosition.X + 16f, tilePosition.Y + 16f);
return true;
}
if ((edgesToTest & BallCollision.TileEdges.Top) == BallCollision.TileEdges.None)
return false;
Tile tile1 = Main.tile[x, y - 1];
if ((Main.tile[x, y].halfBrick() || !BallCollision.IsNeighborSolid(tile1) || tile1.slope() == (byte) 3 ? 1 : (tile1.slope() == (byte) 4 ? 1 : 0)) == 0)
return false;
if (Main.tile[x, y].halfBrick())
tilePosition.Y += 8f;
edge.Start = new Vector2(tilePosition.X, tilePosition.Y);
edge.End = new Vector2(tilePosition.X + 16f, tilePosition.Y);
return true;
}
private static bool GetLeftOrRightEdge(
BallCollision.TileEdges edgesToTest,
int x,
int y,
Vector2 tilePosition,
ref LineSegment edge)
{
if ((edgesToTest & BallCollision.TileEdges.Left) != BallCollision.TileEdges.None)
{
Tile tile1 = Main.tile[x, y];
Tile tile2 = Main.tile[x - 1, y];
if ((!BallCollision.IsNeighborSolid(tile2) || tile2.slope() == (byte) 1 || tile2.slope() == (byte) 3 ? 1 : (!tile2.halfBrick() ? 0 : (!tile1.halfBrick() ? 1 : 0))) == 0)
return false;
edge.Start = new Vector2(tilePosition.X, tilePosition.Y);
edge.End = new Vector2(tilePosition.X, tilePosition.Y + 16f);
if (tile1.halfBrick())
edge.Start.Y += 8f;
return true;
}
if ((edgesToTest & BallCollision.TileEdges.Right) == BallCollision.TileEdges.None)
return false;
Tile tile3 = Main.tile[x, y];
Tile tile4 = Main.tile[x + 1, y];
if ((!BallCollision.IsNeighborSolid(tile4) || tile4.slope() == (byte) 2 || tile4.slope() == (byte) 4 ? 1 : (!tile4.halfBrick() ? 0 : (!tile3.halfBrick() ? 1 : 0))) == 0)
return false;
edge.Start = new Vector2(tilePosition.X + 16f, tilePosition.Y);
edge.End = new Vector2(tilePosition.X + 16f, tilePosition.Y + 16f);
if (tile3.halfBrick())
edge.Start.Y += 8f;
return true;
}
private static Rectangle GetTileBounds(Vector2 position, Vector2 size)
{
int x = (int) Math.Floor((double) position.X / 16.0);
int y = (int) Math.Floor((double) position.Y / 16.0);
int num1 = (int) Math.Floor(((double) position.X + (double) size.X) / 16.0);
int num2 = (int) Math.Floor(((double) position.Y + (double) size.Y) / 16.0);
return new Rectangle(x, y, num1 - x + 1, num2 - y + 1);
}
private static bool IsNeighborSolid(Tile tile) => tile != null && tile.nactive() && Main.tileSolid[(int) tile.type] && !Main.tileSolidTop[(int) tile.type];
private static Vector2 ClosestPointOnLineSegment(Vector2 point, LineSegment lineSegment)
{
Vector2 vector2_1 = point - lineSegment.Start;
Vector2 vector2_2 = lineSegment.End - lineSegment.Start;
float num1 = vector2_2.LengthSquared();
Vector2 vector2_3 = vector2_2;
float num2 = Vector2.Dot(vector2_1, vector2_3) / num1;
if ((double) num2 < 0.0)
return lineSegment.Start;
return (double) num2 > 1.0 ? lineSegment.End : lineSegment.Start + vector2_2 * num2;
}
[Conditional("DEBUG")]
private static void DrawEdge(LineSegment edge)
{
}
[Flags]
private enum TileEdges : uint
{
None = 0,
Top = 1,
Bottom = 2,
Left = 4,
Right = 8,
TopLeftSlope = 16, // 0x00000010
TopRightSlope = 32, // 0x00000020
BottomLeftSlope = 64, // 0x00000040
BottomRightSlope = 128, // 0x00000080
}
}
}