This document is about: QUANTUM 1
SWITCH TO

Normal from Shape and Contact Point

When doing raycasts, they do not provide contact normal.
You can use this code to get it.

C#

public static unsafe class DynamicHitExts {
  public static FPVector2 normal(
    this DynamicHit hit, Frame f
  ) {
    var entity = (Entity*) hit.Entity;
    if (entity == null) {
      var collider = f.Map.StaticColliders[hit.StaticData.ColliderId - 1];
      return collider.normal(hit.Point);
    }
    else {
      var body = Entity.GetDynamicBody(entity);
      var t2d = Entity.GetTransform2D(entity);
      return body->Shape.normal(t2d, hit.Point);
    }
  }
}
  
public static class MapStaticColliderExts {
  /// <param name="collider"></param>
  /// <param name="contactPoint">world position</param>
  /// <exception cref="ArgumentOutOfRangeException"></exception>
  public static FPVector2 normal(this MapStaticCollider collider, FPVector2 contactPoint) {
    FPVector2 rot(FPVector2 v) => FPVector2.Rotate(v, collider.Rotation);
    
    var relativeContactPoint = FPVector2.Rotate(contactPoint - collider.Position, -collider.Rotation);
    switch (collider.ShapeType) {
      case DynamicShapeType.Circle:
        return rot(DynamicShapeExts.circleNormal(relativeContactPoint));
      case DynamicShapeType.Polygon:
        return rot(DynamicShapeExts.polygonNormal(collider.PolygonCollider, relativeContactPoint));
      case DynamicShapeType.Box:
        return rot(DynamicShapeExts.boxNormal(collider.BoxExtents, relativeContactPoint));
      case DynamicShapeType.None:
      default:
        throw new ArgumentOutOfRangeException(nameof(collider.ShapeType), collider.ShapeType, "unknown shape type");
    }
  }
}
  
public static unsafe class DynamicShapeExts {
  static bool checkNormal(
    FPVector2 start, FPVector2 end, FPVector2 contactPoint, ref FP currentCross
  ) {
    var vCross = FPMath.Abs(FPVector2.Cross(end - contactPoint, end - start));
    if (vCross < currentCross) {
      currentCross = vCross;
      return true;
    }
  
    return false;
  }
  
  public static FPVector2 circleNormal(FPVector2 contactPoint) => contactPoint.Normalized;

  public static FPVector2 boxNormal(FPVector2 extents, FPVector2 contactPoint) {
    extents.extentsPoints(out var bottomLeft, out var bottomRight, out var topLeft, out var topRight);
    var normal = FPVector2.Zero;
    var currentCross = FP.MaxValue;
    if (checkNormal(bottomLeft, topLeft, contactPoint, ref currentCross)) normal = FPVector2.Left;
    if (checkNormal(bottomLeft, bottomRight, contactPoint, ref currentCross)) normal = FPVector2.Down;
    if (checkNormal(topLeft, topRight, contactPoint, ref currentCross)) normal = FPVector2.Up;
    if (checkNormal(bottomRight, topRight, contactPoint, ref currentCross)) normal = FPVector2.Right;
    return normal;
  }
  
  public static FPVector2 polygonNormal(PolygonCollider collider, FPVector2 contactPoint) {
    var maxIdx = collider.Vertices.Length - 1;
    var normal = FPVector2.Zero;
    var currentCross = FP.MaxValue;
    for (var idx = 0; idx < maxIdx; idx++) {
      if (checkNormal(collider.Vertices[idx], collider.Vertices[idx + 1], contactPoint, ref currentCross))
        normal = collider.Normals[idx];
    }
  
    if (checkNormal(collider.Vertices[maxIdx], collider.Vertices[0], contactPoint, ref currentCross))
      normal = collider.Normals[maxIdx];
  
    return normal;
  }
  
  /// <param name="shape"></param>
  /// <param name="contactPoint">relative to this shape</param>
  /// <returns></returns>
  /// <exception cref="ArgumentOutOfRangeException"></exception>
  public static FPVector2 normal(this DynamicShape shape, FPVector2 contactPoint) {
    var normal = FPVector2.Zero;
    var currentCross = FP.MaxValue;
    
    switch (shape.Type) {
      case DynamicShapeType.Circle: return circleNormal(contactPoint);
      case DynamicShapeType.Box: return boxNormal(shape.Box.Extents, contactPoint);
      case DynamicShapeType.Polygon:
        var collider = (PolygonCollider) DB.FastUnsafe[shape.Polygon.AssetId];
        return polygonNormal(collider, contactPoint);
      case DynamicShapeType.None: 
      default:
        throw new ArgumentOutOfRangeException(nameof(shape.Type), shape.Type, "Unknown shape type");
    }
  }
  
  /// <param name="shape"></param>
  /// <param name="t2d"></param>
  /// <param name="contactPoint">in world space</param>
  /// <returns></returns>
  public static FPVector2 normal(
    this DynamicShape shape, Transform2D* t2d, FPVector2 contactPoint
  ) {
    var relativeContactPoint = FPVector2.Rotate(contactPoint - t2d->Position, -t2d->Rotation);
    return FPVector2.Rotate(shape.normal(relativeContactPoint), t2d->Rotation);
  }
}
Back to top